##// END OF EJS Templates
sessions: added interface to show, and cleanup user auth sessions.
marcink -
r1295:5854ddda default
parent child Browse files
Show More
@@ -0,0 +1,125 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2017-2017 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 import datetime
22 import dateutil
23 from rhodecode.model.db import DbSession, Session
24
25
26 class CleanupCommand(Exception):
27 pass
28
29
30 class BaseAuthSessions(object):
31 SESSION_TYPE = None
32
33 def __init__(self, config):
34 session_conf = {}
35 for k, v in config.items():
36 if k.startswith('beaker.session'):
37 session_conf[k] = v
38 self.config = session_conf
39
40 def get_count(self):
41 raise NotImplementedError
42
43 def get_expired_count(self):
44 raise NotImplementedError
45
46 def clean_sessions(self, older_than_seconds=None):
47 raise NotImplementedError
48
49 def _seconds_to_date(self, seconds):
50 return datetime.datetime.utcnow() - dateutil.relativedelta.relativedelta(
51 seconds=seconds)
52
53
54 class DbAuthSessions(BaseAuthSessions):
55 SESSION_TYPE = 'ext:database'
56
57 def get_count(self):
58 return DbSession.query().count()
59
60 def get_expired_count(self, older_than_seconds=None):
61 expiry_date = self._seconds_to_date(older_than_seconds)
62 return DbSession.query().filter(DbSession.accessed < expiry_date).count()
63
64 def clean_sessions(self, older_than_seconds=None):
65 expiry_date = self._seconds_to_date(older_than_seconds)
66 DbSession.query().filter(DbSession.accessed < expiry_date).delete()
67 Session().commit()
68
69
70 class FileAuthSessions(BaseAuthSessions):
71 SESSION_TYPE = 'file sessions'
72
73 def get_count(self):
74 return 'NOT AVAILABLE'
75
76 def get_expired_count(self):
77 return self.get_count()
78
79 def clean_sessions(self, older_than_seconds=None):
80 data_dir = self.config.get('beaker.session.data_dir')
81 raise CleanupCommand(
82 'Please execute this command: '
83 '`find . -mtime +60 -exec rm {{}} \;` inside {} directory'.format(
84 data_dir))
85
86
87 class MemcachedAuthSessions(BaseAuthSessions):
88 SESSION_TYPE = 'ext:memcached'
89
90 def get_count(self):
91 return 'NOT AVAILABLE'
92
93 def get_expired_count(self):
94 return self.get_count()
95
96 def clean_sessions(self, older_than_seconds=None):
97 raise CleanupCommand('Cleanup for this session type not yet available')
98
99
100 class MemoryAuthSessions(BaseAuthSessions):
101 SESSION_TYPE = 'memory'
102
103 def get_count(self):
104 return 'NOT AVAILABLE'
105
106 def get_expired_count(self):
107 return self.get_count()
108
109 def clean_sessions(self, older_than_seconds=None):
110 raise CleanupCommand('Cleanup for this session type not yet available')
111
112
113 def get_session_handler(session_type):
114 types = {
115 'file': FileAuthSessions,
116 'ext:memcached': MemcachedAuthSessions,
117 'ext:database': DbAuthSessions,
118 'memory': MemoryAuthSessions
119 }
120
121 try:
122 return types[session_type]
123 except KeyError:
124 raise ValueError(
125 'This type {} is not supported'.format(session_type))
@@ -0,0 +1,60 b''
1 <div class="panel panel-default">
2 <div class="panel-heading">
3 <h3 class="panel-title">${_('User Sessions Configuration')}</h3>
4 </div>
5 <div class="panel-body">
6 <%
7 elems = [
8 (_('Session type'), c.session_model.SESSION_TYPE, ''),
9 (_('Session expiration period'), '{} seconds'.format(c.session_conf.get('beaker.session.timeout', 0)), ''),
10
11 (_('Total sessions'), c.session_count, ''),
12 (_('Expired sessions ({} days)').format(c.cleanup_older_days ), c.session_expired_count, ''),
13
14 ]
15 %>
16 <dl class="dl-horizontal settings">
17 %for dt, dd, tt in elems:
18 <dt>${dt}:</dt>
19 <dd title="${tt}">${dd}</dd>
20 %endfor
21 </dl>
22 </div>
23 </div>
24
25
26 <div class="panel panel-warning">
27 <div class="panel-heading">
28 <h3 class="panel-title">${_('Cleanup Old Sessions')}</h3>
29 </div>
30 <div class="panel-body">
31 ${h.secure_form(h.url('admin_settings_sessions_cleanup'), method='post')}
32
33 <div style="margin: 0 0 20px 0" class="fake-space">
34 ${_('Cleanup all sessions that were not active during choosen time frame')} <br/>
35 ${_('Picking All will log-out all users in the system, and each user will be required to log in again.')}
36 </div>
37 <select id="expire_days" name="expire_days">
38 % for n in [60, 90, 30, 7, 0]:
39 <option value="${n}">${'{} days'.format(n) if n != 0 else 'All'}</option>
40 % endfor
41 </select>
42 <button class="btn btn-small" type="submit"
43 onclick="return confirm('${_('Confirm to cleanup user sessions')}');">
44 ${_('Cleanup sessions')}
45 </button>
46 ${h.end_form()}
47 </div>
48 </div>
49
50
51 <script type="text/javascript">
52 $(document).ready(function() {
53 $('#expire_days').select2({
54 containerCssClass: 'drop-menu',
55 dropdownCssClass: 'drop-menu-dropdown',
56 dropdownAutoWidth: true,
57 minimumResultsForSearch: -1
58 });
59 });
60 </script> No newline at end of file
@@ -1,126 +1,128 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 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 import logging
22 import logging
23 import collections
23 import collections
24
24
25 from pylons import url
25 from pylons import url
26 from zope.interface import implementer
26 from zope.interface import implementer
27
27
28 from rhodecode.admin.interfaces import IAdminNavigationRegistry
28 from rhodecode.admin.interfaces import IAdminNavigationRegistry
29 from rhodecode.lib.utils import get_registry
29 from rhodecode.lib.utils import get_registry
30 from rhodecode.translation import _
30 from rhodecode.translation import _
31
31
32
32
33 log = logging.getLogger(__name__)
33 log = logging.getLogger(__name__)
34
34
35 NavListEntry = collections.namedtuple('NavListEntry', ['key', 'name', 'url'])
35 NavListEntry = collections.namedtuple('NavListEntry', ['key', 'name', 'url'])
36
36
37
37
38 class NavEntry(object):
38 class NavEntry(object):
39 """
39 """
40 Represents an entry in the admin navigation.
40 Represents an entry in the admin navigation.
41
41
42 :param key: Unique identifier used to store reference in an OrderedDict.
42 :param key: Unique identifier used to store reference in an OrderedDict.
43 :param name: Display name, usually a translation string.
43 :param name: Display name, usually a translation string.
44 :param view_name: Name of the view, used generate the URL.
44 :param view_name: Name of the view, used generate the URL.
45 :param pyramid: Indicator to use pyramid for URL generation. This should
45 :param pyramid: Indicator to use pyramid for URL generation. This should
46 be removed as soon as we are fully migrated to pyramid.
46 be removed as soon as we are fully migrated to pyramid.
47 """
47 """
48
48
49 def __init__(self, key, name, view_name, pyramid=False):
49 def __init__(self, key, name, view_name, pyramid=False):
50 self.key = key
50 self.key = key
51 self.name = name
51 self.name = name
52 self.view_name = view_name
52 self.view_name = view_name
53 self.pyramid = pyramid
53 self.pyramid = pyramid
54
54
55 def generate_url(self, request):
55 def generate_url(self, request):
56 if self.pyramid:
56 if self.pyramid:
57 if hasattr(request, 'route_path'):
57 if hasattr(request, 'route_path'):
58 return request.route_path(self.view_name)
58 return request.route_path(self.view_name)
59 else:
59 else:
60 # TODO: johbo: Remove this after migrating to pyramid.
60 # TODO: johbo: Remove this after migrating to pyramid.
61 # We need the pyramid request here to generate URLs to pyramid
61 # We need the pyramid request here to generate URLs to pyramid
62 # views from within pylons views.
62 # views from within pylons views.
63 from pyramid.threadlocal import get_current_request
63 from pyramid.threadlocal import get_current_request
64 pyramid_request = get_current_request()
64 pyramid_request = get_current_request()
65 return pyramid_request.route_path(self.view_name)
65 return pyramid_request.route_path(self.view_name)
66 else:
66 else:
67 return url(self.view_name)
67 return url(self.view_name)
68
68
69
69
70 @implementer(IAdminNavigationRegistry)
70 @implementer(IAdminNavigationRegistry)
71 class NavigationRegistry(object):
71 class NavigationRegistry(object):
72
72
73 _base_entries = [
73 _base_entries = [
74 NavEntry('global', _('Global'), 'admin_settings_global'),
74 NavEntry('global', _('Global'), 'admin_settings_global'),
75 NavEntry('vcs', _('VCS'), 'admin_settings_vcs'),
75 NavEntry('vcs', _('VCS'), 'admin_settings_vcs'),
76 NavEntry('visual', _('Visual'), 'admin_settings_visual'),
76 NavEntry('visual', _('Visual'), 'admin_settings_visual'),
77 NavEntry('mapping', _('Remap and Rescan'), 'admin_settings_mapping'),
77 NavEntry('mapping', _('Remap and Rescan'), 'admin_settings_mapping'),
78 NavEntry('issuetracker', _('Issue Tracker'),
78 NavEntry('issuetracker', _('Issue Tracker'),
79 'admin_settings_issuetracker'),
79 'admin_settings_issuetracker'),
80 NavEntry('email', _('Email'), 'admin_settings_email'),
80 NavEntry('email', _('Email'), 'admin_settings_email'),
81 NavEntry('hooks', _('Hooks'), 'admin_settings_hooks'),
81 NavEntry('hooks', _('Hooks'), 'admin_settings_hooks'),
82 NavEntry('search', _('Full Text Search'), 'admin_settings_search'),
82 NavEntry('search', _('Full Text Search'), 'admin_settings_search'),
83 NavEntry('integrations', _('Integrations'),
83 NavEntry('integrations', _('Integrations'),
84 'global_integrations_home', pyramid=True),
84 'global_integrations_home', pyramid=True),
85 NavEntry('system', _('System Info'), 'admin_settings_system'),
85 NavEntry('system', _('System Info'), 'admin_settings_system'),
86 NavEntry('session', _('User Sessions'), 'admin_settings_sessions'),
86 NavEntry('open_source', _('Open Source Licenses'),
87 NavEntry('open_source', _('Open Source Licenses'),
87 'admin_settings_open_source', pyramid=True),
88 'admin_settings_open_source', pyramid=True),
89
88 # TODO: marcink: we disable supervisor now until the supervisor stats
90 # TODO: marcink: we disable supervisor now until the supervisor stats
89 # page is fixed in the nix configuration
91 # page is fixed in the nix configuration
90 # NavEntry('supervisor', _('Supervisor'), 'admin_settings_supervisor'),
92 # NavEntry('supervisor', _('Supervisor'), 'admin_settings_supervisor'),
91 ]
93 ]
92
94
93 _labs_entry = NavEntry('labs', _('Labs'),
95 _labs_entry = NavEntry('labs', _('Labs'),
94 'admin_settings_labs')
96 'admin_settings_labs')
95
97
96 def __init__(self, labs_active=False):
98 def __init__(self, labs_active=False):
97 self._registered_entries = collections.OrderedDict([
99 self._registered_entries = collections.OrderedDict([
98 (item.key, item) for item in self.__class__._base_entries
100 (item.key, item) for item in self.__class__._base_entries
99 ])
101 ])
100
102
101 if labs_active:
103 if labs_active:
102 self.add_entry(self._labs_entry)
104 self.add_entry(self._labs_entry)
103
105
104 def add_entry(self, entry):
106 def add_entry(self, entry):
105 self._registered_entries[entry.key] = entry
107 self._registered_entries[entry.key] = entry
106
108
107 def get_navlist(self, request):
109 def get_navlist(self, request):
108 navlist = [NavListEntry(i.key, i.name, i.generate_url(request))
110 navlist = [NavListEntry(i.key, i.name, i.generate_url(request))
109 for i in self._registered_entries.values()]
111 for i in self._registered_entries.values()]
110 return navlist
112 return navlist
111
113
112
114
113 def navigation_registry(request):
115 def navigation_registry(request):
114 """
116 """
115 Helper that returns the admin navigation registry.
117 Helper that returns the admin navigation registry.
116 """
118 """
117 pyramid_registry = get_registry(request)
119 pyramid_registry = get_registry(request)
118 nav_registry = pyramid_registry.queryUtility(IAdminNavigationRegistry)
120 nav_registry = pyramid_registry.queryUtility(IAdminNavigationRegistry)
119 return nav_registry
121 return nav_registry
120
122
121
123
122 def navigation_list(request):
124 def navigation_list(request):
123 """
125 """
124 Helper that returns the admin navigation as list of NavListEntry objects.
126 Helper that returns the admin navigation as list of NavListEntry objects.
125 """
127 """
126 return navigation_registry(request).get_navlist(request)
128 return navigation_registry(request).get_navlist(request)
@@ -1,1173 +1,1179 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 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', jsroute=True)
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']}, jsroute=True)
308 action='edit', conditions={'method': ['GET']}, jsroute=True)
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='user_group_members', conditions={'method': ['GET']})
392 action='user_group_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_sessions', '/settings/sessions',
513 action='settings_sessions', conditions={'method': ['GET']})
514
515 m.connect('admin_settings_sessions_cleanup', '/settings/sessions/cleanup',
516 action='settings_sessions_cleanup', conditions={'method': ['POST']})
517
512 m.connect('admin_settings_supervisor', '/settings/supervisor',
518 m.connect('admin_settings_supervisor', '/settings/supervisor',
513 action='settings_supervisor', conditions={'method': ['GET']})
519 action='settings_supervisor', conditions={'method': ['GET']})
514 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
520 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
515 action='settings_supervisor_log', conditions={'method': ['GET']})
521 action='settings_supervisor_log', conditions={'method': ['GET']})
516
522
517 m.connect('admin_settings_labs', '/settings/labs',
523 m.connect('admin_settings_labs', '/settings/labs',
518 action='settings_labs_update',
524 action='settings_labs_update',
519 conditions={'method': ['POST']})
525 conditions={'method': ['POST']})
520 m.connect('admin_settings_labs', '/settings/labs',
526 m.connect('admin_settings_labs', '/settings/labs',
521 action='settings_labs', conditions={'method': ['GET']})
527 action='settings_labs', conditions={'method': ['GET']})
522
528
523 # ADMIN MY ACCOUNT
529 # ADMIN MY ACCOUNT
524 with rmap.submapper(path_prefix=ADMIN_PREFIX,
530 with rmap.submapper(path_prefix=ADMIN_PREFIX,
525 controller='admin/my_account') as m:
531 controller='admin/my_account') as m:
526
532
527 m.connect('my_account', '/my_account',
533 m.connect('my_account', '/my_account',
528 action='my_account', conditions={'method': ['GET']})
534 action='my_account', conditions={'method': ['GET']})
529 m.connect('my_account_edit', '/my_account/edit',
535 m.connect('my_account_edit', '/my_account/edit',
530 action='my_account_edit', conditions={'method': ['GET']})
536 action='my_account_edit', conditions={'method': ['GET']})
531 m.connect('my_account', '/my_account',
537 m.connect('my_account', '/my_account',
532 action='my_account_update', conditions={'method': ['POST']})
538 action='my_account_update', conditions={'method': ['POST']})
533
539
534 m.connect('my_account_password', '/my_account/password',
540 m.connect('my_account_password', '/my_account/password',
535 action='my_account_password', conditions={'method': ['GET', 'POST']})
541 action='my_account_password', conditions={'method': ['GET', 'POST']})
536
542
537 m.connect('my_account_repos', '/my_account/repos',
543 m.connect('my_account_repos', '/my_account/repos',
538 action='my_account_repos', conditions={'method': ['GET']})
544 action='my_account_repos', conditions={'method': ['GET']})
539
545
540 m.connect('my_account_watched', '/my_account/watched',
546 m.connect('my_account_watched', '/my_account/watched',
541 action='my_account_watched', conditions={'method': ['GET']})
547 action='my_account_watched', conditions={'method': ['GET']})
542
548
543 m.connect('my_account_pullrequests', '/my_account/pull_requests',
549 m.connect('my_account_pullrequests', '/my_account/pull_requests',
544 action='my_account_pullrequests', conditions={'method': ['GET']})
550 action='my_account_pullrequests', conditions={'method': ['GET']})
545
551
546 m.connect('my_account_perms', '/my_account/perms',
552 m.connect('my_account_perms', '/my_account/perms',
547 action='my_account_perms', conditions={'method': ['GET']})
553 action='my_account_perms', conditions={'method': ['GET']})
548
554
549 m.connect('my_account_emails', '/my_account/emails',
555 m.connect('my_account_emails', '/my_account/emails',
550 action='my_account_emails', conditions={'method': ['GET']})
556 action='my_account_emails', conditions={'method': ['GET']})
551 m.connect('my_account_emails', '/my_account/emails',
557 m.connect('my_account_emails', '/my_account/emails',
552 action='my_account_emails_add', conditions={'method': ['POST']})
558 action='my_account_emails_add', conditions={'method': ['POST']})
553 m.connect('my_account_emails', '/my_account/emails',
559 m.connect('my_account_emails', '/my_account/emails',
554 action='my_account_emails_delete', conditions={'method': ['DELETE']})
560 action='my_account_emails_delete', conditions={'method': ['DELETE']})
555
561
556 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
562 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
557 action='my_account_auth_tokens', conditions={'method': ['GET']})
563 action='my_account_auth_tokens', conditions={'method': ['GET']})
558 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
564 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
559 action='my_account_auth_tokens_add', conditions={'method': ['POST']})
565 action='my_account_auth_tokens_add', conditions={'method': ['POST']})
560 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
566 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
561 action='my_account_auth_tokens_delete', conditions={'method': ['DELETE']})
567 action='my_account_auth_tokens_delete', conditions={'method': ['DELETE']})
562 m.connect('my_account_notifications', '/my_account/notifications',
568 m.connect('my_account_notifications', '/my_account/notifications',
563 action='my_notifications',
569 action='my_notifications',
564 conditions={'method': ['GET']})
570 conditions={'method': ['GET']})
565 m.connect('my_account_notifications_toggle_visibility',
571 m.connect('my_account_notifications_toggle_visibility',
566 '/my_account/toggle_visibility',
572 '/my_account/toggle_visibility',
567 action='my_notifications_toggle_visibility',
573 action='my_notifications_toggle_visibility',
568 conditions={'method': ['POST']})
574 conditions={'method': ['POST']})
569 m.connect('my_account_notifications_test_channelstream',
575 m.connect('my_account_notifications_test_channelstream',
570 '/my_account/test_channelstream',
576 '/my_account/test_channelstream',
571 action='my_account_notifications_test_channelstream',
577 action='my_account_notifications_test_channelstream',
572 conditions={'method': ['POST']})
578 conditions={'method': ['POST']})
573
579
574 # NOTIFICATION REST ROUTES
580 # NOTIFICATION REST ROUTES
575 with rmap.submapper(path_prefix=ADMIN_PREFIX,
581 with rmap.submapper(path_prefix=ADMIN_PREFIX,
576 controller='admin/notifications') as m:
582 controller='admin/notifications') as m:
577 m.connect('notifications', '/notifications',
583 m.connect('notifications', '/notifications',
578 action='index', conditions={'method': ['GET']})
584 action='index', conditions={'method': ['GET']})
579 m.connect('notifications_mark_all_read', '/notifications/mark_all_read',
585 m.connect('notifications_mark_all_read', '/notifications/mark_all_read',
580 action='mark_all_read', conditions={'method': ['POST']})
586 action='mark_all_read', conditions={'method': ['POST']})
581 m.connect('/notifications/{notification_id}',
587 m.connect('/notifications/{notification_id}',
582 action='update', conditions={'method': ['PUT']})
588 action='update', conditions={'method': ['PUT']})
583 m.connect('/notifications/{notification_id}',
589 m.connect('/notifications/{notification_id}',
584 action='delete', conditions={'method': ['DELETE']})
590 action='delete', conditions={'method': ['DELETE']})
585 m.connect('notification', '/notifications/{notification_id}',
591 m.connect('notification', '/notifications/{notification_id}',
586 action='show', conditions={'method': ['GET']})
592 action='show', conditions={'method': ['GET']})
587
593
588 # ADMIN GIST
594 # ADMIN GIST
589 with rmap.submapper(path_prefix=ADMIN_PREFIX,
595 with rmap.submapper(path_prefix=ADMIN_PREFIX,
590 controller='admin/gists') as m:
596 controller='admin/gists') as m:
591 m.connect('gists', '/gists',
597 m.connect('gists', '/gists',
592 action='create', conditions={'method': ['POST']})
598 action='create', conditions={'method': ['POST']})
593 m.connect('gists', '/gists', jsroute=True,
599 m.connect('gists', '/gists', jsroute=True,
594 action='index', conditions={'method': ['GET']})
600 action='index', conditions={'method': ['GET']})
595 m.connect('new_gist', '/gists/new', jsroute=True,
601 m.connect('new_gist', '/gists/new', jsroute=True,
596 action='new', conditions={'method': ['GET']})
602 action='new', conditions={'method': ['GET']})
597
603
598 m.connect('/gists/{gist_id}',
604 m.connect('/gists/{gist_id}',
599 action='delete', conditions={'method': ['DELETE']})
605 action='delete', conditions={'method': ['DELETE']})
600 m.connect('edit_gist', '/gists/{gist_id}/edit',
606 m.connect('edit_gist', '/gists/{gist_id}/edit',
601 action='edit_form', conditions={'method': ['GET']})
607 action='edit_form', conditions={'method': ['GET']})
602 m.connect('edit_gist', '/gists/{gist_id}/edit',
608 m.connect('edit_gist', '/gists/{gist_id}/edit',
603 action='edit', conditions={'method': ['POST']})
609 action='edit', conditions={'method': ['POST']})
604 m.connect(
610 m.connect(
605 'edit_gist_check_revision', '/gists/{gist_id}/edit/check_revision',
611 'edit_gist_check_revision', '/gists/{gist_id}/edit/check_revision',
606 action='check_revision', conditions={'method': ['GET']})
612 action='check_revision', conditions={'method': ['GET']})
607
613
608 m.connect('gist', '/gists/{gist_id}',
614 m.connect('gist', '/gists/{gist_id}',
609 action='show', conditions={'method': ['GET']})
615 action='show', conditions={'method': ['GET']})
610 m.connect('gist_rev', '/gists/{gist_id}/{revision}',
616 m.connect('gist_rev', '/gists/{gist_id}/{revision}',
611 revision='tip',
617 revision='tip',
612 action='show', conditions={'method': ['GET']})
618 action='show', conditions={'method': ['GET']})
613 m.connect('formatted_gist', '/gists/{gist_id}/{revision}/{format}',
619 m.connect('formatted_gist', '/gists/{gist_id}/{revision}/{format}',
614 revision='tip',
620 revision='tip',
615 action='show', conditions={'method': ['GET']})
621 action='show', conditions={'method': ['GET']})
616 m.connect('formatted_gist_file', '/gists/{gist_id}/{revision}/{format}/{f_path}',
622 m.connect('formatted_gist_file', '/gists/{gist_id}/{revision}/{format}/{f_path}',
617 revision='tip',
623 revision='tip',
618 action='show', conditions={'method': ['GET']},
624 action='show', conditions={'method': ['GET']},
619 requirements=URL_NAME_REQUIREMENTS)
625 requirements=URL_NAME_REQUIREMENTS)
620
626
621 # ADMIN MAIN PAGES
627 # ADMIN MAIN PAGES
622 with rmap.submapper(path_prefix=ADMIN_PREFIX,
628 with rmap.submapper(path_prefix=ADMIN_PREFIX,
623 controller='admin/admin') as m:
629 controller='admin/admin') as m:
624 m.connect('admin_home', '', action='index')
630 m.connect('admin_home', '', action='index')
625 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
631 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
626 action='add_repo')
632 action='add_repo')
627 m.connect(
633 m.connect(
628 'pull_requests_global_0', '/pull_requests/{pull_request_id:[0-9]+}',
634 'pull_requests_global_0', '/pull_requests/{pull_request_id:[0-9]+}',
629 action='pull_requests')
635 action='pull_requests')
630 m.connect(
636 m.connect(
631 'pull_requests_global_1', '/pull-requests/{pull_request_id:[0-9]+}',
637 'pull_requests_global_1', '/pull-requests/{pull_request_id:[0-9]+}',
632 action='pull_requests')
638 action='pull_requests')
633 m.connect(
639 m.connect(
634 'pull_requests_global', '/pull-request/{pull_request_id:[0-9]+}',
640 'pull_requests_global', '/pull-request/{pull_request_id:[0-9]+}',
635 action='pull_requests')
641 action='pull_requests')
636
642
637 # USER JOURNAL
643 # USER JOURNAL
638 rmap.connect('journal', '%s/journal' % (ADMIN_PREFIX,),
644 rmap.connect('journal', '%s/journal' % (ADMIN_PREFIX,),
639 controller='journal', action='index')
645 controller='journal', action='index')
640 rmap.connect('journal_rss', '%s/journal/rss' % (ADMIN_PREFIX,),
646 rmap.connect('journal_rss', '%s/journal/rss' % (ADMIN_PREFIX,),
641 controller='journal', action='journal_rss')
647 controller='journal', action='journal_rss')
642 rmap.connect('journal_atom', '%s/journal/atom' % (ADMIN_PREFIX,),
648 rmap.connect('journal_atom', '%s/journal/atom' % (ADMIN_PREFIX,),
643 controller='journal', action='journal_atom')
649 controller='journal', action='journal_atom')
644
650
645 rmap.connect('public_journal', '%s/public_journal' % (ADMIN_PREFIX,),
651 rmap.connect('public_journal', '%s/public_journal' % (ADMIN_PREFIX,),
646 controller='journal', action='public_journal')
652 controller='journal', action='public_journal')
647
653
648 rmap.connect('public_journal_rss', '%s/public_journal/rss' % (ADMIN_PREFIX,),
654 rmap.connect('public_journal_rss', '%s/public_journal/rss' % (ADMIN_PREFIX,),
649 controller='journal', action='public_journal_rss')
655 controller='journal', action='public_journal_rss')
650
656
651 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % (ADMIN_PREFIX,),
657 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % (ADMIN_PREFIX,),
652 controller='journal', action='public_journal_rss')
658 controller='journal', action='public_journal_rss')
653
659
654 rmap.connect('public_journal_atom',
660 rmap.connect('public_journal_atom',
655 '%s/public_journal/atom' % (ADMIN_PREFIX,), controller='journal',
661 '%s/public_journal/atom' % (ADMIN_PREFIX,), controller='journal',
656 action='public_journal_atom')
662 action='public_journal_atom')
657
663
658 rmap.connect('public_journal_atom_old',
664 rmap.connect('public_journal_atom_old',
659 '%s/public_journal_atom' % (ADMIN_PREFIX,), controller='journal',
665 '%s/public_journal_atom' % (ADMIN_PREFIX,), controller='journal',
660 action='public_journal_atom')
666 action='public_journal_atom')
661
667
662 rmap.connect('toggle_following', '%s/toggle_following' % (ADMIN_PREFIX,),
668 rmap.connect('toggle_following', '%s/toggle_following' % (ADMIN_PREFIX,),
663 controller='journal', action='toggle_following', jsroute=True,
669 controller='journal', action='toggle_following', jsroute=True,
664 conditions={'method': ['POST']})
670 conditions={'method': ['POST']})
665
671
666 # FULL TEXT SEARCH
672 # FULL TEXT SEARCH
667 rmap.connect('search', '%s/search' % (ADMIN_PREFIX,),
673 rmap.connect('search', '%s/search' % (ADMIN_PREFIX,),
668 controller='search')
674 controller='search')
669 rmap.connect('search_repo_home', '/{repo_name}/search',
675 rmap.connect('search_repo_home', '/{repo_name}/search',
670 controller='search',
676 controller='search',
671 action='index',
677 action='index',
672 conditions={'function': check_repo},
678 conditions={'function': check_repo},
673 requirements=URL_NAME_REQUIREMENTS)
679 requirements=URL_NAME_REQUIREMENTS)
674
680
675 # FEEDS
681 # FEEDS
676 rmap.connect('rss_feed_home', '/{repo_name}/feed/rss',
682 rmap.connect('rss_feed_home', '/{repo_name}/feed/rss',
677 controller='feed', action='rss',
683 controller='feed', action='rss',
678 conditions={'function': check_repo},
684 conditions={'function': check_repo},
679 requirements=URL_NAME_REQUIREMENTS)
685 requirements=URL_NAME_REQUIREMENTS)
680
686
681 rmap.connect('atom_feed_home', '/{repo_name}/feed/atom',
687 rmap.connect('atom_feed_home', '/{repo_name}/feed/atom',
682 controller='feed', action='atom',
688 controller='feed', action='atom',
683 conditions={'function': check_repo},
689 conditions={'function': check_repo},
684 requirements=URL_NAME_REQUIREMENTS)
690 requirements=URL_NAME_REQUIREMENTS)
685
691
686 #==========================================================================
692 #==========================================================================
687 # REPOSITORY ROUTES
693 # REPOSITORY ROUTES
688 #==========================================================================
694 #==========================================================================
689
695
690 rmap.connect('repo_creating_home', '/{repo_name}/repo_creating',
696 rmap.connect('repo_creating_home', '/{repo_name}/repo_creating',
691 controller='admin/repos', action='repo_creating',
697 controller='admin/repos', action='repo_creating',
692 requirements=URL_NAME_REQUIREMENTS)
698 requirements=URL_NAME_REQUIREMENTS)
693 rmap.connect('repo_check_home', '/{repo_name}/crepo_check',
699 rmap.connect('repo_check_home', '/{repo_name}/crepo_check',
694 controller='admin/repos', action='repo_check',
700 controller='admin/repos', action='repo_check',
695 requirements=URL_NAME_REQUIREMENTS)
701 requirements=URL_NAME_REQUIREMENTS)
696
702
697 rmap.connect('repo_stats', '/{repo_name}/repo_stats/{commit_id}',
703 rmap.connect('repo_stats', '/{repo_name}/repo_stats/{commit_id}',
698 controller='summary', action='repo_stats',
704 controller='summary', action='repo_stats',
699 conditions={'function': check_repo},
705 conditions={'function': check_repo},
700 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
706 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
701
707
702 rmap.connect('repo_refs_data', '/{repo_name}/refs-data',
708 rmap.connect('repo_refs_data', '/{repo_name}/refs-data',
703 controller='summary', action='repo_refs_data', jsroute=True,
709 controller='summary', action='repo_refs_data', jsroute=True,
704 requirements=URL_NAME_REQUIREMENTS)
710 requirements=URL_NAME_REQUIREMENTS)
705 rmap.connect('repo_refs_changelog_data', '/{repo_name}/refs-data-changelog',
711 rmap.connect('repo_refs_changelog_data', '/{repo_name}/refs-data-changelog',
706 controller='summary', action='repo_refs_changelog_data',
712 controller='summary', action='repo_refs_changelog_data',
707 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
713 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
708 rmap.connect('repo_default_reviewers_data', '/{repo_name}/default-reviewers',
714 rmap.connect('repo_default_reviewers_data', '/{repo_name}/default-reviewers',
709 controller='summary', action='repo_default_reviewers_data',
715 controller='summary', action='repo_default_reviewers_data',
710 jsroute=True, requirements=URL_NAME_REQUIREMENTS)
716 jsroute=True, requirements=URL_NAME_REQUIREMENTS)
711
717
712 rmap.connect('changeset_home', '/{repo_name}/changeset/{revision}',
718 rmap.connect('changeset_home', '/{repo_name}/changeset/{revision}',
713 controller='changeset', revision='tip', jsroute=True,
719 controller='changeset', revision='tip', jsroute=True,
714 conditions={'function': check_repo},
720 conditions={'function': check_repo},
715 requirements=URL_NAME_REQUIREMENTS)
721 requirements=URL_NAME_REQUIREMENTS)
716 rmap.connect('changeset_children', '/{repo_name}/changeset_children/{revision}',
722 rmap.connect('changeset_children', '/{repo_name}/changeset_children/{revision}',
717 controller='changeset', revision='tip', action='changeset_children',
723 controller='changeset', revision='tip', action='changeset_children',
718 conditions={'function': check_repo},
724 conditions={'function': check_repo},
719 requirements=URL_NAME_REQUIREMENTS)
725 requirements=URL_NAME_REQUIREMENTS)
720 rmap.connect('changeset_parents', '/{repo_name}/changeset_parents/{revision}',
726 rmap.connect('changeset_parents', '/{repo_name}/changeset_parents/{revision}',
721 controller='changeset', revision='tip', action='changeset_parents',
727 controller='changeset', revision='tip', action='changeset_parents',
722 conditions={'function': check_repo},
728 conditions={'function': check_repo},
723 requirements=URL_NAME_REQUIREMENTS)
729 requirements=URL_NAME_REQUIREMENTS)
724
730
725 # repo edit options
731 # repo edit options
726 rmap.connect('edit_repo', '/{repo_name}/settings', jsroute=True,
732 rmap.connect('edit_repo', '/{repo_name}/settings', jsroute=True,
727 controller='admin/repos', action='edit',
733 controller='admin/repos', action='edit',
728 conditions={'method': ['GET'], 'function': check_repo},
734 conditions={'method': ['GET'], 'function': check_repo},
729 requirements=URL_NAME_REQUIREMENTS)
735 requirements=URL_NAME_REQUIREMENTS)
730
736
731 rmap.connect('edit_repo_perms', '/{repo_name}/settings/permissions',
737 rmap.connect('edit_repo_perms', '/{repo_name}/settings/permissions',
732 jsroute=True,
738 jsroute=True,
733 controller='admin/repos', action='edit_permissions',
739 controller='admin/repos', action='edit_permissions',
734 conditions={'method': ['GET'], 'function': check_repo},
740 conditions={'method': ['GET'], 'function': check_repo},
735 requirements=URL_NAME_REQUIREMENTS)
741 requirements=URL_NAME_REQUIREMENTS)
736 rmap.connect('edit_repo_perms_update', '/{repo_name}/settings/permissions',
742 rmap.connect('edit_repo_perms_update', '/{repo_name}/settings/permissions',
737 controller='admin/repos', action='edit_permissions_update',
743 controller='admin/repos', action='edit_permissions_update',
738 conditions={'method': ['PUT'], 'function': check_repo},
744 conditions={'method': ['PUT'], 'function': check_repo},
739 requirements=URL_NAME_REQUIREMENTS)
745 requirements=URL_NAME_REQUIREMENTS)
740
746
741 rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields',
747 rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields',
742 controller='admin/repos', action='edit_fields',
748 controller='admin/repos', action='edit_fields',
743 conditions={'method': ['GET'], 'function': check_repo},
749 conditions={'method': ['GET'], 'function': check_repo},
744 requirements=URL_NAME_REQUIREMENTS)
750 requirements=URL_NAME_REQUIREMENTS)
745 rmap.connect('create_repo_fields', '/{repo_name}/settings/fields/new',
751 rmap.connect('create_repo_fields', '/{repo_name}/settings/fields/new',
746 controller='admin/repos', action='create_repo_field',
752 controller='admin/repos', action='create_repo_field',
747 conditions={'method': ['PUT'], 'function': check_repo},
753 conditions={'method': ['PUT'], 'function': check_repo},
748 requirements=URL_NAME_REQUIREMENTS)
754 requirements=URL_NAME_REQUIREMENTS)
749 rmap.connect('delete_repo_fields', '/{repo_name}/settings/fields/{field_id}',
755 rmap.connect('delete_repo_fields', '/{repo_name}/settings/fields/{field_id}',
750 controller='admin/repos', action='delete_repo_field',
756 controller='admin/repos', action='delete_repo_field',
751 conditions={'method': ['DELETE'], 'function': check_repo},
757 conditions={'method': ['DELETE'], 'function': check_repo},
752 requirements=URL_NAME_REQUIREMENTS)
758 requirements=URL_NAME_REQUIREMENTS)
753
759
754 rmap.connect('edit_repo_advanced', '/{repo_name}/settings/advanced',
760 rmap.connect('edit_repo_advanced', '/{repo_name}/settings/advanced',
755 controller='admin/repos', action='edit_advanced',
761 controller='admin/repos', action='edit_advanced',
756 conditions={'method': ['GET'], 'function': check_repo},
762 conditions={'method': ['GET'], 'function': check_repo},
757 requirements=URL_NAME_REQUIREMENTS)
763 requirements=URL_NAME_REQUIREMENTS)
758
764
759 rmap.connect('edit_repo_advanced_locking', '/{repo_name}/settings/advanced/locking',
765 rmap.connect('edit_repo_advanced_locking', '/{repo_name}/settings/advanced/locking',
760 controller='admin/repos', action='edit_advanced_locking',
766 controller='admin/repos', action='edit_advanced_locking',
761 conditions={'method': ['PUT'], 'function': check_repo},
767 conditions={'method': ['PUT'], 'function': check_repo},
762 requirements=URL_NAME_REQUIREMENTS)
768 requirements=URL_NAME_REQUIREMENTS)
763 rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle',
769 rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle',
764 controller='admin/repos', action='toggle_locking',
770 controller='admin/repos', action='toggle_locking',
765 conditions={'method': ['GET'], 'function': check_repo},
771 conditions={'method': ['GET'], 'function': check_repo},
766 requirements=URL_NAME_REQUIREMENTS)
772 requirements=URL_NAME_REQUIREMENTS)
767
773
768 rmap.connect('edit_repo_advanced_journal', '/{repo_name}/settings/advanced/journal',
774 rmap.connect('edit_repo_advanced_journal', '/{repo_name}/settings/advanced/journal',
769 controller='admin/repos', action='edit_advanced_journal',
775 controller='admin/repos', action='edit_advanced_journal',
770 conditions={'method': ['PUT'], 'function': check_repo},
776 conditions={'method': ['PUT'], 'function': check_repo},
771 requirements=URL_NAME_REQUIREMENTS)
777 requirements=URL_NAME_REQUIREMENTS)
772
778
773 rmap.connect('edit_repo_advanced_fork', '/{repo_name}/settings/advanced/fork',
779 rmap.connect('edit_repo_advanced_fork', '/{repo_name}/settings/advanced/fork',
774 controller='admin/repos', action='edit_advanced_fork',
780 controller='admin/repos', action='edit_advanced_fork',
775 conditions={'method': ['PUT'], 'function': check_repo},
781 conditions={'method': ['PUT'], 'function': check_repo},
776 requirements=URL_NAME_REQUIREMENTS)
782 requirements=URL_NAME_REQUIREMENTS)
777
783
778 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
784 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
779 controller='admin/repos', action='edit_caches_form',
785 controller='admin/repos', action='edit_caches_form',
780 conditions={'method': ['GET'], 'function': check_repo},
786 conditions={'method': ['GET'], 'function': check_repo},
781 requirements=URL_NAME_REQUIREMENTS)
787 requirements=URL_NAME_REQUIREMENTS)
782 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
788 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
783 controller='admin/repos', action='edit_caches',
789 controller='admin/repos', action='edit_caches',
784 conditions={'method': ['PUT'], 'function': check_repo},
790 conditions={'method': ['PUT'], 'function': check_repo},
785 requirements=URL_NAME_REQUIREMENTS)
791 requirements=URL_NAME_REQUIREMENTS)
786
792
787 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
793 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
788 controller='admin/repos', action='edit_remote_form',
794 controller='admin/repos', action='edit_remote_form',
789 conditions={'method': ['GET'], 'function': check_repo},
795 conditions={'method': ['GET'], 'function': check_repo},
790 requirements=URL_NAME_REQUIREMENTS)
796 requirements=URL_NAME_REQUIREMENTS)
791 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
797 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
792 controller='admin/repos', action='edit_remote',
798 controller='admin/repos', action='edit_remote',
793 conditions={'method': ['PUT'], 'function': check_repo},
799 conditions={'method': ['PUT'], 'function': check_repo},
794 requirements=URL_NAME_REQUIREMENTS)
800 requirements=URL_NAME_REQUIREMENTS)
795
801
796 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
802 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
797 controller='admin/repos', action='edit_statistics_form',
803 controller='admin/repos', action='edit_statistics_form',
798 conditions={'method': ['GET'], 'function': check_repo},
804 conditions={'method': ['GET'], 'function': check_repo},
799 requirements=URL_NAME_REQUIREMENTS)
805 requirements=URL_NAME_REQUIREMENTS)
800 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
806 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
801 controller='admin/repos', action='edit_statistics',
807 controller='admin/repos', action='edit_statistics',
802 conditions={'method': ['PUT'], 'function': check_repo},
808 conditions={'method': ['PUT'], 'function': check_repo},
803 requirements=URL_NAME_REQUIREMENTS)
809 requirements=URL_NAME_REQUIREMENTS)
804 rmap.connect('repo_settings_issuetracker',
810 rmap.connect('repo_settings_issuetracker',
805 '/{repo_name}/settings/issue-tracker',
811 '/{repo_name}/settings/issue-tracker',
806 controller='admin/repos', action='repo_issuetracker',
812 controller='admin/repos', action='repo_issuetracker',
807 conditions={'method': ['GET'], 'function': check_repo},
813 conditions={'method': ['GET'], 'function': check_repo},
808 requirements=URL_NAME_REQUIREMENTS)
814 requirements=URL_NAME_REQUIREMENTS)
809 rmap.connect('repo_issuetracker_test',
815 rmap.connect('repo_issuetracker_test',
810 '/{repo_name}/settings/issue-tracker/test',
816 '/{repo_name}/settings/issue-tracker/test',
811 controller='admin/repos', action='repo_issuetracker_test',
817 controller='admin/repos', action='repo_issuetracker_test',
812 conditions={'method': ['POST'], 'function': check_repo},
818 conditions={'method': ['POST'], 'function': check_repo},
813 requirements=URL_NAME_REQUIREMENTS)
819 requirements=URL_NAME_REQUIREMENTS)
814 rmap.connect('repo_issuetracker_delete',
820 rmap.connect('repo_issuetracker_delete',
815 '/{repo_name}/settings/issue-tracker/delete',
821 '/{repo_name}/settings/issue-tracker/delete',
816 controller='admin/repos', action='repo_issuetracker_delete',
822 controller='admin/repos', action='repo_issuetracker_delete',
817 conditions={'method': ['DELETE'], 'function': check_repo},
823 conditions={'method': ['DELETE'], 'function': check_repo},
818 requirements=URL_NAME_REQUIREMENTS)
824 requirements=URL_NAME_REQUIREMENTS)
819 rmap.connect('repo_issuetracker_save',
825 rmap.connect('repo_issuetracker_save',
820 '/{repo_name}/settings/issue-tracker/save',
826 '/{repo_name}/settings/issue-tracker/save',
821 controller='admin/repos', action='repo_issuetracker_save',
827 controller='admin/repos', action='repo_issuetracker_save',
822 conditions={'method': ['POST'], 'function': check_repo},
828 conditions={'method': ['POST'], 'function': check_repo},
823 requirements=URL_NAME_REQUIREMENTS)
829 requirements=URL_NAME_REQUIREMENTS)
824 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
830 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
825 controller='admin/repos', action='repo_settings_vcs_update',
831 controller='admin/repos', action='repo_settings_vcs_update',
826 conditions={'method': ['POST'], 'function': check_repo},
832 conditions={'method': ['POST'], 'function': check_repo},
827 requirements=URL_NAME_REQUIREMENTS)
833 requirements=URL_NAME_REQUIREMENTS)
828 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
834 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
829 controller='admin/repos', action='repo_settings_vcs',
835 controller='admin/repos', action='repo_settings_vcs',
830 conditions={'method': ['GET'], 'function': check_repo},
836 conditions={'method': ['GET'], 'function': check_repo},
831 requirements=URL_NAME_REQUIREMENTS)
837 requirements=URL_NAME_REQUIREMENTS)
832 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
838 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
833 controller='admin/repos', action='repo_delete_svn_pattern',
839 controller='admin/repos', action='repo_delete_svn_pattern',
834 conditions={'method': ['DELETE'], 'function': check_repo},
840 conditions={'method': ['DELETE'], 'function': check_repo},
835 requirements=URL_NAME_REQUIREMENTS)
841 requirements=URL_NAME_REQUIREMENTS)
836 rmap.connect('repo_pullrequest_settings', '/{repo_name}/settings/pullrequest',
842 rmap.connect('repo_pullrequest_settings', '/{repo_name}/settings/pullrequest',
837 controller='admin/repos', action='repo_settings_pullrequest',
843 controller='admin/repos', action='repo_settings_pullrequest',
838 conditions={'method': ['GET', 'POST'], 'function': check_repo},
844 conditions={'method': ['GET', 'POST'], 'function': check_repo},
839 requirements=URL_NAME_REQUIREMENTS)
845 requirements=URL_NAME_REQUIREMENTS)
840
846
841 # still working url for backward compat.
847 # still working url for backward compat.
842 rmap.connect('raw_changeset_home_depraced',
848 rmap.connect('raw_changeset_home_depraced',
843 '/{repo_name}/raw-changeset/{revision}',
849 '/{repo_name}/raw-changeset/{revision}',
844 controller='changeset', action='changeset_raw',
850 controller='changeset', action='changeset_raw',
845 revision='tip', conditions={'function': check_repo},
851 revision='tip', conditions={'function': check_repo},
846 requirements=URL_NAME_REQUIREMENTS)
852 requirements=URL_NAME_REQUIREMENTS)
847
853
848 # new URLs
854 # new URLs
849 rmap.connect('changeset_raw_home',
855 rmap.connect('changeset_raw_home',
850 '/{repo_name}/changeset-diff/{revision}',
856 '/{repo_name}/changeset-diff/{revision}',
851 controller='changeset', action='changeset_raw',
857 controller='changeset', action='changeset_raw',
852 revision='tip', conditions={'function': check_repo},
858 revision='tip', conditions={'function': check_repo},
853 requirements=URL_NAME_REQUIREMENTS)
859 requirements=URL_NAME_REQUIREMENTS)
854
860
855 rmap.connect('changeset_patch_home',
861 rmap.connect('changeset_patch_home',
856 '/{repo_name}/changeset-patch/{revision}',
862 '/{repo_name}/changeset-patch/{revision}',
857 controller='changeset', action='changeset_patch',
863 controller='changeset', action='changeset_patch',
858 revision='tip', conditions={'function': check_repo},
864 revision='tip', conditions={'function': check_repo},
859 requirements=URL_NAME_REQUIREMENTS)
865 requirements=URL_NAME_REQUIREMENTS)
860
866
861 rmap.connect('changeset_download_home',
867 rmap.connect('changeset_download_home',
862 '/{repo_name}/changeset-download/{revision}',
868 '/{repo_name}/changeset-download/{revision}',
863 controller='changeset', action='changeset_download',
869 controller='changeset', action='changeset_download',
864 revision='tip', conditions={'function': check_repo},
870 revision='tip', conditions={'function': check_repo},
865 requirements=URL_NAME_REQUIREMENTS)
871 requirements=URL_NAME_REQUIREMENTS)
866
872
867 rmap.connect('changeset_comment',
873 rmap.connect('changeset_comment',
868 '/{repo_name}/changeset/{revision}/comment', jsroute=True,
874 '/{repo_name}/changeset/{revision}/comment', jsroute=True,
869 controller='changeset', revision='tip', action='comment',
875 controller='changeset', revision='tip', action='comment',
870 conditions={'function': check_repo},
876 conditions={'function': check_repo},
871 requirements=URL_NAME_REQUIREMENTS)
877 requirements=URL_NAME_REQUIREMENTS)
872
878
873 rmap.connect('changeset_comment_preview',
879 rmap.connect('changeset_comment_preview',
874 '/{repo_name}/changeset/comment/preview', jsroute=True,
880 '/{repo_name}/changeset/comment/preview', jsroute=True,
875 controller='changeset', action='preview_comment',
881 controller='changeset', action='preview_comment',
876 conditions={'function': check_repo, 'method': ['POST']},
882 conditions={'function': check_repo, 'method': ['POST']},
877 requirements=URL_NAME_REQUIREMENTS)
883 requirements=URL_NAME_REQUIREMENTS)
878
884
879 rmap.connect('changeset_comment_delete',
885 rmap.connect('changeset_comment_delete',
880 '/{repo_name}/changeset/comment/{comment_id}/delete',
886 '/{repo_name}/changeset/comment/{comment_id}/delete',
881 controller='changeset', action='delete_comment',
887 controller='changeset', action='delete_comment',
882 conditions={'function': check_repo, 'method': ['DELETE']},
888 conditions={'function': check_repo, 'method': ['DELETE']},
883 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
889 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
884
890
885 rmap.connect('changeset_info', '/{repo_name}/changeset_info/{revision}',
891 rmap.connect('changeset_info', '/{repo_name}/changeset_info/{revision}',
886 controller='changeset', action='changeset_info',
892 controller='changeset', action='changeset_info',
887 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
893 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
888
894
889 rmap.connect('compare_home',
895 rmap.connect('compare_home',
890 '/{repo_name}/compare',
896 '/{repo_name}/compare',
891 controller='compare', action='index',
897 controller='compare', action='index',
892 conditions={'function': check_repo},
898 conditions={'function': check_repo},
893 requirements=URL_NAME_REQUIREMENTS)
899 requirements=URL_NAME_REQUIREMENTS)
894
900
895 rmap.connect('compare_url',
901 rmap.connect('compare_url',
896 '/{repo_name}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}',
902 '/{repo_name}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}',
897 controller='compare', action='compare',
903 controller='compare', action='compare',
898 conditions={'function': check_repo},
904 conditions={'function': check_repo},
899 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
905 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
900
906
901 rmap.connect('pullrequest_home',
907 rmap.connect('pullrequest_home',
902 '/{repo_name}/pull-request/new', controller='pullrequests',
908 '/{repo_name}/pull-request/new', controller='pullrequests',
903 action='index', conditions={'function': check_repo,
909 action='index', conditions={'function': check_repo,
904 'method': ['GET']},
910 'method': ['GET']},
905 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
911 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
906
912
907 rmap.connect('pullrequest',
913 rmap.connect('pullrequest',
908 '/{repo_name}/pull-request/new', controller='pullrequests',
914 '/{repo_name}/pull-request/new', controller='pullrequests',
909 action='create', conditions={'function': check_repo,
915 action='create', conditions={'function': check_repo,
910 'method': ['POST']},
916 'method': ['POST']},
911 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
917 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
912
918
913 rmap.connect('pullrequest_repo_refs',
919 rmap.connect('pullrequest_repo_refs',
914 '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
920 '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
915 controller='pullrequests',
921 controller='pullrequests',
916 action='get_repo_refs',
922 action='get_repo_refs',
917 conditions={'function': check_repo, 'method': ['GET']},
923 conditions={'function': check_repo, 'method': ['GET']},
918 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
924 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
919
925
920 rmap.connect('pullrequest_repo_destinations',
926 rmap.connect('pullrequest_repo_destinations',
921 '/{repo_name}/pull-request/repo-destinations',
927 '/{repo_name}/pull-request/repo-destinations',
922 controller='pullrequests',
928 controller='pullrequests',
923 action='get_repo_destinations',
929 action='get_repo_destinations',
924 conditions={'function': check_repo, 'method': ['GET']},
930 conditions={'function': check_repo, 'method': ['GET']},
925 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
931 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
926
932
927 rmap.connect('pullrequest_show',
933 rmap.connect('pullrequest_show',
928 '/{repo_name}/pull-request/{pull_request_id}',
934 '/{repo_name}/pull-request/{pull_request_id}',
929 controller='pullrequests',
935 controller='pullrequests',
930 action='show', conditions={'function': check_repo,
936 action='show', conditions={'function': check_repo,
931 'method': ['GET']},
937 'method': ['GET']},
932 requirements=URL_NAME_REQUIREMENTS)
938 requirements=URL_NAME_REQUIREMENTS)
933
939
934 rmap.connect('pullrequest_update',
940 rmap.connect('pullrequest_update',
935 '/{repo_name}/pull-request/{pull_request_id}',
941 '/{repo_name}/pull-request/{pull_request_id}',
936 controller='pullrequests',
942 controller='pullrequests',
937 action='update', conditions={'function': check_repo,
943 action='update', conditions={'function': check_repo,
938 'method': ['PUT']},
944 'method': ['PUT']},
939 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
945 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
940
946
941 rmap.connect('pullrequest_merge',
947 rmap.connect('pullrequest_merge',
942 '/{repo_name}/pull-request/{pull_request_id}',
948 '/{repo_name}/pull-request/{pull_request_id}',
943 controller='pullrequests',
949 controller='pullrequests',
944 action='merge', conditions={'function': check_repo,
950 action='merge', conditions={'function': check_repo,
945 'method': ['POST']},
951 'method': ['POST']},
946 requirements=URL_NAME_REQUIREMENTS)
952 requirements=URL_NAME_REQUIREMENTS)
947
953
948 rmap.connect('pullrequest_delete',
954 rmap.connect('pullrequest_delete',
949 '/{repo_name}/pull-request/{pull_request_id}',
955 '/{repo_name}/pull-request/{pull_request_id}',
950 controller='pullrequests',
956 controller='pullrequests',
951 action='delete', conditions={'function': check_repo,
957 action='delete', conditions={'function': check_repo,
952 'method': ['DELETE']},
958 'method': ['DELETE']},
953 requirements=URL_NAME_REQUIREMENTS)
959 requirements=URL_NAME_REQUIREMENTS)
954
960
955 rmap.connect('pullrequest_show_all',
961 rmap.connect('pullrequest_show_all',
956 '/{repo_name}/pull-request',
962 '/{repo_name}/pull-request',
957 controller='pullrequests',
963 controller='pullrequests',
958 action='show_all', conditions={'function': check_repo,
964 action='show_all', conditions={'function': check_repo,
959 'method': ['GET']},
965 'method': ['GET']},
960 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
966 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
961
967
962 rmap.connect('pullrequest_comment',
968 rmap.connect('pullrequest_comment',
963 '/{repo_name}/pull-request-comment/{pull_request_id}',
969 '/{repo_name}/pull-request-comment/{pull_request_id}',
964 controller='pullrequests',
970 controller='pullrequests',
965 action='comment', conditions={'function': check_repo,
971 action='comment', conditions={'function': check_repo,
966 'method': ['POST']},
972 'method': ['POST']},
967 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
973 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
968
974
969 rmap.connect('pullrequest_comment_delete',
975 rmap.connect('pullrequest_comment_delete',
970 '/{repo_name}/pull-request-comment/{comment_id}/delete',
976 '/{repo_name}/pull-request-comment/{comment_id}/delete',
971 controller='pullrequests', action='delete_comment',
977 controller='pullrequests', action='delete_comment',
972 conditions={'function': check_repo, 'method': ['DELETE']},
978 conditions={'function': check_repo, 'method': ['DELETE']},
973 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
979 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
974
980
975 rmap.connect('summary_home_explicit', '/{repo_name}/summary',
981 rmap.connect('summary_home_explicit', '/{repo_name}/summary',
976 controller='summary', conditions={'function': check_repo},
982 controller='summary', conditions={'function': check_repo},
977 requirements=URL_NAME_REQUIREMENTS)
983 requirements=URL_NAME_REQUIREMENTS)
978
984
979 rmap.connect('branches_home', '/{repo_name}/branches',
985 rmap.connect('branches_home', '/{repo_name}/branches',
980 controller='branches', conditions={'function': check_repo},
986 controller='branches', conditions={'function': check_repo},
981 requirements=URL_NAME_REQUIREMENTS)
987 requirements=URL_NAME_REQUIREMENTS)
982
988
983 rmap.connect('tags_home', '/{repo_name}/tags',
989 rmap.connect('tags_home', '/{repo_name}/tags',
984 controller='tags', conditions={'function': check_repo},
990 controller='tags', conditions={'function': check_repo},
985 requirements=URL_NAME_REQUIREMENTS)
991 requirements=URL_NAME_REQUIREMENTS)
986
992
987 rmap.connect('bookmarks_home', '/{repo_name}/bookmarks',
993 rmap.connect('bookmarks_home', '/{repo_name}/bookmarks',
988 controller='bookmarks', conditions={'function': check_repo},
994 controller='bookmarks', conditions={'function': check_repo},
989 requirements=URL_NAME_REQUIREMENTS)
995 requirements=URL_NAME_REQUIREMENTS)
990
996
991 rmap.connect('changelog_home', '/{repo_name}/changelog', jsroute=True,
997 rmap.connect('changelog_home', '/{repo_name}/changelog', jsroute=True,
992 controller='changelog', conditions={'function': check_repo},
998 controller='changelog', conditions={'function': check_repo},
993 requirements=URL_NAME_REQUIREMENTS)
999 requirements=URL_NAME_REQUIREMENTS)
994
1000
995 rmap.connect('changelog_summary_home', '/{repo_name}/changelog_summary',
1001 rmap.connect('changelog_summary_home', '/{repo_name}/changelog_summary',
996 controller='changelog', action='changelog_summary',
1002 controller='changelog', action='changelog_summary',
997 conditions={'function': check_repo},
1003 conditions={'function': check_repo},
998 requirements=URL_NAME_REQUIREMENTS)
1004 requirements=URL_NAME_REQUIREMENTS)
999
1005
1000 rmap.connect('changelog_file_home',
1006 rmap.connect('changelog_file_home',
1001 '/{repo_name}/changelog/{revision}/{f_path}',
1007 '/{repo_name}/changelog/{revision}/{f_path}',
1002 controller='changelog', f_path=None,
1008 controller='changelog', f_path=None,
1003 conditions={'function': check_repo},
1009 conditions={'function': check_repo},
1004 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1010 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1005
1011
1006 rmap.connect('changelog_details', '/{repo_name}/changelog_details/{cs}',
1012 rmap.connect('changelog_details', '/{repo_name}/changelog_details/{cs}',
1007 controller='changelog', action='changelog_details',
1013 controller='changelog', action='changelog_details',
1008 conditions={'function': check_repo},
1014 conditions={'function': check_repo},
1009 requirements=URL_NAME_REQUIREMENTS)
1015 requirements=URL_NAME_REQUIREMENTS)
1010
1016
1011 rmap.connect('files_home', '/{repo_name}/files/{revision}/{f_path}',
1017 rmap.connect('files_home', '/{repo_name}/files/{revision}/{f_path}',
1012 controller='files', revision='tip', f_path='',
1018 controller='files', revision='tip', f_path='',
1013 conditions={'function': check_repo},
1019 conditions={'function': check_repo},
1014 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1020 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1015
1021
1016 rmap.connect('files_home_simple_catchrev',
1022 rmap.connect('files_home_simple_catchrev',
1017 '/{repo_name}/files/{revision}',
1023 '/{repo_name}/files/{revision}',
1018 controller='files', revision='tip', f_path='',
1024 controller='files', revision='tip', f_path='',
1019 conditions={'function': check_repo},
1025 conditions={'function': check_repo},
1020 requirements=URL_NAME_REQUIREMENTS)
1026 requirements=URL_NAME_REQUIREMENTS)
1021
1027
1022 rmap.connect('files_home_simple_catchall',
1028 rmap.connect('files_home_simple_catchall',
1023 '/{repo_name}/files',
1029 '/{repo_name}/files',
1024 controller='files', revision='tip', f_path='',
1030 controller='files', revision='tip', f_path='',
1025 conditions={'function': check_repo},
1031 conditions={'function': check_repo},
1026 requirements=URL_NAME_REQUIREMENTS)
1032 requirements=URL_NAME_REQUIREMENTS)
1027
1033
1028 rmap.connect('files_history_home',
1034 rmap.connect('files_history_home',
1029 '/{repo_name}/history/{revision}/{f_path}',
1035 '/{repo_name}/history/{revision}/{f_path}',
1030 controller='files', action='history', revision='tip', f_path='',
1036 controller='files', action='history', revision='tip', f_path='',
1031 conditions={'function': check_repo},
1037 conditions={'function': check_repo},
1032 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1038 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1033
1039
1034 rmap.connect('files_authors_home',
1040 rmap.connect('files_authors_home',
1035 '/{repo_name}/authors/{revision}/{f_path}',
1041 '/{repo_name}/authors/{revision}/{f_path}',
1036 controller='files', action='authors', revision='tip', f_path='',
1042 controller='files', action='authors', revision='tip', f_path='',
1037 conditions={'function': check_repo},
1043 conditions={'function': check_repo},
1038 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1044 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1039
1045
1040 rmap.connect('files_diff_home', '/{repo_name}/diff/{f_path}',
1046 rmap.connect('files_diff_home', '/{repo_name}/diff/{f_path}',
1041 controller='files', action='diff', f_path='',
1047 controller='files', action='diff', f_path='',
1042 conditions={'function': check_repo},
1048 conditions={'function': check_repo},
1043 requirements=URL_NAME_REQUIREMENTS)
1049 requirements=URL_NAME_REQUIREMENTS)
1044
1050
1045 rmap.connect('files_diff_2way_home',
1051 rmap.connect('files_diff_2way_home',
1046 '/{repo_name}/diff-2way/{f_path}',
1052 '/{repo_name}/diff-2way/{f_path}',
1047 controller='files', action='diff_2way', f_path='',
1053 controller='files', action='diff_2way', f_path='',
1048 conditions={'function': check_repo},
1054 conditions={'function': check_repo},
1049 requirements=URL_NAME_REQUIREMENTS)
1055 requirements=URL_NAME_REQUIREMENTS)
1050
1056
1051 rmap.connect('files_rawfile_home',
1057 rmap.connect('files_rawfile_home',
1052 '/{repo_name}/rawfile/{revision}/{f_path}',
1058 '/{repo_name}/rawfile/{revision}/{f_path}',
1053 controller='files', action='rawfile', revision='tip',
1059 controller='files', action='rawfile', revision='tip',
1054 f_path='', conditions={'function': check_repo},
1060 f_path='', conditions={'function': check_repo},
1055 requirements=URL_NAME_REQUIREMENTS)
1061 requirements=URL_NAME_REQUIREMENTS)
1056
1062
1057 rmap.connect('files_raw_home',
1063 rmap.connect('files_raw_home',
1058 '/{repo_name}/raw/{revision}/{f_path}',
1064 '/{repo_name}/raw/{revision}/{f_path}',
1059 controller='files', action='raw', revision='tip', f_path='',
1065 controller='files', action='raw', revision='tip', f_path='',
1060 conditions={'function': check_repo},
1066 conditions={'function': check_repo},
1061 requirements=URL_NAME_REQUIREMENTS)
1067 requirements=URL_NAME_REQUIREMENTS)
1062
1068
1063 rmap.connect('files_render_home',
1069 rmap.connect('files_render_home',
1064 '/{repo_name}/render/{revision}/{f_path}',
1070 '/{repo_name}/render/{revision}/{f_path}',
1065 controller='files', action='index', revision='tip', f_path='',
1071 controller='files', action='index', revision='tip', f_path='',
1066 rendered=True, conditions={'function': check_repo},
1072 rendered=True, conditions={'function': check_repo},
1067 requirements=URL_NAME_REQUIREMENTS)
1073 requirements=URL_NAME_REQUIREMENTS)
1068
1074
1069 rmap.connect('files_annotate_home',
1075 rmap.connect('files_annotate_home',
1070 '/{repo_name}/annotate/{revision}/{f_path}',
1076 '/{repo_name}/annotate/{revision}/{f_path}',
1071 controller='files', action='index', revision='tip',
1077 controller='files', action='index', revision='tip',
1072 f_path='', annotate=True, conditions={'function': check_repo},
1078 f_path='', annotate=True, conditions={'function': check_repo},
1073 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1079 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1074
1080
1075 rmap.connect('files_edit',
1081 rmap.connect('files_edit',
1076 '/{repo_name}/edit/{revision}/{f_path}',
1082 '/{repo_name}/edit/{revision}/{f_path}',
1077 controller='files', action='edit', revision='tip',
1083 controller='files', action='edit', revision='tip',
1078 f_path='',
1084 f_path='',
1079 conditions={'function': check_repo, 'method': ['POST']},
1085 conditions={'function': check_repo, 'method': ['POST']},
1080 requirements=URL_NAME_REQUIREMENTS)
1086 requirements=URL_NAME_REQUIREMENTS)
1081
1087
1082 rmap.connect('files_edit_home',
1088 rmap.connect('files_edit_home',
1083 '/{repo_name}/edit/{revision}/{f_path}',
1089 '/{repo_name}/edit/{revision}/{f_path}',
1084 controller='files', action='edit_home', revision='tip',
1090 controller='files', action='edit_home', revision='tip',
1085 f_path='', conditions={'function': check_repo},
1091 f_path='', conditions={'function': check_repo},
1086 requirements=URL_NAME_REQUIREMENTS)
1092 requirements=URL_NAME_REQUIREMENTS)
1087
1093
1088 rmap.connect('files_add',
1094 rmap.connect('files_add',
1089 '/{repo_name}/add/{revision}/{f_path}',
1095 '/{repo_name}/add/{revision}/{f_path}',
1090 controller='files', action='add', revision='tip',
1096 controller='files', action='add', revision='tip',
1091 f_path='',
1097 f_path='',
1092 conditions={'function': check_repo, 'method': ['POST']},
1098 conditions={'function': check_repo, 'method': ['POST']},
1093 requirements=URL_NAME_REQUIREMENTS)
1099 requirements=URL_NAME_REQUIREMENTS)
1094
1100
1095 rmap.connect('files_add_home',
1101 rmap.connect('files_add_home',
1096 '/{repo_name}/add/{revision}/{f_path}',
1102 '/{repo_name}/add/{revision}/{f_path}',
1097 controller='files', action='add_home', revision='tip',
1103 controller='files', action='add_home', revision='tip',
1098 f_path='', conditions={'function': check_repo},
1104 f_path='', conditions={'function': check_repo},
1099 requirements=URL_NAME_REQUIREMENTS)
1105 requirements=URL_NAME_REQUIREMENTS)
1100
1106
1101 rmap.connect('files_delete',
1107 rmap.connect('files_delete',
1102 '/{repo_name}/delete/{revision}/{f_path}',
1108 '/{repo_name}/delete/{revision}/{f_path}',
1103 controller='files', action='delete', revision='tip',
1109 controller='files', action='delete', revision='tip',
1104 f_path='',
1110 f_path='',
1105 conditions={'function': check_repo, 'method': ['POST']},
1111 conditions={'function': check_repo, 'method': ['POST']},
1106 requirements=URL_NAME_REQUIREMENTS)
1112 requirements=URL_NAME_REQUIREMENTS)
1107
1113
1108 rmap.connect('files_delete_home',
1114 rmap.connect('files_delete_home',
1109 '/{repo_name}/delete/{revision}/{f_path}',
1115 '/{repo_name}/delete/{revision}/{f_path}',
1110 controller='files', action='delete_home', revision='tip',
1116 controller='files', action='delete_home', revision='tip',
1111 f_path='', conditions={'function': check_repo},
1117 f_path='', conditions={'function': check_repo},
1112 requirements=URL_NAME_REQUIREMENTS)
1118 requirements=URL_NAME_REQUIREMENTS)
1113
1119
1114 rmap.connect('files_archive_home', '/{repo_name}/archive/{fname}',
1120 rmap.connect('files_archive_home', '/{repo_name}/archive/{fname}',
1115 controller='files', action='archivefile',
1121 controller='files', action='archivefile',
1116 conditions={'function': check_repo},
1122 conditions={'function': check_repo},
1117 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1123 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1118
1124
1119 rmap.connect('files_nodelist_home',
1125 rmap.connect('files_nodelist_home',
1120 '/{repo_name}/nodelist/{revision}/{f_path}',
1126 '/{repo_name}/nodelist/{revision}/{f_path}',
1121 controller='files', action='nodelist',
1127 controller='files', action='nodelist',
1122 conditions={'function': check_repo},
1128 conditions={'function': check_repo},
1123 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1129 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1124
1130
1125 rmap.connect('files_nodetree_full',
1131 rmap.connect('files_nodetree_full',
1126 '/{repo_name}/nodetree_full/{commit_id}/{f_path}',
1132 '/{repo_name}/nodetree_full/{commit_id}/{f_path}',
1127 controller='files', action='nodetree_full',
1133 controller='files', action='nodetree_full',
1128 conditions={'function': check_repo},
1134 conditions={'function': check_repo},
1129 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1135 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1130
1136
1131 rmap.connect('repo_fork_create_home', '/{repo_name}/fork',
1137 rmap.connect('repo_fork_create_home', '/{repo_name}/fork',
1132 controller='forks', action='fork_create',
1138 controller='forks', action='fork_create',
1133 conditions={'function': check_repo, 'method': ['POST']},
1139 conditions={'function': check_repo, 'method': ['POST']},
1134 requirements=URL_NAME_REQUIREMENTS)
1140 requirements=URL_NAME_REQUIREMENTS)
1135
1141
1136 rmap.connect('repo_fork_home', '/{repo_name}/fork',
1142 rmap.connect('repo_fork_home', '/{repo_name}/fork',
1137 controller='forks', action='fork',
1143 controller='forks', action='fork',
1138 conditions={'function': check_repo},
1144 conditions={'function': check_repo},
1139 requirements=URL_NAME_REQUIREMENTS)
1145 requirements=URL_NAME_REQUIREMENTS)
1140
1146
1141 rmap.connect('repo_forks_home', '/{repo_name}/forks',
1147 rmap.connect('repo_forks_home', '/{repo_name}/forks',
1142 controller='forks', action='forks',
1148 controller='forks', action='forks',
1143 conditions={'function': check_repo},
1149 conditions={'function': check_repo},
1144 requirements=URL_NAME_REQUIREMENTS)
1150 requirements=URL_NAME_REQUIREMENTS)
1145
1151
1146 rmap.connect('repo_followers_home', '/{repo_name}/followers',
1152 rmap.connect('repo_followers_home', '/{repo_name}/followers',
1147 controller='followers', action='followers',
1153 controller='followers', action='followers',
1148 conditions={'function': check_repo},
1154 conditions={'function': check_repo},
1149 requirements=URL_NAME_REQUIREMENTS)
1155 requirements=URL_NAME_REQUIREMENTS)
1150
1156
1151 # must be here for proper group/repo catching pattern
1157 # must be here for proper group/repo catching pattern
1152 _connect_with_slash(
1158 _connect_with_slash(
1153 rmap, 'repo_group_home', '/{group_name}',
1159 rmap, 'repo_group_home', '/{group_name}',
1154 controller='home', action='index_repo_group',
1160 controller='home', action='index_repo_group',
1155 conditions={'function': check_group},
1161 conditions={'function': check_group},
1156 requirements=URL_NAME_REQUIREMENTS)
1162 requirements=URL_NAME_REQUIREMENTS)
1157
1163
1158 # catch all, at the end
1164 # catch all, at the end
1159 _connect_with_slash(
1165 _connect_with_slash(
1160 rmap, 'summary_home', '/{repo_name}', jsroute=True,
1166 rmap, 'summary_home', '/{repo_name}', jsroute=True,
1161 controller='summary', action='index',
1167 controller='summary', action='index',
1162 conditions={'function': check_repo},
1168 conditions={'function': check_repo},
1163 requirements=URL_NAME_REQUIREMENTS)
1169 requirements=URL_NAME_REQUIREMENTS)
1164
1170
1165 return rmap
1171 return rmap
1166
1172
1167
1173
1168 def _connect_with_slash(mapper, name, path, *args, **kwargs):
1174 def _connect_with_slash(mapper, name, path, *args, **kwargs):
1169 """
1175 """
1170 Connect a route with an optional trailing slash in `path`.
1176 Connect a route with an optional trailing slash in `path`.
1171 """
1177 """
1172 mapper.connect(name + '_slash', path + '/', *args, **kwargs)
1178 mapper.connect(name + '_slash', path + '/', *args, **kwargs)
1173 mapper.connect(name, path, *args, **kwargs)
1179 mapper.connect(name, path, *args, **kwargs)
@@ -1,842 +1,890 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 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 settings controller for rhodecode admin
23 settings controller for rhodecode admin
24 """
24 """
25
25
26 import collections
26 import collections
27 import logging
27 import logging
28 import urllib2
28 import urllib2
29
29
30 import datetime
30 import datetime
31 import formencode
31 import formencode
32 from formencode import htmlfill
32 from formencode import htmlfill
33 import packaging.version
33 import packaging.version
34 from pylons import request, tmpl_context as c, url, config
34 from pylons import request, tmpl_context as c, url, config
35 from pylons.controllers.util import redirect
35 from pylons.controllers.util import redirect
36 from pylons.i18n.translation import _, lazy_ugettext
36 from pylons.i18n.translation import _, lazy_ugettext
37 from pyramid.threadlocal import get_current_registry
37 from pyramid.threadlocal import get_current_registry
38 from webob.exc import HTTPBadRequest
38 from webob.exc import HTTPBadRequest
39
39
40 import rhodecode
40 import rhodecode
41 from rhodecode.admin.navigation import navigation_list
41 from rhodecode.admin.navigation import navigation_list
42 from rhodecode.lib import auth
42 from rhodecode.lib import auth
43 from rhodecode.lib import helpers as h
43 from rhodecode.lib import helpers as h
44 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
44 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
45 from rhodecode.lib.base import BaseController, render
45 from rhodecode.lib.base import BaseController, render
46 from rhodecode.lib.celerylib import tasks, run_task
46 from rhodecode.lib.celerylib import tasks, run_task
47 from rhodecode.lib.utils import repo2db_mapper
47 from rhodecode.lib.utils import repo2db_mapper
48 from rhodecode.lib.utils2 import (
48 from rhodecode.lib.utils2 import (
49 str2bool, safe_unicode, AttributeDict, safe_int)
49 str2bool, safe_unicode, AttributeDict, safe_int)
50 from rhodecode.lib.compat import OrderedDict
50 from rhodecode.lib.compat import OrderedDict
51 from rhodecode.lib.ext_json import json
51 from rhodecode.lib.ext_json import json
52 from rhodecode.lib.utils import jsonify
52 from rhodecode.lib.utils import jsonify
53 from rhodecode.lib import system_info
54 from rhodecode.lib import user_sessions
53
55
54 from rhodecode.model.db import RhodeCodeUi, Repository
56 from rhodecode.model.db import RhodeCodeUi, Repository
55 from rhodecode.model.forms import ApplicationSettingsForm, \
57 from rhodecode.model.forms import ApplicationSettingsForm, \
56 ApplicationUiSettingsForm, ApplicationVisualisationForm, \
58 ApplicationUiSettingsForm, ApplicationVisualisationForm, \
57 LabsSettingsForm, IssueTrackerPatternsForm
59 LabsSettingsForm, IssueTrackerPatternsForm
58 from rhodecode.model.repo_group import RepoGroupModel
60 from rhodecode.model.repo_group import RepoGroupModel
59
61
60 from rhodecode.model.scm import ScmModel
62 from rhodecode.model.scm import ScmModel
61 from rhodecode.model.notification import EmailNotificationModel
63 from rhodecode.model.notification import EmailNotificationModel
62 from rhodecode.model.meta import Session
64 from rhodecode.model.meta import Session
63 from rhodecode.model.settings import (
65 from rhodecode.model.settings import (
64 IssueTrackerSettingsModel, VcsSettingsModel, SettingNotFound,
66 IssueTrackerSettingsModel, VcsSettingsModel, SettingNotFound,
65 SettingsModel)
67 SettingsModel)
66
68
67 from rhodecode.model.supervisor import SupervisorModel, SUPERVISOR_MASTER
69 from rhodecode.model.supervisor import SupervisorModel, SUPERVISOR_MASTER
68 from rhodecode.svn_support.config_keys import generate_config
70 from rhodecode.svn_support.config_keys import generate_config
69
71
70
72
71 log = logging.getLogger(__name__)
73 log = logging.getLogger(__name__)
72
74
73
75
74 class SettingsController(BaseController):
76 class SettingsController(BaseController):
75 """REST Controller styled on the Atom Publishing Protocol"""
77 """REST Controller styled on the Atom Publishing Protocol"""
76 # To properly map this controller, ensure your config/routing.py
78 # To properly map this controller, ensure your config/routing.py
77 # file has a resource setup:
79 # file has a resource setup:
78 # map.resource('setting', 'settings', controller='admin/settings',
80 # map.resource('setting', 'settings', controller='admin/settings',
79 # path_prefix='/admin', name_prefix='admin_')
81 # path_prefix='/admin', name_prefix='admin_')
80
82
81 @LoginRequired()
83 @LoginRequired()
82 def __before__(self):
84 def __before__(self):
83 super(SettingsController, self).__before__()
85 super(SettingsController, self).__before__()
84 c.labs_active = str2bool(
86 c.labs_active = str2bool(
85 rhodecode.CONFIG.get('labs_settings_active', 'true'))
87 rhodecode.CONFIG.get('labs_settings_active', 'true'))
86 c.navlist = navigation_list(request)
88 c.navlist = navigation_list(request)
87
89
88 def _get_hg_ui_settings(self):
90 def _get_hg_ui_settings(self):
89 ret = RhodeCodeUi.query().all()
91 ret = RhodeCodeUi.query().all()
90
92
91 if not ret:
93 if not ret:
92 raise Exception('Could not get application ui settings !')
94 raise Exception('Could not get application ui settings !')
93 settings = {}
95 settings = {}
94 for each in ret:
96 for each in ret:
95 k = each.ui_key
97 k = each.ui_key
96 v = each.ui_value
98 v = each.ui_value
97 if k == '/':
99 if k == '/':
98 k = 'root_path'
100 k = 'root_path'
99
101
100 if k in ['push_ssl', 'publish']:
102 if k in ['push_ssl', 'publish']:
101 v = str2bool(v)
103 v = str2bool(v)
102
104
103 if k.find('.') != -1:
105 if k.find('.') != -1:
104 k = k.replace('.', '_')
106 k = k.replace('.', '_')
105
107
106 if each.ui_section in ['hooks', 'extensions']:
108 if each.ui_section in ['hooks', 'extensions']:
107 v = each.ui_active
109 v = each.ui_active
108
110
109 settings[each.ui_section + '_' + k] = v
111 settings[each.ui_section + '_' + k] = v
110 return settings
112 return settings
111
113
112 @HasPermissionAllDecorator('hg.admin')
114 @HasPermissionAllDecorator('hg.admin')
113 @auth.CSRFRequired()
115 @auth.CSRFRequired()
114 @jsonify
116 @jsonify
115 def delete_svn_pattern(self):
117 def delete_svn_pattern(self):
116 if not request.is_xhr:
118 if not request.is_xhr:
117 raise HTTPBadRequest()
119 raise HTTPBadRequest()
118
120
119 delete_pattern_id = request.POST.get('delete_svn_pattern')
121 delete_pattern_id = request.POST.get('delete_svn_pattern')
120 model = VcsSettingsModel()
122 model = VcsSettingsModel()
121 try:
123 try:
122 model.delete_global_svn_pattern(delete_pattern_id)
124 model.delete_global_svn_pattern(delete_pattern_id)
123 except SettingNotFound:
125 except SettingNotFound:
124 raise HTTPBadRequest()
126 raise HTTPBadRequest()
125
127
126 Session().commit()
128 Session().commit()
127 return True
129 return True
128
130
129 @HasPermissionAllDecorator('hg.admin')
131 @HasPermissionAllDecorator('hg.admin')
130 @auth.CSRFRequired()
132 @auth.CSRFRequired()
131 def settings_vcs_update(self):
133 def settings_vcs_update(self):
132 """POST /admin/settings: All items in the collection"""
134 """POST /admin/settings: All items in the collection"""
133 # url('admin_settings_vcs')
135 # url('admin_settings_vcs')
134 c.active = 'vcs'
136 c.active = 'vcs'
135
137
136 model = VcsSettingsModel()
138 model = VcsSettingsModel()
137 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
139 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
138 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
140 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
139
141
140 # TODO: Replace with request.registry after migrating to pyramid.
142 # TODO: Replace with request.registry after migrating to pyramid.
141 pyramid_settings = get_current_registry().settings
143 pyramid_settings = get_current_registry().settings
142 c.svn_proxy_generate_config = pyramid_settings[generate_config]
144 c.svn_proxy_generate_config = pyramid_settings[generate_config]
143
145
144 application_form = ApplicationUiSettingsForm()()
146 application_form = ApplicationUiSettingsForm()()
145
147
146 try:
148 try:
147 form_result = application_form.to_python(dict(request.POST))
149 form_result = application_form.to_python(dict(request.POST))
148 except formencode.Invalid as errors:
150 except formencode.Invalid as errors:
149 h.flash(
151 h.flash(
150 _("Some form inputs contain invalid data."),
152 _("Some form inputs contain invalid data."),
151 category='error')
153 category='error')
152 return htmlfill.render(
154 return htmlfill.render(
153 render('admin/settings/settings.mako'),
155 render('admin/settings/settings.mako'),
154 defaults=errors.value,
156 defaults=errors.value,
155 errors=errors.error_dict or {},
157 errors=errors.error_dict or {},
156 prefix_error=False,
158 prefix_error=False,
157 encoding="UTF-8",
159 encoding="UTF-8",
158 force_defaults=False
160 force_defaults=False
159 )
161 )
160
162
161 try:
163 try:
162 if c.visual.allow_repo_location_change:
164 if c.visual.allow_repo_location_change:
163 model.update_global_path_setting(
165 model.update_global_path_setting(
164 form_result['paths_root_path'])
166 form_result['paths_root_path'])
165
167
166 model.update_global_ssl_setting(form_result['web_push_ssl'])
168 model.update_global_ssl_setting(form_result['web_push_ssl'])
167 model.update_global_hook_settings(form_result)
169 model.update_global_hook_settings(form_result)
168
170
169 model.create_or_update_global_svn_settings(form_result)
171 model.create_or_update_global_svn_settings(form_result)
170 model.create_or_update_global_hg_settings(form_result)
172 model.create_or_update_global_hg_settings(form_result)
171 model.create_or_update_global_pr_settings(form_result)
173 model.create_or_update_global_pr_settings(form_result)
172 except Exception:
174 except Exception:
173 log.exception("Exception while updating settings")
175 log.exception("Exception while updating settings")
174 h.flash(_('Error occurred during updating '
176 h.flash(_('Error occurred during updating '
175 'application settings'), category='error')
177 'application settings'), category='error')
176 else:
178 else:
177 Session().commit()
179 Session().commit()
178 h.flash(_('Updated VCS settings'), category='success')
180 h.flash(_('Updated VCS settings'), category='success')
179 return redirect(url('admin_settings_vcs'))
181 return redirect(url('admin_settings_vcs'))
180
182
181 return htmlfill.render(
183 return htmlfill.render(
182 render('admin/settings/settings.mako'),
184 render('admin/settings/settings.mako'),
183 defaults=self._form_defaults(),
185 defaults=self._form_defaults(),
184 encoding="UTF-8",
186 encoding="UTF-8",
185 force_defaults=False)
187 force_defaults=False)
186
188
187 @HasPermissionAllDecorator('hg.admin')
189 @HasPermissionAllDecorator('hg.admin')
188 def settings_vcs(self):
190 def settings_vcs(self):
189 """GET /admin/settings: All items in the collection"""
191 """GET /admin/settings: All items in the collection"""
190 # url('admin_settings_vcs')
192 # url('admin_settings_vcs')
191 c.active = 'vcs'
193 c.active = 'vcs'
192 model = VcsSettingsModel()
194 model = VcsSettingsModel()
193 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
195 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
194 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
196 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
195
197
196 # TODO: Replace with request.registry after migrating to pyramid.
198 # TODO: Replace with request.registry after migrating to pyramid.
197 pyramid_settings = get_current_registry().settings
199 pyramid_settings = get_current_registry().settings
198 c.svn_proxy_generate_config = pyramid_settings[generate_config]
200 c.svn_proxy_generate_config = pyramid_settings[generate_config]
199
201
200 return htmlfill.render(
202 return htmlfill.render(
201 render('admin/settings/settings.mako'),
203 render('admin/settings/settings.mako'),
202 defaults=self._form_defaults(),
204 defaults=self._form_defaults(),
203 encoding="UTF-8",
205 encoding="UTF-8",
204 force_defaults=False)
206 force_defaults=False)
205
207
206 @HasPermissionAllDecorator('hg.admin')
208 @HasPermissionAllDecorator('hg.admin')
207 @auth.CSRFRequired()
209 @auth.CSRFRequired()
208 def settings_mapping_update(self):
210 def settings_mapping_update(self):
209 """POST /admin/settings/mapping: All items in the collection"""
211 """POST /admin/settings/mapping: All items in the collection"""
210 # url('admin_settings_mapping')
212 # url('admin_settings_mapping')
211 c.active = 'mapping'
213 c.active = 'mapping'
212 rm_obsolete = request.POST.get('destroy', False)
214 rm_obsolete = request.POST.get('destroy', False)
213 invalidate_cache = request.POST.get('invalidate', False)
215 invalidate_cache = request.POST.get('invalidate', False)
214 log.debug(
216 log.debug(
215 'rescanning repo location with destroy obsolete=%s', rm_obsolete)
217 'rescanning repo location with destroy obsolete=%s', rm_obsolete)
216
218
217 if invalidate_cache:
219 if invalidate_cache:
218 log.debug('invalidating all repositories cache')
220 log.debug('invalidating all repositories cache')
219 for repo in Repository.get_all():
221 for repo in Repository.get_all():
220 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
222 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
221
223
222 filesystem_repos = ScmModel().repo_scan()
224 filesystem_repos = ScmModel().repo_scan()
223 added, removed = repo2db_mapper(filesystem_repos, rm_obsolete)
225 added, removed = repo2db_mapper(filesystem_repos, rm_obsolete)
224 _repr = lambda l: ', '.join(map(safe_unicode, l)) or '-'
226 _repr = lambda l: ', '.join(map(safe_unicode, l)) or '-'
225 h.flash(_('Repositories successfully '
227 h.flash(_('Repositories successfully '
226 'rescanned added: %s ; removed: %s') %
228 'rescanned added: %s ; removed: %s') %
227 (_repr(added), _repr(removed)),
229 (_repr(added), _repr(removed)),
228 category='success')
230 category='success')
229 return redirect(url('admin_settings_mapping'))
231 return redirect(url('admin_settings_mapping'))
230
232
231 @HasPermissionAllDecorator('hg.admin')
233 @HasPermissionAllDecorator('hg.admin')
232 def settings_mapping(self):
234 def settings_mapping(self):
233 """GET /admin/settings/mapping: All items in the collection"""
235 """GET /admin/settings/mapping: All items in the collection"""
234 # url('admin_settings_mapping')
236 # url('admin_settings_mapping')
235 c.active = 'mapping'
237 c.active = 'mapping'
236
238
237 return htmlfill.render(
239 return htmlfill.render(
238 render('admin/settings/settings.mako'),
240 render('admin/settings/settings.mako'),
239 defaults=self._form_defaults(),
241 defaults=self._form_defaults(),
240 encoding="UTF-8",
242 encoding="UTF-8",
241 force_defaults=False)
243 force_defaults=False)
242
244
243 @HasPermissionAllDecorator('hg.admin')
245 @HasPermissionAllDecorator('hg.admin')
244 @auth.CSRFRequired()
246 @auth.CSRFRequired()
245 def settings_global_update(self):
247 def settings_global_update(self):
246 """POST /admin/settings/global: All items in the collection"""
248 """POST /admin/settings/global: All items in the collection"""
247 # url('admin_settings_global')
249 # url('admin_settings_global')
248 c.active = 'global'
250 c.active = 'global'
249 c.personal_repo_group_default_pattern = RepoGroupModel()\
251 c.personal_repo_group_default_pattern = RepoGroupModel()\
250 .get_personal_group_name_pattern()
252 .get_personal_group_name_pattern()
251 application_form = ApplicationSettingsForm()()
253 application_form = ApplicationSettingsForm()()
252 try:
254 try:
253 form_result = application_form.to_python(dict(request.POST))
255 form_result = application_form.to_python(dict(request.POST))
254 except formencode.Invalid as errors:
256 except formencode.Invalid as errors:
255 return htmlfill.render(
257 return htmlfill.render(
256 render('admin/settings/settings.mako'),
258 render('admin/settings/settings.mako'),
257 defaults=errors.value,
259 defaults=errors.value,
258 errors=errors.error_dict or {},
260 errors=errors.error_dict or {},
259 prefix_error=False,
261 prefix_error=False,
260 encoding="UTF-8",
262 encoding="UTF-8",
261 force_defaults=False)
263 force_defaults=False)
262
264
263 try:
265 try:
264 settings = [
266 settings = [
265 ('title', 'rhodecode_title', 'unicode'),
267 ('title', 'rhodecode_title', 'unicode'),
266 ('realm', 'rhodecode_realm', 'unicode'),
268 ('realm', 'rhodecode_realm', 'unicode'),
267 ('pre_code', 'rhodecode_pre_code', 'unicode'),
269 ('pre_code', 'rhodecode_pre_code', 'unicode'),
268 ('post_code', 'rhodecode_post_code', 'unicode'),
270 ('post_code', 'rhodecode_post_code', 'unicode'),
269 ('captcha_public_key', 'rhodecode_captcha_public_key', 'unicode'),
271 ('captcha_public_key', 'rhodecode_captcha_public_key', 'unicode'),
270 ('captcha_private_key', 'rhodecode_captcha_private_key', 'unicode'),
272 ('captcha_private_key', 'rhodecode_captcha_private_key', 'unicode'),
271 ('create_personal_repo_group', 'rhodecode_create_personal_repo_group', 'bool'),
273 ('create_personal_repo_group', 'rhodecode_create_personal_repo_group', 'bool'),
272 ('personal_repo_group_pattern', 'rhodecode_personal_repo_group_pattern', 'unicode'),
274 ('personal_repo_group_pattern', 'rhodecode_personal_repo_group_pattern', 'unicode'),
273 ]
275 ]
274 for setting, form_key, type_ in settings:
276 for setting, form_key, type_ in settings:
275 sett = SettingsModel().create_or_update_setting(
277 sett = SettingsModel().create_or_update_setting(
276 setting, form_result[form_key], type_)
278 setting, form_result[form_key], type_)
277 Session().add(sett)
279 Session().add(sett)
278
280
279 Session().commit()
281 Session().commit()
280 SettingsModel().invalidate_settings_cache()
282 SettingsModel().invalidate_settings_cache()
281 h.flash(_('Updated application settings'), category='success')
283 h.flash(_('Updated application settings'), category='success')
282 except Exception:
284 except Exception:
283 log.exception("Exception while updating application settings")
285 log.exception("Exception while updating application settings")
284 h.flash(
286 h.flash(
285 _('Error occurred during updating application settings'),
287 _('Error occurred during updating application settings'),
286 category='error')
288 category='error')
287
289
288 return redirect(url('admin_settings_global'))
290 return redirect(url('admin_settings_global'))
289
291
290 @HasPermissionAllDecorator('hg.admin')
292 @HasPermissionAllDecorator('hg.admin')
291 def settings_global(self):
293 def settings_global(self):
292 """GET /admin/settings/global: All items in the collection"""
294 """GET /admin/settings/global: All items in the collection"""
293 # url('admin_settings_global')
295 # url('admin_settings_global')
294 c.active = 'global'
296 c.active = 'global'
295 c.personal_repo_group_default_pattern = RepoGroupModel()\
297 c.personal_repo_group_default_pattern = RepoGroupModel()\
296 .get_personal_group_name_pattern()
298 .get_personal_group_name_pattern()
297
299
298 return htmlfill.render(
300 return htmlfill.render(
299 render('admin/settings/settings.mako'),
301 render('admin/settings/settings.mako'),
300 defaults=self._form_defaults(),
302 defaults=self._form_defaults(),
301 encoding="UTF-8",
303 encoding="UTF-8",
302 force_defaults=False)
304 force_defaults=False)
303
305
304 @HasPermissionAllDecorator('hg.admin')
306 @HasPermissionAllDecorator('hg.admin')
305 @auth.CSRFRequired()
307 @auth.CSRFRequired()
306 def settings_visual_update(self):
308 def settings_visual_update(self):
307 """POST /admin/settings/visual: All items in the collection"""
309 """POST /admin/settings/visual: All items in the collection"""
308 # url('admin_settings_visual')
310 # url('admin_settings_visual')
309 c.active = 'visual'
311 c.active = 'visual'
310 application_form = ApplicationVisualisationForm()()
312 application_form = ApplicationVisualisationForm()()
311 try:
313 try:
312 form_result = application_form.to_python(dict(request.POST))
314 form_result = application_form.to_python(dict(request.POST))
313 except formencode.Invalid as errors:
315 except formencode.Invalid as errors:
314 return htmlfill.render(
316 return htmlfill.render(
315 render('admin/settings/settings.mako'),
317 render('admin/settings/settings.mako'),
316 defaults=errors.value,
318 defaults=errors.value,
317 errors=errors.error_dict or {},
319 errors=errors.error_dict or {},
318 prefix_error=False,
320 prefix_error=False,
319 encoding="UTF-8",
321 encoding="UTF-8",
320 force_defaults=False
322 force_defaults=False
321 )
323 )
322
324
323 try:
325 try:
324 settings = [
326 settings = [
325 ('show_public_icon', 'rhodecode_show_public_icon', 'bool'),
327 ('show_public_icon', 'rhodecode_show_public_icon', 'bool'),
326 ('show_private_icon', 'rhodecode_show_private_icon', 'bool'),
328 ('show_private_icon', 'rhodecode_show_private_icon', 'bool'),
327 ('stylify_metatags', 'rhodecode_stylify_metatags', 'bool'),
329 ('stylify_metatags', 'rhodecode_stylify_metatags', 'bool'),
328 ('repository_fields', 'rhodecode_repository_fields', 'bool'),
330 ('repository_fields', 'rhodecode_repository_fields', 'bool'),
329 ('dashboard_items', 'rhodecode_dashboard_items', 'int'),
331 ('dashboard_items', 'rhodecode_dashboard_items', 'int'),
330 ('admin_grid_items', 'rhodecode_admin_grid_items', 'int'),
332 ('admin_grid_items', 'rhodecode_admin_grid_items', 'int'),
331 ('show_version', 'rhodecode_show_version', 'bool'),
333 ('show_version', 'rhodecode_show_version', 'bool'),
332 ('use_gravatar', 'rhodecode_use_gravatar', 'bool'),
334 ('use_gravatar', 'rhodecode_use_gravatar', 'bool'),
333 ('markup_renderer', 'rhodecode_markup_renderer', 'unicode'),
335 ('markup_renderer', 'rhodecode_markup_renderer', 'unicode'),
334 ('gravatar_url', 'rhodecode_gravatar_url', 'unicode'),
336 ('gravatar_url', 'rhodecode_gravatar_url', 'unicode'),
335 ('clone_uri_tmpl', 'rhodecode_clone_uri_tmpl', 'unicode'),
337 ('clone_uri_tmpl', 'rhodecode_clone_uri_tmpl', 'unicode'),
336 ('support_url', 'rhodecode_support_url', 'unicode'),
338 ('support_url', 'rhodecode_support_url', 'unicode'),
337 ('show_revision_number', 'rhodecode_show_revision_number', 'bool'),
339 ('show_revision_number', 'rhodecode_show_revision_number', 'bool'),
338 ('show_sha_length', 'rhodecode_show_sha_length', 'int'),
340 ('show_sha_length', 'rhodecode_show_sha_length', 'int'),
339 ]
341 ]
340 for setting, form_key, type_ in settings:
342 for setting, form_key, type_ in settings:
341 sett = SettingsModel().create_or_update_setting(
343 sett = SettingsModel().create_or_update_setting(
342 setting, form_result[form_key], type_)
344 setting, form_result[form_key], type_)
343 Session().add(sett)
345 Session().add(sett)
344
346
345 Session().commit()
347 Session().commit()
346 SettingsModel().invalidate_settings_cache()
348 SettingsModel().invalidate_settings_cache()
347 h.flash(_('Updated visualisation settings'), category='success')
349 h.flash(_('Updated visualisation settings'), category='success')
348 except Exception:
350 except Exception:
349 log.exception("Exception updating visualization settings")
351 log.exception("Exception updating visualization settings")
350 h.flash(_('Error occurred during updating '
352 h.flash(_('Error occurred during updating '
351 'visualisation settings'),
353 'visualisation settings'),
352 category='error')
354 category='error')
353
355
354 return redirect(url('admin_settings_visual'))
356 return redirect(url('admin_settings_visual'))
355
357
356 @HasPermissionAllDecorator('hg.admin')
358 @HasPermissionAllDecorator('hg.admin')
357 def settings_visual(self):
359 def settings_visual(self):
358 """GET /admin/settings/visual: All items in the collection"""
360 """GET /admin/settings/visual: All items in the collection"""
359 # url('admin_settings_visual')
361 # url('admin_settings_visual')
360 c.active = 'visual'
362 c.active = 'visual'
361
363
362 return htmlfill.render(
364 return htmlfill.render(
363 render('admin/settings/settings.mako'),
365 render('admin/settings/settings.mako'),
364 defaults=self._form_defaults(),
366 defaults=self._form_defaults(),
365 encoding="UTF-8",
367 encoding="UTF-8",
366 force_defaults=False)
368 force_defaults=False)
367
369
368 @HasPermissionAllDecorator('hg.admin')
370 @HasPermissionAllDecorator('hg.admin')
369 @auth.CSRFRequired()
371 @auth.CSRFRequired()
370 def settings_issuetracker_test(self):
372 def settings_issuetracker_test(self):
371 if request.is_xhr:
373 if request.is_xhr:
372 return h.urlify_commit_message(
374 return h.urlify_commit_message(
373 request.POST.get('test_text', ''),
375 request.POST.get('test_text', ''),
374 'repo_group/test_repo1')
376 'repo_group/test_repo1')
375 else:
377 else:
376 raise HTTPBadRequest()
378 raise HTTPBadRequest()
377
379
378 @HasPermissionAllDecorator('hg.admin')
380 @HasPermissionAllDecorator('hg.admin')
379 @auth.CSRFRequired()
381 @auth.CSRFRequired()
380 def settings_issuetracker_delete(self):
382 def settings_issuetracker_delete(self):
381 uid = request.POST.get('uid')
383 uid = request.POST.get('uid')
382 IssueTrackerSettingsModel().delete_entries(uid)
384 IssueTrackerSettingsModel().delete_entries(uid)
383 h.flash(_('Removed issue tracker entry'), category='success')
385 h.flash(_('Removed issue tracker entry'), category='success')
384 return redirect(url('admin_settings_issuetracker'))
386 return redirect(url('admin_settings_issuetracker'))
385
387
386 @HasPermissionAllDecorator('hg.admin')
388 @HasPermissionAllDecorator('hg.admin')
387 def settings_issuetracker(self):
389 def settings_issuetracker(self):
388 """GET /admin/settings/issue-tracker: All items in the collection"""
390 """GET /admin/settings/issue-tracker: All items in the collection"""
389 # url('admin_settings_issuetracker')
391 # url('admin_settings_issuetracker')
390 c.active = 'issuetracker'
392 c.active = 'issuetracker'
391 defaults = SettingsModel().get_all_settings()
393 defaults = SettingsModel().get_all_settings()
392
394
393 entry_key = 'rhodecode_issuetracker_pat_'
395 entry_key = 'rhodecode_issuetracker_pat_'
394
396
395 c.issuetracker_entries = {}
397 c.issuetracker_entries = {}
396 for k, v in defaults.items():
398 for k, v in defaults.items():
397 if k.startswith(entry_key):
399 if k.startswith(entry_key):
398 uid = k[len(entry_key):]
400 uid = k[len(entry_key):]
399 c.issuetracker_entries[uid] = None
401 c.issuetracker_entries[uid] = None
400
402
401 for uid in c.issuetracker_entries:
403 for uid in c.issuetracker_entries:
402 c.issuetracker_entries[uid] = AttributeDict({
404 c.issuetracker_entries[uid] = AttributeDict({
403 'pat': defaults.get('rhodecode_issuetracker_pat_' + uid),
405 'pat': defaults.get('rhodecode_issuetracker_pat_' + uid),
404 'url': defaults.get('rhodecode_issuetracker_url_' + uid),
406 'url': defaults.get('rhodecode_issuetracker_url_' + uid),
405 'pref': defaults.get('rhodecode_issuetracker_pref_' + uid),
407 'pref': defaults.get('rhodecode_issuetracker_pref_' + uid),
406 'desc': defaults.get('rhodecode_issuetracker_desc_' + uid),
408 'desc': defaults.get('rhodecode_issuetracker_desc_' + uid),
407 })
409 })
408
410
409 return render('admin/settings/settings.mako')
411 return render('admin/settings/settings.mako')
410
412
411 @HasPermissionAllDecorator('hg.admin')
413 @HasPermissionAllDecorator('hg.admin')
412 @auth.CSRFRequired()
414 @auth.CSRFRequired()
413 def settings_issuetracker_save(self):
415 def settings_issuetracker_save(self):
414 settings_model = IssueTrackerSettingsModel()
416 settings_model = IssueTrackerSettingsModel()
415
417
416 form = IssueTrackerPatternsForm()().to_python(request.POST)
418 form = IssueTrackerPatternsForm()().to_python(request.POST)
417 if form:
419 if form:
418 for uid in form.get('delete_patterns', []):
420 for uid in form.get('delete_patterns', []):
419 settings_model.delete_entries(uid)
421 settings_model.delete_entries(uid)
420
422
421 for pattern in form.get('patterns', []):
423 for pattern in form.get('patterns', []):
422 for setting, value, type_ in pattern:
424 for setting, value, type_ in pattern:
423 sett = settings_model.create_or_update_setting(
425 sett = settings_model.create_or_update_setting(
424 setting, value, type_)
426 setting, value, type_)
425 Session().add(sett)
427 Session().add(sett)
426
428
427 Session().commit()
429 Session().commit()
428
430
429 SettingsModel().invalidate_settings_cache()
431 SettingsModel().invalidate_settings_cache()
430 h.flash(_('Updated issue tracker entries'), category='success')
432 h.flash(_('Updated issue tracker entries'), category='success')
431 return redirect(url('admin_settings_issuetracker'))
433 return redirect(url('admin_settings_issuetracker'))
432
434
433 @HasPermissionAllDecorator('hg.admin')
435 @HasPermissionAllDecorator('hg.admin')
434 @auth.CSRFRequired()
436 @auth.CSRFRequired()
435 def settings_email_update(self):
437 def settings_email_update(self):
436 """POST /admin/settings/email: All items in the collection"""
438 """POST /admin/settings/email: All items in the collection"""
437 # url('admin_settings_email')
439 # url('admin_settings_email')
438 c.active = 'email'
440 c.active = 'email'
439
441
440 test_email = request.POST.get('test_email')
442 test_email = request.POST.get('test_email')
441
443
442 if not test_email:
444 if not test_email:
443 h.flash(_('Please enter email address'), category='error')
445 h.flash(_('Please enter email address'), category='error')
444 return redirect(url('admin_settings_email'))
446 return redirect(url('admin_settings_email'))
445
447
446 email_kwargs = {
448 email_kwargs = {
447 'date': datetime.datetime.now(),
449 'date': datetime.datetime.now(),
448 'user': c.rhodecode_user,
450 'user': c.rhodecode_user,
449 'rhodecode_version': c.rhodecode_version
451 'rhodecode_version': c.rhodecode_version
450 }
452 }
451
453
452 (subject, headers, email_body,
454 (subject, headers, email_body,
453 email_body_plaintext) = EmailNotificationModel().render_email(
455 email_body_plaintext) = EmailNotificationModel().render_email(
454 EmailNotificationModel.TYPE_EMAIL_TEST, **email_kwargs)
456 EmailNotificationModel.TYPE_EMAIL_TEST, **email_kwargs)
455
457
456 recipients = [test_email] if test_email else None
458 recipients = [test_email] if test_email else None
457
459
458 run_task(tasks.send_email, recipients, subject,
460 run_task(tasks.send_email, recipients, subject,
459 email_body_plaintext, email_body)
461 email_body_plaintext, email_body)
460
462
461 h.flash(_('Send email task created'), category='success')
463 h.flash(_('Send email task created'), category='success')
462 return redirect(url('admin_settings_email'))
464 return redirect(url('admin_settings_email'))
463
465
464 @HasPermissionAllDecorator('hg.admin')
466 @HasPermissionAllDecorator('hg.admin')
465 def settings_email(self):
467 def settings_email(self):
466 """GET /admin/settings/email: All items in the collection"""
468 """GET /admin/settings/email: All items in the collection"""
467 # url('admin_settings_email')
469 # url('admin_settings_email')
468 c.active = 'email'
470 c.active = 'email'
469 c.rhodecode_ini = rhodecode.CONFIG
471 c.rhodecode_ini = rhodecode.CONFIG
470
472
471 return htmlfill.render(
473 return htmlfill.render(
472 render('admin/settings/settings.mako'),
474 render('admin/settings/settings.mako'),
473 defaults=self._form_defaults(),
475 defaults=self._form_defaults(),
474 encoding="UTF-8",
476 encoding="UTF-8",
475 force_defaults=False)
477 force_defaults=False)
476
478
477 @HasPermissionAllDecorator('hg.admin')
479 @HasPermissionAllDecorator('hg.admin')
478 @auth.CSRFRequired()
480 @auth.CSRFRequired()
479 def settings_hooks_update(self):
481 def settings_hooks_update(self):
480 """POST or DELETE /admin/settings/hooks: All items in the collection"""
482 """POST or DELETE /admin/settings/hooks: All items in the collection"""
481 # url('admin_settings_hooks')
483 # url('admin_settings_hooks')
482 c.active = 'hooks'
484 c.active = 'hooks'
483 if c.visual.allow_custom_hooks_settings:
485 if c.visual.allow_custom_hooks_settings:
484 ui_key = request.POST.get('new_hook_ui_key')
486 ui_key = request.POST.get('new_hook_ui_key')
485 ui_value = request.POST.get('new_hook_ui_value')
487 ui_value = request.POST.get('new_hook_ui_value')
486
488
487 hook_id = request.POST.get('hook_id')
489 hook_id = request.POST.get('hook_id')
488 new_hook = False
490 new_hook = False
489
491
490 model = SettingsModel()
492 model = SettingsModel()
491 try:
493 try:
492 if ui_value and ui_key:
494 if ui_value and ui_key:
493 model.create_or_update_hook(ui_key, ui_value)
495 model.create_or_update_hook(ui_key, ui_value)
494 h.flash(_('Added new hook'), category='success')
496 h.flash(_('Added new hook'), category='success')
495 new_hook = True
497 new_hook = True
496 elif hook_id:
498 elif hook_id:
497 RhodeCodeUi.delete(hook_id)
499 RhodeCodeUi.delete(hook_id)
498 Session().commit()
500 Session().commit()
499
501
500 # check for edits
502 # check for edits
501 update = False
503 update = False
502 _d = request.POST.dict_of_lists()
504 _d = request.POST.dict_of_lists()
503 for k, v in zip(_d.get('hook_ui_key', []),
505 for k, v in zip(_d.get('hook_ui_key', []),
504 _d.get('hook_ui_value_new', [])):
506 _d.get('hook_ui_value_new', [])):
505 model.create_or_update_hook(k, v)
507 model.create_or_update_hook(k, v)
506 update = True
508 update = True
507
509
508 if update and not new_hook:
510 if update and not new_hook:
509 h.flash(_('Updated hooks'), category='success')
511 h.flash(_('Updated hooks'), category='success')
510 Session().commit()
512 Session().commit()
511 except Exception:
513 except Exception:
512 log.exception("Exception during hook creation")
514 log.exception("Exception during hook creation")
513 h.flash(_('Error occurred during hook creation'),
515 h.flash(_('Error occurred during hook creation'),
514 category='error')
516 category='error')
515
517
516 return redirect(url('admin_settings_hooks'))
518 return redirect(url('admin_settings_hooks'))
517
519
518 @HasPermissionAllDecorator('hg.admin')
520 @HasPermissionAllDecorator('hg.admin')
519 def settings_hooks(self):
521 def settings_hooks(self):
520 """GET /admin/settings/hooks: All items in the collection"""
522 """GET /admin/settings/hooks: All items in the collection"""
521 # url('admin_settings_hooks')
523 # url('admin_settings_hooks')
522 c.active = 'hooks'
524 c.active = 'hooks'
523
525
524 model = SettingsModel()
526 model = SettingsModel()
525 c.hooks = model.get_builtin_hooks()
527 c.hooks = model.get_builtin_hooks()
526 c.custom_hooks = model.get_custom_hooks()
528 c.custom_hooks = model.get_custom_hooks()
527
529
528 return htmlfill.render(
530 return htmlfill.render(
529 render('admin/settings/settings.mako'),
531 render('admin/settings/settings.mako'),
530 defaults=self._form_defaults(),
532 defaults=self._form_defaults(),
531 encoding="UTF-8",
533 encoding="UTF-8",
532 force_defaults=False)
534 force_defaults=False)
533
535
534 @HasPermissionAllDecorator('hg.admin')
536 @HasPermissionAllDecorator('hg.admin')
535 def settings_search(self):
537 def settings_search(self):
536 """GET /admin/settings/search: All items in the collection"""
538 """GET /admin/settings/search: All items in the collection"""
537 # url('admin_settings_search')
539 # url('admin_settings_search')
538 c.active = 'search'
540 c.active = 'search'
539
541
540 from rhodecode.lib.index import searcher_from_config
542 from rhodecode.lib.index import searcher_from_config
541 searcher = searcher_from_config(config)
543 searcher = searcher_from_config(config)
542 c.statistics = searcher.statistics()
544 c.statistics = searcher.statistics()
543
545
544 return render('admin/settings/settings.mako')
546 return render('admin/settings/settings.mako')
545
547
546 @HasPermissionAllDecorator('hg.admin')
548 @HasPermissionAllDecorator('hg.admin')
547 def settings_system(self):
549 def settings_system(self):
548 """GET /admin/settings/system: All items in the collection"""
550 """GET /admin/settings/system: All items in the collection"""
549 # url('admin_settings_system')
551 # url('admin_settings_system')
550 snapshot = str2bool(request.GET.get('snapshot'))
552 snapshot = str2bool(request.GET.get('snapshot'))
551 defaults = self._form_defaults()
553 defaults = self._form_defaults()
552
554
553 c.active = 'system'
555 c.active = 'system'
554 c.rhodecode_update_url = defaults.get('rhodecode_update_url')
556 c.rhodecode_update_url = defaults.get('rhodecode_update_url')
555 server_info = ScmModel().get_server_info(request.environ)
557 server_info = ScmModel().get_server_info(request.environ)
556
558
557 for key, val in server_info.iteritems():
559 for key, val in server_info.iteritems():
558 setattr(c, key, val)
560 setattr(c, key, val)
559
561
560 def val(name, subkey='human_value'):
562 def val(name, subkey='human_value'):
561 return server_info[name][subkey]
563 return server_info[name][subkey]
562
564
563 def state(name):
565 def state(name):
564 return server_info[name]['state']
566 return server_info[name]['state']
565
567
566 def val2(name):
568 def val2(name):
567 val = server_info[name]['human_value']
569 val = server_info[name]['human_value']
568 state = server_info[name]['state']
570 state = server_info[name]['state']
569 return val, state
571 return val, state
570
572
571 c.data_items = [
573 c.data_items = [
572 # update info
574 # update info
573 (_('Update info'), h.literal(
575 (_('Update info'), h.literal(
574 '<span class="link" id="check_for_update" >%s.</span>' % (
576 '<span class="link" id="check_for_update" >%s.</span>' % (
575 _('Check for updates')) +
577 _('Check for updates')) +
576 '<br/> <span >%s.</span>' % (_('Note: please make sure this server can access `%s` for the update link to work') % c.rhodecode_update_url)
578 '<br/> <span >%s.</span>' % (_('Note: please make sure this server can access `%s` for the update link to work') % c.rhodecode_update_url)
577 ), ''),
579 ), ''),
578
580
579 # RhodeCode specific
581 # RhodeCode specific
580 (_('RhodeCode Version'), val('rhodecode_app')['text'], state('rhodecode_app')),
582 (_('RhodeCode Version'), val('rhodecode_app')['text'], state('rhodecode_app')),
581 (_('RhodeCode Server IP'), val('server')['server_ip'], state('server')),
583 (_('RhodeCode Server IP'), val('server')['server_ip'], state('server')),
582 (_('RhodeCode Server ID'), val('server')['server_id'], state('server')),
584 (_('RhodeCode Server ID'), val('server')['server_id'], state('server')),
583 (_('RhodeCode Configuration'), val('rhodecode_config')['path'], state('rhodecode_config')),
585 (_('RhodeCode Configuration'), val('rhodecode_config')['path'], state('rhodecode_config')),
584 ('', '', ''), # spacer
586 ('', '', ''), # spacer
585
587
586 # Database
588 # Database
587 (_('Database'), val('database')['url'], state('database')),
589 (_('Database'), val('database')['url'], state('database')),
588 (_('Database version'), val('database')['version'], state('database')),
590 (_('Database version'), val('database')['version'], state('database')),
589 ('', '', ''), # spacer
591 ('', '', ''), # spacer
590
592
591 # Platform/Python
593 # Platform/Python
592 (_('Platform'), val('platform')['name'], state('platform')),
594 (_('Platform'), val('platform')['name'], state('platform')),
593 (_('Platform UUID'), val('platform')['uuid'], state('platform')),
595 (_('Platform UUID'), val('platform')['uuid'], state('platform')),
594 (_('Python version'), val('python')['version'], state('python')),
596 (_('Python version'), val('python')['version'], state('python')),
595 (_('Python path'), val('python')['executable'], state('python')),
597 (_('Python path'), val('python')['executable'], state('python')),
596 ('', '', ''), # spacer
598 ('', '', ''), # spacer
597
599
598 # Systems stats
600 # Systems stats
599 (_('CPU'), val('cpu'), state('cpu')),
601 (_('CPU'), val('cpu'), state('cpu')),
600 (_('Load'), val('load')['text'], state('load')),
602 (_('Load'), val('load')['text'], state('load')),
601 (_('Memory'), val('memory')['text'], state('memory')),
603 (_('Memory'), val('memory')['text'], state('memory')),
602 (_('Uptime'), val('uptime')['text'], state('uptime')),
604 (_('Uptime'), val('uptime')['text'], state('uptime')),
603 ('', '', ''), # spacer
605 ('', '', ''), # spacer
604
606
605 # Repo storage
607 # Repo storage
606 (_('Storage location'), val('storage')['path'], state('storage')),
608 (_('Storage location'), val('storage')['path'], state('storage')),
607 (_('Storage info'), val('storage')['text'], state('storage')),
609 (_('Storage info'), val('storage')['text'], state('storage')),
608 (_('Storage inodes'), val('storage_inodes')['text'], state('storage_inodes')),
610 (_('Storage inodes'), val('storage_inodes')['text'], state('storage_inodes')),
609
611
610 (_('Gist storage location'), val('storage_gist')['path'], state('storage_gist')),
612 (_('Gist storage location'), val('storage_gist')['path'], state('storage_gist')),
611 (_('Gist storage info'), val('storage_gist')['text'], state('storage_gist')),
613 (_('Gist storage info'), val('storage_gist')['text'], state('storage_gist')),
612
614
613 (_('Archive cache storage location'), val('storage_archive')['path'], state('storage_archive')),
615 (_('Archive cache storage location'), val('storage_archive')['path'], state('storage_archive')),
614 (_('Archive cache info'), val('storage_archive')['text'], state('storage_archive')),
616 (_('Archive cache info'), val('storage_archive')['text'], state('storage_archive')),
615
617
616 (_('Temp storage location'), val('storage_temp')['path'], state('storage_temp')),
618 (_('Temp storage location'), val('storage_temp')['path'], state('storage_temp')),
617 (_('Temp storage info'), val('storage_temp')['text'], state('storage_temp')),
619 (_('Temp storage info'), val('storage_temp')['text'], state('storage_temp')),
618
620
619 (_('Search info'), val('search')['text'], state('search')),
621 (_('Search info'), val('search')['text'], state('search')),
620 (_('Search location'), val('search')['location'], state('search')),
622 (_('Search location'), val('search')['location'], state('search')),
621 ('', '', ''), # spacer
623 ('', '', ''), # spacer
622
624
623 # VCS specific
625 # VCS specific
624 (_('VCS Backends'), val('vcs_backends'), state('vcs_backends')),
626 (_('VCS Backends'), val('vcs_backends'), state('vcs_backends')),
625 (_('VCS Server'), val('vcs_server')['text'], state('vcs_server')),
627 (_('VCS Server'), val('vcs_server')['text'], state('vcs_server')),
626 (_('GIT'), val('git'), state('git')),
628 (_('GIT'), val('git'), state('git')),
627 (_('HG'), val('hg'), state('hg')),
629 (_('HG'), val('hg'), state('hg')),
628 (_('SVN'), val('svn'), state('svn')),
630 (_('SVN'), val('svn'), state('svn')),
629
631
630 ]
632 ]
631
633
632 # TODO: marcink, figure out how to allow only selected users to do this
634 # TODO: marcink, figure out how to allow only selected users to do this
633 c.allowed_to_snapshot = c.rhodecode_user.admin
635 c.allowed_to_snapshot = c.rhodecode_user.admin
634
636
635 if snapshot:
637 if snapshot:
636 if c.allowed_to_snapshot:
638 if c.allowed_to_snapshot:
637 c.data_items.pop(0) # remove server info
639 c.data_items.pop(0) # remove server info
638 return render('admin/settings/settings_system_snapshot.mako')
640 return render('admin/settings/settings_system_snapshot.mako')
639 else:
641 else:
640 h.flash('You are not allowed to do this', category='warning')
642 h.flash('You are not allowed to do this', category='warning')
641
643
642 return htmlfill.render(
644 return htmlfill.render(
643 render('admin/settings/settings.mako'),
645 render('admin/settings/settings.mako'),
644 defaults=defaults,
646 defaults=defaults,
645 encoding="UTF-8",
647 encoding="UTF-8",
646 force_defaults=False)
648 force_defaults=False)
647
649
648 @staticmethod
650 @staticmethod
649 def get_update_data(update_url):
651 def get_update_data(update_url):
650 """Return the JSON update data."""
652 """Return the JSON update data."""
651 ver = rhodecode.__version__
653 ver = rhodecode.__version__
652 log.debug('Checking for upgrade on `%s` server', update_url)
654 log.debug('Checking for upgrade on `%s` server', update_url)
653 opener = urllib2.build_opener()
655 opener = urllib2.build_opener()
654 opener.addheaders = [('User-agent', 'RhodeCode-SCM/%s' % ver)]
656 opener.addheaders = [('User-agent', 'RhodeCode-SCM/%s' % ver)]
655 response = opener.open(update_url)
657 response = opener.open(update_url)
656 response_data = response.read()
658 response_data = response.read()
657 data = json.loads(response_data)
659 data = json.loads(response_data)
658
660
659 return data
661 return data
660
662
661 @HasPermissionAllDecorator('hg.admin')
663 @HasPermissionAllDecorator('hg.admin')
662 def settings_system_update(self):
664 def settings_system_update(self):
663 """GET /admin/settings/system/updates: All items in the collection"""
665 """GET /admin/settings/system/updates: All items in the collection"""
664 # url('admin_settings_system_update')
666 # url('admin_settings_system_update')
665 defaults = self._form_defaults()
667 defaults = self._form_defaults()
666 update_url = defaults.get('rhodecode_update_url', '')
668 update_url = defaults.get('rhodecode_update_url', '')
667
669
668 _err = lambda s: '<div style="color:#ff8888; padding:4px 0px">%s</div>' % (s)
670 _err = lambda s: '<div style="color:#ff8888; padding:4px 0px">%s</div>' % (s)
669 try:
671 try:
670 data = self.get_update_data(update_url)
672 data = self.get_update_data(update_url)
671 except urllib2.URLError as e:
673 except urllib2.URLError as e:
672 log.exception("Exception contacting upgrade server")
674 log.exception("Exception contacting upgrade server")
673 return _err('Failed to contact upgrade server: %r' % e)
675 return _err('Failed to contact upgrade server: %r' % e)
674 except ValueError as e:
676 except ValueError as e:
675 log.exception("Bad data sent from update server")
677 log.exception("Bad data sent from update server")
676 return _err('Bad data sent from update server')
678 return _err('Bad data sent from update server')
677
679
678 latest = data['versions'][0]
680 latest = data['versions'][0]
679
681
680 c.update_url = update_url
682 c.update_url = update_url
681 c.latest_data = latest
683 c.latest_data = latest
682 c.latest_ver = latest['version']
684 c.latest_ver = latest['version']
683 c.cur_ver = rhodecode.__version__
685 c.cur_ver = rhodecode.__version__
684 c.should_upgrade = False
686 c.should_upgrade = False
685
687
686 if (packaging.version.Version(c.latest_ver) >
688 if (packaging.version.Version(c.latest_ver) >
687 packaging.version.Version(c.cur_ver)):
689 packaging.version.Version(c.cur_ver)):
688 c.should_upgrade = True
690 c.should_upgrade = True
689 c.important_notices = latest['general']
691 c.important_notices = latest['general']
690
692
691 return render('admin/settings/settings_system_update.mako')
693 return render('admin/settings/settings_system_update.mako')
692
694
693 @HasPermissionAllDecorator('hg.admin')
695 @HasPermissionAllDecorator('hg.admin')
696 def settings_sessions(self):
697 # url('admin_settings_sessions')
698
699 c.active = 'sessions'
700 c.cleanup_older_days = 60
701 older_than_seconds = 24 * 60 * 60 * 24 * c.cleanup_older_days
702
703 config = system_info.rhodecode_config().get_value()['value']['config']
704 c.session_model = user_sessions.get_session_handler(
705 config.get('beaker.session.type', 'memory'))(config)
706
707 c.session_conf = c.session_model.config
708 c.session_count = c.session_model.get_count()
709 c.session_expired_count = c.session_model.get_expired_count(
710 older_than_seconds)
711
712 return render('admin/settings/settings.mako')
713
714 @HasPermissionAllDecorator('hg.admin')
715 def settings_sessions_cleanup(self):
716 # url('admin_settings_sessions_update')
717
718 expire_days = safe_int(request.POST.get('expire_days'))
719
720 if expire_days is None:
721 expire_days = 60
722
723 older_than_seconds = 24 * 60 * 60 * 24 * expire_days
724
725 config = system_info.rhodecode_config().get_value()['value']['config']
726 session_model = user_sessions.get_session_handler(
727 config.get('beaker.session.type', 'memory'))(config)
728
729 try:
730 session_model.clean_sessions(
731 older_than_seconds=older_than_seconds)
732 h.flash(_('Cleaned up old sessions'), category='success')
733 except user_sessions.CleanupCommand as msg:
734 h.flash(msg, category='warning')
735 except Exception as e:
736 log.exception('Failed session cleanup')
737 h.flash(_('Failed to cleanup up old sessions'), category='error')
738
739 return redirect(url('admin_settings_sessions'))
740
741 @HasPermissionAllDecorator('hg.admin')
694 def settings_supervisor(self):
742 def settings_supervisor(self):
695 c.rhodecode_ini = rhodecode.CONFIG
743 c.rhodecode_ini = rhodecode.CONFIG
696 c.active = 'supervisor'
744 c.active = 'supervisor'
697
745
698 c.supervisor_procs = OrderedDict([
746 c.supervisor_procs = OrderedDict([
699 (SUPERVISOR_MASTER, {}),
747 (SUPERVISOR_MASTER, {}),
700 ])
748 ])
701
749
702 c.log_size = 10240
750 c.log_size = 10240
703 supervisor = SupervisorModel()
751 supervisor = SupervisorModel()
704
752
705 _connection = supervisor.get_connection(
753 _connection = supervisor.get_connection(
706 c.rhodecode_ini.get('supervisor.uri'))
754 c.rhodecode_ini.get('supervisor.uri'))
707 c.connection_error = None
755 c.connection_error = None
708 try:
756 try:
709 _connection.supervisor.getAllProcessInfo()
757 _connection.supervisor.getAllProcessInfo()
710 except Exception as e:
758 except Exception as e:
711 c.connection_error = str(e)
759 c.connection_error = str(e)
712 log.exception("Exception reading supervisor data")
760 log.exception("Exception reading supervisor data")
713 return render('admin/settings/settings.mako')
761 return render('admin/settings/settings.mako')
714
762
715 groupid = c.rhodecode_ini.get('supervisor.group_id')
763 groupid = c.rhodecode_ini.get('supervisor.group_id')
716
764
717 # feed our group processes to the main
765 # feed our group processes to the main
718 for proc in supervisor.get_group_processes(_connection, groupid):
766 for proc in supervisor.get_group_processes(_connection, groupid):
719 c.supervisor_procs[proc['name']] = {}
767 c.supervisor_procs[proc['name']] = {}
720
768
721 for k in c.supervisor_procs.keys():
769 for k in c.supervisor_procs.keys():
722 try:
770 try:
723 # master process info
771 # master process info
724 if k == SUPERVISOR_MASTER:
772 if k == SUPERVISOR_MASTER:
725 _data = supervisor.get_master_state(_connection)
773 _data = supervisor.get_master_state(_connection)
726 _data['name'] = 'supervisor master'
774 _data['name'] = 'supervisor master'
727 _data['description'] = 'pid %s, id: %s, ver: %s' % (
775 _data['description'] = 'pid %s, id: %s, ver: %s' % (
728 _data['pid'], _data['id'], _data['ver'])
776 _data['pid'], _data['id'], _data['ver'])
729 c.supervisor_procs[k] = _data
777 c.supervisor_procs[k] = _data
730 else:
778 else:
731 procid = groupid + ":" + k
779 procid = groupid + ":" + k
732 c.supervisor_procs[k] = supervisor.get_process_info(_connection, procid)
780 c.supervisor_procs[k] = supervisor.get_process_info(_connection, procid)
733 except Exception as e:
781 except Exception as e:
734 log.exception("Exception reading supervisor data")
782 log.exception("Exception reading supervisor data")
735 c.supervisor_procs[k] = {'_rhodecode_error': str(e)}
783 c.supervisor_procs[k] = {'_rhodecode_error': str(e)}
736
784
737 return render('admin/settings/settings.mako')
785 return render('admin/settings/settings.mako')
738
786
739 @HasPermissionAllDecorator('hg.admin')
787 @HasPermissionAllDecorator('hg.admin')
740 def settings_supervisor_log(self, procid):
788 def settings_supervisor_log(self, procid):
741 import rhodecode
789 import rhodecode
742 c.rhodecode_ini = rhodecode.CONFIG
790 c.rhodecode_ini = rhodecode.CONFIG
743 c.active = 'supervisor_tail'
791 c.active = 'supervisor_tail'
744
792
745 supervisor = SupervisorModel()
793 supervisor = SupervisorModel()
746 _connection = supervisor.get_connection(c.rhodecode_ini.get('supervisor.uri'))
794 _connection = supervisor.get_connection(c.rhodecode_ini.get('supervisor.uri'))
747 groupid = c.rhodecode_ini.get('supervisor.group_id')
795 groupid = c.rhodecode_ini.get('supervisor.group_id')
748 procid = groupid + ":" + procid if procid != SUPERVISOR_MASTER else procid
796 procid = groupid + ":" + procid if procid != SUPERVISOR_MASTER else procid
749
797
750 c.log_size = 10240
798 c.log_size = 10240
751 offset = abs(safe_int(request.GET.get('offset', c.log_size))) * -1
799 offset = abs(safe_int(request.GET.get('offset', c.log_size))) * -1
752 c.log = supervisor.read_process_log(_connection, procid, offset, 0)
800 c.log = supervisor.read_process_log(_connection, procid, offset, 0)
753
801
754 return render('admin/settings/settings.mako')
802 return render('admin/settings/settings.mako')
755
803
756 @HasPermissionAllDecorator('hg.admin')
804 @HasPermissionAllDecorator('hg.admin')
757 @auth.CSRFRequired()
805 @auth.CSRFRequired()
758 def settings_labs_update(self):
806 def settings_labs_update(self):
759 """POST /admin/settings/labs: All items in the collection"""
807 """POST /admin/settings/labs: All items in the collection"""
760 # url('admin_settings/labs', method={'POST'})
808 # url('admin_settings/labs', method={'POST'})
761 c.active = 'labs'
809 c.active = 'labs'
762
810
763 application_form = LabsSettingsForm()()
811 application_form = LabsSettingsForm()()
764 try:
812 try:
765 form_result = application_form.to_python(dict(request.POST))
813 form_result = application_form.to_python(dict(request.POST))
766 except formencode.Invalid as errors:
814 except formencode.Invalid as errors:
767 h.flash(
815 h.flash(
768 _('Some form inputs contain invalid data.'),
816 _('Some form inputs contain invalid data.'),
769 category='error')
817 category='error')
770 return htmlfill.render(
818 return htmlfill.render(
771 render('admin/settings/settings.mako'),
819 render('admin/settings/settings.mako'),
772 defaults=errors.value,
820 defaults=errors.value,
773 errors=errors.error_dict or {},
821 errors=errors.error_dict or {},
774 prefix_error=False,
822 prefix_error=False,
775 encoding='UTF-8',
823 encoding='UTF-8',
776 force_defaults=False
824 force_defaults=False
777 )
825 )
778
826
779 try:
827 try:
780 session = Session()
828 session = Session()
781 for setting in _LAB_SETTINGS:
829 for setting in _LAB_SETTINGS:
782 setting_name = setting.key[len('rhodecode_'):]
830 setting_name = setting.key[len('rhodecode_'):]
783 sett = SettingsModel().create_or_update_setting(
831 sett = SettingsModel().create_or_update_setting(
784 setting_name, form_result[setting.key], setting.type)
832 setting_name, form_result[setting.key], setting.type)
785 session.add(sett)
833 session.add(sett)
786
834
787 except Exception:
835 except Exception:
788 log.exception('Exception while updating lab settings')
836 log.exception('Exception while updating lab settings')
789 h.flash(_('Error occurred during updating labs settings'),
837 h.flash(_('Error occurred during updating labs settings'),
790 category='error')
838 category='error')
791 else:
839 else:
792 Session().commit()
840 Session().commit()
793 SettingsModel().invalidate_settings_cache()
841 SettingsModel().invalidate_settings_cache()
794 h.flash(_('Updated Labs settings'), category='success')
842 h.flash(_('Updated Labs settings'), category='success')
795 return redirect(url('admin_settings_labs'))
843 return redirect(url('admin_settings_labs'))
796
844
797 return htmlfill.render(
845 return htmlfill.render(
798 render('admin/settings/settings.mako'),
846 render('admin/settings/settings.mako'),
799 defaults=self._form_defaults(),
847 defaults=self._form_defaults(),
800 encoding='UTF-8',
848 encoding='UTF-8',
801 force_defaults=False)
849 force_defaults=False)
802
850
803 @HasPermissionAllDecorator('hg.admin')
851 @HasPermissionAllDecorator('hg.admin')
804 def settings_labs(self):
852 def settings_labs(self):
805 """GET /admin/settings/labs: All items in the collection"""
853 """GET /admin/settings/labs: All items in the collection"""
806 # url('admin_settings_labs')
854 # url('admin_settings_labs')
807 if not c.labs_active:
855 if not c.labs_active:
808 redirect(url('admin_settings'))
856 redirect(url('admin_settings'))
809
857
810 c.active = 'labs'
858 c.active = 'labs'
811 c.lab_settings = _LAB_SETTINGS
859 c.lab_settings = _LAB_SETTINGS
812
860
813 return htmlfill.render(
861 return htmlfill.render(
814 render('admin/settings/settings.mako'),
862 render('admin/settings/settings.mako'),
815 defaults=self._form_defaults(),
863 defaults=self._form_defaults(),
816 encoding='UTF-8',
864 encoding='UTF-8',
817 force_defaults=False)
865 force_defaults=False)
818
866
819 def _form_defaults(self):
867 def _form_defaults(self):
820 defaults = SettingsModel().get_all_settings()
868 defaults = SettingsModel().get_all_settings()
821 defaults.update(self._get_hg_ui_settings())
869 defaults.update(self._get_hg_ui_settings())
822 defaults.update({
870 defaults.update({
823 'new_svn_branch': '',
871 'new_svn_branch': '',
824 'new_svn_tag': '',
872 'new_svn_tag': '',
825 })
873 })
826 return defaults
874 return defaults
827
875
828
876
829 # :param key: name of the setting including the 'rhodecode_' prefix
877 # :param key: name of the setting including the 'rhodecode_' prefix
830 # :param type: the RhodeCodeSetting type to use.
878 # :param type: the RhodeCodeSetting type to use.
831 # :param group: the i18ned group in which we should dispaly this setting
879 # :param group: the i18ned group in which we should dispaly this setting
832 # :param label: the i18ned label we should display for this setting
880 # :param label: the i18ned label we should display for this setting
833 # :param help: the i18ned help we should dispaly for this setting
881 # :param help: the i18ned help we should dispaly for this setting
834 LabSetting = collections.namedtuple(
882 LabSetting = collections.namedtuple(
835 'LabSetting', ('key', 'type', 'group', 'label', 'help'))
883 'LabSetting', ('key', 'type', 'group', 'label', 'help'))
836
884
837
885
838 # This list has to be kept in sync with the form
886 # This list has to be kept in sync with the form
839 # rhodecode.model.forms.LabsSettingsForm.
887 # rhodecode.model.forms.LabsSettingsForm.
840 _LAB_SETTINGS = [
888 _LAB_SETTINGS = [
841
889
842 ]
890 ]
@@ -1,639 +1,642 b''
1 import os
1 import os
2 import sys
2 import sys
3 import time
3 import time
4 import platform
4 import platform
5 import pkg_resources
5 import pkg_resources
6 import logging
6 import logging
7 import string
7 import string
8
8
9
9
10 log = logging.getLogger(__name__)
10 log = logging.getLogger(__name__)
11
11
12
12
13 psutil = None
13 psutil = None
14
14
15 try:
15 try:
16 # cygwin cannot have yet psutil support.
16 # cygwin cannot have yet psutil support.
17 import psutil as psutil
17 import psutil as psutil
18 except ImportError:
18 except ImportError:
19 pass
19 pass
20
20
21
21
22 _NA = 'NOT AVAILABLE'
22 _NA = 'NOT AVAILABLE'
23
23
24 STATE_OK = 'ok'
24 STATE_OK = 'ok'
25 STATE_ERR = 'error'
25 STATE_ERR = 'error'
26 STATE_WARN = 'warning'
26 STATE_WARN = 'warning'
27
27
28 STATE_OK_DEFAULT = {'message': '', 'type': STATE_OK}
28 STATE_OK_DEFAULT = {'message': '', 'type': STATE_OK}
29
29
30
30
31 # HELPERS
31 # HELPERS
32 def percentage(part, whole):
32 def percentage(part, whole):
33 whole = float(whole)
33 whole = float(whole)
34 if whole > 0:
34 if whole > 0:
35 return round(100 * float(part) / whole, 1)
35 return round(100 * float(part) / whole, 1)
36 return 0.0
36 return 0.0
37
37
38
38
39 def get_storage_size(storage_path):
39 def get_storage_size(storage_path):
40 sizes = []
40 sizes = []
41 for file_ in os.listdir(storage_path):
41 for file_ in os.listdir(storage_path):
42 storage_file = os.path.join(storage_path, file_)
42 storage_file = os.path.join(storage_path, file_)
43 if os.path.isfile(storage_file):
43 if os.path.isfile(storage_file):
44 try:
44 try:
45 sizes.append(os.path.getsize(storage_file))
45 sizes.append(os.path.getsize(storage_file))
46 except OSError:
46 except OSError:
47 log.exception('Failed to get size of storage file %s',
47 log.exception('Failed to get size of storage file %s',
48 storage_file)
48 storage_file)
49 pass
49 pass
50
50
51 return sum(sizes)
51 return sum(sizes)
52
52
53
53
54 class SysInfoRes(object):
54 class SysInfoRes(object):
55 def __init__(self, value, state=STATE_OK_DEFAULT, human_value=None):
55 def __init__(self, value, state=STATE_OK_DEFAULT, human_value=None):
56 self.value = value
56 self.value = value
57 self.state = state
57 self.state = state
58 self.human_value = human_value or value
58 self.human_value = human_value or value
59
59
60 def __json__(self):
60 def __json__(self):
61 return {
61 return {
62 'value': self.value,
62 'value': self.value,
63 'state': self.state,
63 'state': self.state,
64 'human_value': self.human_value,
64 'human_value': self.human_value,
65 }
65 }
66
66
67 def get_value(self):
68 return self.__json__()
69
67 def __str__(self):
70 def __str__(self):
68 return '<SysInfoRes({})>'.format(self.__json__())
71 return '<SysInfoRes({})>'.format(self.__json__())
69
72
70
73
71 class SysInfo(object):
74 class SysInfo(object):
72
75
73 def __init__(self, func_name, **kwargs):
76 def __init__(self, func_name, **kwargs):
74 self.func_name = func_name
77 self.func_name = func_name
75 self.value = _NA
78 self.value = _NA
76 self.state = None
79 self.state = None
77 self.kwargs = kwargs or {}
80 self.kwargs = kwargs or {}
78
81
79 def __call__(self):
82 def __call__(self):
80 computed = self.compute(**self.kwargs)
83 computed = self.compute(**self.kwargs)
81 if not isinstance(computed, SysInfoRes):
84 if not isinstance(computed, SysInfoRes):
82 raise ValueError(
85 raise ValueError(
83 'computed value for {} is not instance of '
86 'computed value for {} is not instance of '
84 '{}, got {} instead'.format(
87 '{}, got {} instead'.format(
85 self.func_name, SysInfoRes, type(computed)))
88 self.func_name, SysInfoRes, type(computed)))
86 return computed.__json__()
89 return computed.__json__()
87
90
88 def __str__(self):
91 def __str__(self):
89 return '<SysInfo({})>'.format(self.func_name)
92 return '<SysInfo({})>'.format(self.func_name)
90
93
91 def compute(self, **kwargs):
94 def compute(self, **kwargs):
92 return self.func_name(**kwargs)
95 return self.func_name(**kwargs)
93
96
94
97
95 # SysInfo functions
98 # SysInfo functions
96 def python_info():
99 def python_info():
97 value = dict(version=' '.join(platform._sys_version()),
100 value = dict(version=' '.join(platform._sys_version()),
98 executable=sys.executable)
101 executable=sys.executable)
99 return SysInfoRes(value=value)
102 return SysInfoRes(value=value)
100
103
101
104
102 def py_modules():
105 def py_modules():
103 mods = dict([(p.project_name, p.version)
106 mods = dict([(p.project_name, p.version)
104 for p in pkg_resources.working_set])
107 for p in pkg_resources.working_set])
105 value = sorted(mods.items(), key=lambda k: k[0].lower())
108 value = sorted(mods.items(), key=lambda k: k[0].lower())
106 return SysInfoRes(value=value)
109 return SysInfoRes(value=value)
107
110
108
111
109 def platform_type():
112 def platform_type():
110 from rhodecode.lib.utils import safe_unicode, generate_platform_uuid
113 from rhodecode.lib.utils import safe_unicode, generate_platform_uuid
111
114
112 value = dict(
115 value = dict(
113 name=safe_unicode(platform.platform()),
116 name=safe_unicode(platform.platform()),
114 uuid=generate_platform_uuid()
117 uuid=generate_platform_uuid()
115 )
118 )
116 return SysInfoRes(value=value)
119 return SysInfoRes(value=value)
117
120
118
121
119 def uptime():
122 def uptime():
120 from rhodecode.lib.helpers import age, time_to_datetime
123 from rhodecode.lib.helpers import age, time_to_datetime
121
124
122 value = dict(boot_time=0, uptime=0, text='')
125 value = dict(boot_time=0, uptime=0, text='')
123 state = STATE_OK_DEFAULT
126 state = STATE_OK_DEFAULT
124 if not psutil:
127 if not psutil:
125 return SysInfoRes(value=value, state=state)
128 return SysInfoRes(value=value, state=state)
126
129
127 boot_time = psutil.boot_time()
130 boot_time = psutil.boot_time()
128 value['boot_time'] = boot_time
131 value['boot_time'] = boot_time
129 value['uptime'] = time.time() - boot_time
132 value['uptime'] = time.time() - boot_time
130
133
131 human_value = value.copy()
134 human_value = value.copy()
132 human_value['boot_time'] = time_to_datetime(boot_time)
135 human_value['boot_time'] = time_to_datetime(boot_time)
133 human_value['uptime'] = age(time_to_datetime(boot_time), show_suffix=False)
136 human_value['uptime'] = age(time_to_datetime(boot_time), show_suffix=False)
134 human_value['text'] = 'Server started {}'.format(
137 human_value['text'] = 'Server started {}'.format(
135 age(time_to_datetime(boot_time)))
138 age(time_to_datetime(boot_time)))
136
139
137 return SysInfoRes(value=value, human_value=human_value)
140 return SysInfoRes(value=value, human_value=human_value)
138
141
139
142
140 def memory():
143 def memory():
141 from rhodecode.lib.helpers import format_byte_size_binary
144 from rhodecode.lib.helpers import format_byte_size_binary
142 value = dict(available=0, used=0, used_real=0, cached=0, percent=0,
145 value = dict(available=0, used=0, used_real=0, cached=0, percent=0,
143 percent_used=0, free=0, inactive=0, active=0, shared=0,
146 percent_used=0, free=0, inactive=0, active=0, shared=0,
144 total=0, buffers=0, text='')
147 total=0, buffers=0, text='')
145
148
146 state = STATE_OK_DEFAULT
149 state = STATE_OK_DEFAULT
147 if not psutil:
150 if not psutil:
148 return SysInfoRes(value=value, state=state)
151 return SysInfoRes(value=value, state=state)
149
152
150 value.update(dict(psutil.virtual_memory()._asdict()))
153 value.update(dict(psutil.virtual_memory()._asdict()))
151 value['used_real'] = value['total'] - value['available']
154 value['used_real'] = value['total'] - value['available']
152 value['percent_used'] = psutil._common.usage_percent(
155 value['percent_used'] = psutil._common.usage_percent(
153 value['used_real'], value['total'], 1)
156 value['used_real'], value['total'], 1)
154
157
155 human_value = value.copy()
158 human_value = value.copy()
156 human_value['text'] = '%s/%s, %s%% used' % (
159 human_value['text'] = '%s/%s, %s%% used' % (
157 format_byte_size_binary(value['used_real']),
160 format_byte_size_binary(value['used_real']),
158 format_byte_size_binary(value['total']),
161 format_byte_size_binary(value['total']),
159 value['percent_used'],)
162 value['percent_used'],)
160
163
161 keys = value.keys()[::]
164 keys = value.keys()[::]
162 keys.pop(keys.index('percent'))
165 keys.pop(keys.index('percent'))
163 keys.pop(keys.index('percent_used'))
166 keys.pop(keys.index('percent_used'))
164 keys.pop(keys.index('text'))
167 keys.pop(keys.index('text'))
165 for k in keys:
168 for k in keys:
166 human_value[k] = format_byte_size_binary(value[k])
169 human_value[k] = format_byte_size_binary(value[k])
167
170
168 if state['type'] == STATE_OK and value['percent_used'] > 90:
171 if state['type'] == STATE_OK and value['percent_used'] > 90:
169 msg = 'Critical: your available RAM memory is very low.'
172 msg = 'Critical: your available RAM memory is very low.'
170 state = {'message': msg, 'type': STATE_ERR}
173 state = {'message': msg, 'type': STATE_ERR}
171
174
172 elif state['type'] == STATE_OK and value['percent_used'] > 70:
175 elif state['type'] == STATE_OK and value['percent_used'] > 70:
173 msg = 'Warning: your available RAM memory is running low.'
176 msg = 'Warning: your available RAM memory is running low.'
174 state = {'message': msg, 'type': STATE_WARN}
177 state = {'message': msg, 'type': STATE_WARN}
175
178
176 return SysInfoRes(value=value, state=state, human_value=human_value)
179 return SysInfoRes(value=value, state=state, human_value=human_value)
177
180
178
181
179 def machine_load():
182 def machine_load():
180 value = {'1_min': _NA, '5_min': _NA, '15_min': _NA, 'text': ''}
183 value = {'1_min': _NA, '5_min': _NA, '15_min': _NA, 'text': ''}
181 state = STATE_OK_DEFAULT
184 state = STATE_OK_DEFAULT
182 if not psutil:
185 if not psutil:
183 return SysInfoRes(value=value, state=state)
186 return SysInfoRes(value=value, state=state)
184
187
185 # load averages
188 # load averages
186 if hasattr(psutil.os, 'getloadavg'):
189 if hasattr(psutil.os, 'getloadavg'):
187 value.update(dict(
190 value.update(dict(
188 zip(['1_min', '5_min', '15_min'], psutil.os.getloadavg())))
191 zip(['1_min', '5_min', '15_min'], psutil.os.getloadavg())))
189
192
190 human_value = value.copy()
193 human_value = value.copy()
191 human_value['text'] = '1min: {}, 5min: {}, 15min: {}'.format(
194 human_value['text'] = '1min: {}, 5min: {}, 15min: {}'.format(
192 value['1_min'], value['5_min'], value['15_min'])
195 value['1_min'], value['5_min'], value['15_min'])
193
196
194 if state['type'] == STATE_OK and value['15_min'] > 5:
197 if state['type'] == STATE_OK and value['15_min'] > 5:
195 msg = 'Warning: your machine load is very high.'
198 msg = 'Warning: your machine load is very high.'
196 state = {'message': msg, 'type': STATE_WARN}
199 state = {'message': msg, 'type': STATE_WARN}
197
200
198 return SysInfoRes(value=value, state=state, human_value=human_value)
201 return SysInfoRes(value=value, state=state, human_value=human_value)
199
202
200
203
201 def cpu():
204 def cpu():
202 value = 0
205 value = 0
203 state = STATE_OK_DEFAULT
206 state = STATE_OK_DEFAULT
204
207
205 if not psutil:
208 if not psutil:
206 return SysInfoRes(value=value, state=state)
209 return SysInfoRes(value=value, state=state)
207
210
208 value = psutil.cpu_percent(0.5)
211 value = psutil.cpu_percent(0.5)
209 human_value = '{} %'.format(value)
212 human_value = '{} %'.format(value)
210 return SysInfoRes(value=value, state=state, human_value=human_value)
213 return SysInfoRes(value=value, state=state, human_value=human_value)
211
214
212
215
213 def storage():
216 def storage():
214 from rhodecode.lib.helpers import format_byte_size_binary
217 from rhodecode.lib.helpers import format_byte_size_binary
215 from rhodecode.model.settings import VcsSettingsModel
218 from rhodecode.model.settings import VcsSettingsModel
216 path = VcsSettingsModel().get_repos_location()
219 path = VcsSettingsModel().get_repos_location()
217
220
218 value = dict(percent=0, used=0, total=0, path=path, text='')
221 value = dict(percent=0, used=0, total=0, path=path, text='')
219 state = STATE_OK_DEFAULT
222 state = STATE_OK_DEFAULT
220 if not psutil:
223 if not psutil:
221 return SysInfoRes(value=value, state=state)
224 return SysInfoRes(value=value, state=state)
222
225
223 try:
226 try:
224 value.update(dict(psutil.disk_usage(path)._asdict()))
227 value.update(dict(psutil.disk_usage(path)._asdict()))
225 except Exception as e:
228 except Exception as e:
226 log.exception('Failed to fetch disk info')
229 log.exception('Failed to fetch disk info')
227 state = {'message': str(e), 'type': STATE_ERR}
230 state = {'message': str(e), 'type': STATE_ERR}
228
231
229 human_value = value.copy()
232 human_value = value.copy()
230 human_value['used'] = format_byte_size_binary(value['used'])
233 human_value['used'] = format_byte_size_binary(value['used'])
231 human_value['total'] = format_byte_size_binary(value['total'])
234 human_value['total'] = format_byte_size_binary(value['total'])
232 human_value['text'] = "{}/{}, {}% used".format(
235 human_value['text'] = "{}/{}, {}% used".format(
233 format_byte_size_binary(value['used']),
236 format_byte_size_binary(value['used']),
234 format_byte_size_binary(value['total']),
237 format_byte_size_binary(value['total']),
235 value['percent'])
238 value['percent'])
236
239
237 if state['type'] == STATE_OK and value['percent'] > 90:
240 if state['type'] == STATE_OK and value['percent'] > 90:
238 msg = 'Critical: your disk space is very low.'
241 msg = 'Critical: your disk space is very low.'
239 state = {'message': msg, 'type': STATE_ERR}
242 state = {'message': msg, 'type': STATE_ERR}
240
243
241 elif state['type'] == STATE_OK and value['percent'] > 70:
244 elif state['type'] == STATE_OK and value['percent'] > 70:
242 msg = 'Warning: your disk space is running low.'
245 msg = 'Warning: your disk space is running low.'
243 state = {'message': msg, 'type': STATE_WARN}
246 state = {'message': msg, 'type': STATE_WARN}
244
247
245 return SysInfoRes(value=value, state=state, human_value=human_value)
248 return SysInfoRes(value=value, state=state, human_value=human_value)
246
249
247
250
248 def storage_inodes():
251 def storage_inodes():
249 from rhodecode.model.settings import VcsSettingsModel
252 from rhodecode.model.settings import VcsSettingsModel
250 path = VcsSettingsModel().get_repos_location()
253 path = VcsSettingsModel().get_repos_location()
251
254
252 value = dict(percent=0, free=0, used=0, total=0, path=path, text='')
255 value = dict(percent=0, free=0, used=0, total=0, path=path, text='')
253 state = STATE_OK_DEFAULT
256 state = STATE_OK_DEFAULT
254 if not psutil:
257 if not psutil:
255 return SysInfoRes(value=value, state=state)
258 return SysInfoRes(value=value, state=state)
256
259
257 try:
260 try:
258 i_stat = os.statvfs(path)
261 i_stat = os.statvfs(path)
259 value['free'] = i_stat.f_ffree
262 value['free'] = i_stat.f_ffree
260 value['used'] = i_stat.f_files-i_stat.f_favail
263 value['used'] = i_stat.f_files-i_stat.f_favail
261 value['total'] = i_stat.f_files
264 value['total'] = i_stat.f_files
262 value['percent'] = percentage(value['used'], value['total'])
265 value['percent'] = percentage(value['used'], value['total'])
263 except Exception as e:
266 except Exception as e:
264 log.exception('Failed to fetch disk inodes info')
267 log.exception('Failed to fetch disk inodes info')
265 state = {'message': str(e), 'type': STATE_ERR}
268 state = {'message': str(e), 'type': STATE_ERR}
266
269
267 human_value = value.copy()
270 human_value = value.copy()
268 human_value['text'] = "{}/{}, {}% used".format(
271 human_value['text'] = "{}/{}, {}% used".format(
269 value['used'], value['total'], value['percent'])
272 value['used'], value['total'], value['percent'])
270
273
271 if state['type'] == STATE_OK and value['percent'] > 90:
274 if state['type'] == STATE_OK and value['percent'] > 90:
272 msg = 'Critical: your disk free inodes are very low.'
275 msg = 'Critical: your disk free inodes are very low.'
273 state = {'message': msg, 'type': STATE_ERR}
276 state = {'message': msg, 'type': STATE_ERR}
274
277
275 elif state['type'] == STATE_OK and value['percent'] > 70:
278 elif state['type'] == STATE_OK and value['percent'] > 70:
276 msg = 'Warning: your disk free inodes are running low.'
279 msg = 'Warning: your disk free inodes are running low.'
277 state = {'message': msg, 'type': STATE_WARN}
280 state = {'message': msg, 'type': STATE_WARN}
278
281
279 return SysInfoRes(value=value, state=state, human_value=human_value)
282 return SysInfoRes(value=value, state=state, human_value=human_value)
280
283
281
284
282 def storage_archives():
285 def storage_archives():
283 import rhodecode
286 import rhodecode
284 from rhodecode.lib.utils import safe_str
287 from rhodecode.lib.utils import safe_str
285 from rhodecode.lib.helpers import format_byte_size_binary
288 from rhodecode.lib.helpers import format_byte_size_binary
286
289
287 msg = 'Enable this by setting ' \
290 msg = 'Enable this by setting ' \
288 'archive_cache_dir=/path/to/cache option in the .ini file'
291 'archive_cache_dir=/path/to/cache option in the .ini file'
289 path = safe_str(rhodecode.CONFIG.get('archive_cache_dir', msg))
292 path = safe_str(rhodecode.CONFIG.get('archive_cache_dir', msg))
290
293
291 value = dict(percent=0, used=0, total=0, items=0, path=path, text='')
294 value = dict(percent=0, used=0, total=0, items=0, path=path, text='')
292 state = STATE_OK_DEFAULT
295 state = STATE_OK_DEFAULT
293 try:
296 try:
294 items_count = 0
297 items_count = 0
295 used = 0
298 used = 0
296 for root, dirs, files in os.walk(path):
299 for root, dirs, files in os.walk(path):
297 if root == path:
300 if root == path:
298 items_count = len(files)
301 items_count = len(files)
299
302
300 for f in files:
303 for f in files:
301 try:
304 try:
302 used += os.path.getsize(os.path.join(root, f))
305 used += os.path.getsize(os.path.join(root, f))
303 except OSError:
306 except OSError:
304 pass
307 pass
305 value.update({
308 value.update({
306 'percent': 100,
309 'percent': 100,
307 'used': used,
310 'used': used,
308 'total': used,
311 'total': used,
309 'items': items_count
312 'items': items_count
310 })
313 })
311
314
312 except Exception as e:
315 except Exception as e:
313 log.exception('failed to fetch archive cache storage')
316 log.exception('failed to fetch archive cache storage')
314 state = {'message': str(e), 'type': STATE_ERR}
317 state = {'message': str(e), 'type': STATE_ERR}
315
318
316 human_value = value.copy()
319 human_value = value.copy()
317 human_value['used'] = format_byte_size_binary(value['used'])
320 human_value['used'] = format_byte_size_binary(value['used'])
318 human_value['total'] = format_byte_size_binary(value['total'])
321 human_value['total'] = format_byte_size_binary(value['total'])
319 human_value['text'] = "{} ({} items)".format(
322 human_value['text'] = "{} ({} items)".format(
320 human_value['used'], value['items'])
323 human_value['used'], value['items'])
321
324
322 return SysInfoRes(value=value, state=state, human_value=human_value)
325 return SysInfoRes(value=value, state=state, human_value=human_value)
323
326
324
327
325 def storage_gist():
328 def storage_gist():
326 from rhodecode.model.gist import GIST_STORE_LOC
329 from rhodecode.model.gist import GIST_STORE_LOC
327 from rhodecode.model.settings import VcsSettingsModel
330 from rhodecode.model.settings import VcsSettingsModel
328 from rhodecode.lib.utils import safe_str
331 from rhodecode.lib.utils import safe_str
329 from rhodecode.lib.helpers import format_byte_size_binary
332 from rhodecode.lib.helpers import format_byte_size_binary
330 path = safe_str(os.path.join(
333 path = safe_str(os.path.join(
331 VcsSettingsModel().get_repos_location(), GIST_STORE_LOC))
334 VcsSettingsModel().get_repos_location(), GIST_STORE_LOC))
332
335
333 # gist storage
336 # gist storage
334 value = dict(percent=0, used=0, total=0, items=0, path=path, text='')
337 value = dict(percent=0, used=0, total=0, items=0, path=path, text='')
335 state = STATE_OK_DEFAULT
338 state = STATE_OK_DEFAULT
336
339
337 try:
340 try:
338 items_count = 0
341 items_count = 0
339 used = 0
342 used = 0
340 for root, dirs, files in os.walk(path):
343 for root, dirs, files in os.walk(path):
341 if root == path:
344 if root == path:
342 items_count = len(dirs)
345 items_count = len(dirs)
343
346
344 for f in files:
347 for f in files:
345 try:
348 try:
346 used += os.path.getsize(os.path.join(root, f))
349 used += os.path.getsize(os.path.join(root, f))
347 except OSError:
350 except OSError:
348 pass
351 pass
349 value.update({
352 value.update({
350 'percent': 100,
353 'percent': 100,
351 'used': used,
354 'used': used,
352 'total': used,
355 'total': used,
353 'items': items_count
356 'items': items_count
354 })
357 })
355 except Exception as e:
358 except Exception as e:
356 log.exception('failed to fetch gist storage items')
359 log.exception('failed to fetch gist storage items')
357 state = {'message': str(e), 'type': STATE_ERR}
360 state = {'message': str(e), 'type': STATE_ERR}
358
361
359 human_value = value.copy()
362 human_value = value.copy()
360 human_value['used'] = format_byte_size_binary(value['used'])
363 human_value['used'] = format_byte_size_binary(value['used'])
361 human_value['total'] = format_byte_size_binary(value['total'])
364 human_value['total'] = format_byte_size_binary(value['total'])
362 human_value['text'] = "{} ({} items)".format(
365 human_value['text'] = "{} ({} items)".format(
363 human_value['used'], value['items'])
366 human_value['used'], value['items'])
364
367
365 return SysInfoRes(value=value, state=state, human_value=human_value)
368 return SysInfoRes(value=value, state=state, human_value=human_value)
366
369
367
370
368 def storage_temp():
371 def storage_temp():
369 import tempfile
372 import tempfile
370 from rhodecode.lib.helpers import format_byte_size_binary
373 from rhodecode.lib.helpers import format_byte_size_binary
371
374
372 path = tempfile.gettempdir()
375 path = tempfile.gettempdir()
373 value = dict(percent=0, used=0, total=0, items=0, path=path, text='')
376 value = dict(percent=0, used=0, total=0, items=0, path=path, text='')
374 state = STATE_OK_DEFAULT
377 state = STATE_OK_DEFAULT
375
378
376 if not psutil:
379 if not psutil:
377 return SysInfoRes(value=value, state=state)
380 return SysInfoRes(value=value, state=state)
378
381
379 try:
382 try:
380 value.update(dict(psutil.disk_usage(path)._asdict()))
383 value.update(dict(psutil.disk_usage(path)._asdict()))
381 except Exception as e:
384 except Exception as e:
382 log.exception('Failed to fetch temp dir info')
385 log.exception('Failed to fetch temp dir info')
383 state = {'message': str(e), 'type': STATE_ERR}
386 state = {'message': str(e), 'type': STATE_ERR}
384
387
385 human_value = value.copy()
388 human_value = value.copy()
386 human_value['used'] = format_byte_size_binary(value['used'])
389 human_value['used'] = format_byte_size_binary(value['used'])
387 human_value['total'] = format_byte_size_binary(value['total'])
390 human_value['total'] = format_byte_size_binary(value['total'])
388 human_value['text'] = "{}/{}, {}% used".format(
391 human_value['text'] = "{}/{}, {}% used".format(
389 format_byte_size_binary(value['used']),
392 format_byte_size_binary(value['used']),
390 format_byte_size_binary(value['total']),
393 format_byte_size_binary(value['total']),
391 value['percent'])
394 value['percent'])
392
395
393 return SysInfoRes(value=value, state=state, human_value=human_value)
396 return SysInfoRes(value=value, state=state, human_value=human_value)
394
397
395
398
396 def search_info():
399 def search_info():
397 import rhodecode
400 import rhodecode
398 from rhodecode.lib.index import searcher_from_config
401 from rhodecode.lib.index import searcher_from_config
399
402
400 backend = rhodecode.CONFIG.get('search.module', '')
403 backend = rhodecode.CONFIG.get('search.module', '')
401 location = rhodecode.CONFIG.get('search.location', '')
404 location = rhodecode.CONFIG.get('search.location', '')
402
405
403 try:
406 try:
404 searcher = searcher_from_config(rhodecode.CONFIG)
407 searcher = searcher_from_config(rhodecode.CONFIG)
405 searcher = searcher.__class__.__name__
408 searcher = searcher.__class__.__name__
406 except Exception:
409 except Exception:
407 searcher = None
410 searcher = None
408
411
409 value = dict(
412 value = dict(
410 backend=backend, searcher=searcher, location=location, text='')
413 backend=backend, searcher=searcher, location=location, text='')
411 state = STATE_OK_DEFAULT
414 state = STATE_OK_DEFAULT
412
415
413 human_value = value.copy()
416 human_value = value.copy()
414 human_value['text'] = "backend:`{}`".format(human_value['backend'])
417 human_value['text'] = "backend:`{}`".format(human_value['backend'])
415
418
416 return SysInfoRes(value=value, state=state, human_value=human_value)
419 return SysInfoRes(value=value, state=state, human_value=human_value)
417
420
418
421
419 def git_info():
422 def git_info():
420 from rhodecode.lib.vcs.backends import git
423 from rhodecode.lib.vcs.backends import git
421 state = STATE_OK_DEFAULT
424 state = STATE_OK_DEFAULT
422 value = human_value = ''
425 value = human_value = ''
423 try:
426 try:
424 value = git.discover_git_version(raise_on_exc=True)
427 value = git.discover_git_version(raise_on_exc=True)
425 human_value = 'version reported from VCSServer: {}'.format(value)
428 human_value = 'version reported from VCSServer: {}'.format(value)
426 except Exception as e:
429 except Exception as e:
427 state = {'message': str(e), 'type': STATE_ERR}
430 state = {'message': str(e), 'type': STATE_ERR}
428
431
429 return SysInfoRes(value=value, state=state, human_value=human_value)
432 return SysInfoRes(value=value, state=state, human_value=human_value)
430
433
431
434
432 def hg_info():
435 def hg_info():
433 from rhodecode.lib.vcs.backends import hg
436 from rhodecode.lib.vcs.backends import hg
434 state = STATE_OK_DEFAULT
437 state = STATE_OK_DEFAULT
435 value = human_value = ''
438 value = human_value = ''
436 try:
439 try:
437 value = hg.discover_hg_version(raise_on_exc=True)
440 value = hg.discover_hg_version(raise_on_exc=True)
438 human_value = 'version reported from VCSServer: {}'.format(value)
441 human_value = 'version reported from VCSServer: {}'.format(value)
439 except Exception as e:
442 except Exception as e:
440 state = {'message': str(e), 'type': STATE_ERR}
443 state = {'message': str(e), 'type': STATE_ERR}
441 return SysInfoRes(value=value, state=state, human_value=human_value)
444 return SysInfoRes(value=value, state=state, human_value=human_value)
442
445
443
446
444 def svn_info():
447 def svn_info():
445 from rhodecode.lib.vcs.backends import svn
448 from rhodecode.lib.vcs.backends import svn
446 state = STATE_OK_DEFAULT
449 state = STATE_OK_DEFAULT
447 value = human_value = ''
450 value = human_value = ''
448 try:
451 try:
449 value = svn.discover_svn_version(raise_on_exc=True)
452 value = svn.discover_svn_version(raise_on_exc=True)
450 human_value = 'version reported from VCSServer: {}'.format(value)
453 human_value = 'version reported from VCSServer: {}'.format(value)
451 except Exception as e:
454 except Exception as e:
452 state = {'message': str(e), 'type': STATE_ERR}
455 state = {'message': str(e), 'type': STATE_ERR}
453 return SysInfoRes(value=value, state=state, human_value=human_value)
456 return SysInfoRes(value=value, state=state, human_value=human_value)
454
457
455
458
456 def vcs_backends():
459 def vcs_backends():
457 import rhodecode
460 import rhodecode
458 value = map(
461 value = map(
459 string.strip, rhodecode.CONFIG.get('vcs.backends', '').split(','))
462 string.strip, rhodecode.CONFIG.get('vcs.backends', '').split(','))
460 human_value = 'Enabled backends in order: {}'.format(','.join(value))
463 human_value = 'Enabled backends in order: {}'.format(','.join(value))
461 return SysInfoRes(value=value, human_value=human_value)
464 return SysInfoRes(value=value, human_value=human_value)
462
465
463
466
464 def vcs_server():
467 def vcs_server():
465 import rhodecode
468 import rhodecode
466 from rhodecode.lib.vcs.backends import get_vcsserver_version
469 from rhodecode.lib.vcs.backends import get_vcsserver_version
467
470
468 server_url = rhodecode.CONFIG.get('vcs.server')
471 server_url = rhodecode.CONFIG.get('vcs.server')
469 enabled = rhodecode.CONFIG.get('vcs.server.enable')
472 enabled = rhodecode.CONFIG.get('vcs.server.enable')
470 protocol = rhodecode.CONFIG.get('vcs.server.protocol') or 'http'
473 protocol = rhodecode.CONFIG.get('vcs.server.protocol') or 'http'
471 state = STATE_OK_DEFAULT
474 state = STATE_OK_DEFAULT
472 version = None
475 version = None
473
476
474 try:
477 try:
475 version = get_vcsserver_version()
478 version = get_vcsserver_version()
476 connection = 'connected'
479 connection = 'connected'
477 except Exception as e:
480 except Exception as e:
478 connection = 'failed'
481 connection = 'failed'
479 state = {'message': str(e), 'type': STATE_ERR}
482 state = {'message': str(e), 'type': STATE_ERR}
480
483
481 value = dict(
484 value = dict(
482 url=server_url,
485 url=server_url,
483 enabled=enabled,
486 enabled=enabled,
484 protocol=protocol,
487 protocol=protocol,
485 connection=connection,
488 connection=connection,
486 version=version,
489 version=version,
487 text='',
490 text='',
488 )
491 )
489
492
490 human_value = value.copy()
493 human_value = value.copy()
491 human_value['text'] = \
494 human_value['text'] = \
492 '{url}@ver:{ver} via {mode} mode, connection:{conn}'.format(
495 '{url}@ver:{ver} via {mode} mode, connection:{conn}'.format(
493 url=server_url, ver=version, mode=protocol, conn=connection)
496 url=server_url, ver=version, mode=protocol, conn=connection)
494
497
495 return SysInfoRes(value=value, state=state, human_value=human_value)
498 return SysInfoRes(value=value, state=state, human_value=human_value)
496
499
497
500
498 def rhodecode_app_info():
501 def rhodecode_app_info():
499 import rhodecode
502 import rhodecode
500 edition = rhodecode.CONFIG.get('rhodecode.edition')
503 edition = rhodecode.CONFIG.get('rhodecode.edition')
501
504
502 value = dict(
505 value = dict(
503 rhodecode_version=rhodecode.__version__,
506 rhodecode_version=rhodecode.__version__,
504 rhodecode_lib_path=os.path.abspath(rhodecode.__file__),
507 rhodecode_lib_path=os.path.abspath(rhodecode.__file__),
505 text=''
508 text=''
506 )
509 )
507 human_value = value.copy()
510 human_value = value.copy()
508 human_value['text'] = 'RhodeCode {edition}, version {ver}'.format(
511 human_value['text'] = 'RhodeCode {edition}, version {ver}'.format(
509 edition=edition, ver=value['rhodecode_version']
512 edition=edition, ver=value['rhodecode_version']
510 )
513 )
511 return SysInfoRes(value=value, human_value=human_value)
514 return SysInfoRes(value=value, human_value=human_value)
512
515
513
516
514 def rhodecode_config():
517 def rhodecode_config():
515 import rhodecode
518 import rhodecode
516 path = rhodecode.CONFIG.get('__file__')
519 path = rhodecode.CONFIG.get('__file__')
517 rhodecode_ini_safe = rhodecode.CONFIG.copy()
520 rhodecode_ini_safe = rhodecode.CONFIG.copy()
518
521
519 blacklist = [
522 blacklist = [
520 'rhodecode_license_key',
523 'rhodecode_license_key',
521 'routes.map',
524 'routes.map',
522 'pylons.h',
525 'pylons.h',
523 'pylons.app_globals',
526 'pylons.app_globals',
524 'pylons.environ_config',
527 'pylons.environ_config',
525 'sqlalchemy.db1.url',
528 'sqlalchemy.db1.url',
526 'channelstream.secret',
529 'channelstream.secret',
527 'beaker.session.secret',
530 'beaker.session.secret',
528 'rhodecode.encrypted_values.secret',
531 'rhodecode.encrypted_values.secret',
529 'rhodecode_auth_github_consumer_key',
532 'rhodecode_auth_github_consumer_key',
530 'rhodecode_auth_github_consumer_secret',
533 'rhodecode_auth_github_consumer_secret',
531 'rhodecode_auth_google_consumer_key',
534 'rhodecode_auth_google_consumer_key',
532 'rhodecode_auth_google_consumer_secret',
535 'rhodecode_auth_google_consumer_secret',
533 'rhodecode_auth_bitbucket_consumer_secret',
536 'rhodecode_auth_bitbucket_consumer_secret',
534 'rhodecode_auth_bitbucket_consumer_key',
537 'rhodecode_auth_bitbucket_consumer_key',
535 'rhodecode_auth_twitter_consumer_secret',
538 'rhodecode_auth_twitter_consumer_secret',
536 'rhodecode_auth_twitter_consumer_key',
539 'rhodecode_auth_twitter_consumer_key',
537
540
538 'rhodecode_auth_twitter_secret',
541 'rhodecode_auth_twitter_secret',
539 'rhodecode_auth_github_secret',
542 'rhodecode_auth_github_secret',
540 'rhodecode_auth_google_secret',
543 'rhodecode_auth_google_secret',
541 'rhodecode_auth_bitbucket_secret',
544 'rhodecode_auth_bitbucket_secret',
542
545
543 'appenlight.api_key',
546 'appenlight.api_key',
544 ('app_conf', 'sqlalchemy.db1.url')
547 ('app_conf', 'sqlalchemy.db1.url')
545 ]
548 ]
546 for k in blacklist:
549 for k in blacklist:
547 if isinstance(k, tuple):
550 if isinstance(k, tuple):
548 section, key = k
551 section, key = k
549 if section in rhodecode_ini_safe:
552 if section in rhodecode_ini_safe:
550 rhodecode_ini_safe[section] = '**OBFUSCATED**'
553 rhodecode_ini_safe[section] = '**OBFUSCATED**'
551 else:
554 else:
552 rhodecode_ini_safe.pop(k, None)
555 rhodecode_ini_safe.pop(k, None)
553
556
554 # TODO: maybe put some CONFIG checks here ?
557 # TODO: maybe put some CONFIG checks here ?
555 return SysInfoRes(value={'config': rhodecode_ini_safe, 'path': path})
558 return SysInfoRes(value={'config': rhodecode_ini_safe, 'path': path})
556
559
557
560
558 def database_info():
561 def database_info():
559 import rhodecode
562 import rhodecode
560 from sqlalchemy.engine import url as engine_url
563 from sqlalchemy.engine import url as engine_url
561 from rhodecode.model.meta import Base as sql_base, Session
564 from rhodecode.model.meta import Base as sql_base, Session
562 from rhodecode.model.db import DbMigrateVersion
565 from rhodecode.model.db import DbMigrateVersion
563
566
564 state = STATE_OK_DEFAULT
567 state = STATE_OK_DEFAULT
565
568
566 db_migrate = DbMigrateVersion.query().filter(
569 db_migrate = DbMigrateVersion.query().filter(
567 DbMigrateVersion.repository_id == 'rhodecode_db_migrations').one()
570 DbMigrateVersion.repository_id == 'rhodecode_db_migrations').one()
568
571
569 db_url_obj = engine_url.make_url(rhodecode.CONFIG['sqlalchemy.db1.url'])
572 db_url_obj = engine_url.make_url(rhodecode.CONFIG['sqlalchemy.db1.url'])
570
573
571 try:
574 try:
572 engine = sql_base.metadata.bind
575 engine = sql_base.metadata.bind
573 db_server_info = engine.dialect._get_server_version_info(
576 db_server_info = engine.dialect._get_server_version_info(
574 Session.connection(bind=engine))
577 Session.connection(bind=engine))
575 db_version = '.'.join(map(str, db_server_info))
578 db_version = '.'.join(map(str, db_server_info))
576 except Exception:
579 except Exception:
577 log.exception('failed to fetch db version')
580 log.exception('failed to fetch db version')
578 db_version = 'UNKNOWN'
581 db_version = 'UNKNOWN'
579
582
580 db_info = dict(
583 db_info = dict(
581 migrate_version=db_migrate.version,
584 migrate_version=db_migrate.version,
582 type=db_url_obj.get_backend_name(),
585 type=db_url_obj.get_backend_name(),
583 version=db_version,
586 version=db_version,
584 url=repr(db_url_obj)
587 url=repr(db_url_obj)
585 )
588 )
586
589
587 human_value = db_info.copy()
590 human_value = db_info.copy()
588 human_value['url'] = "{} @ migration version: {}".format(
591 human_value['url'] = "{} @ migration version: {}".format(
589 db_info['url'], db_info['migrate_version'])
592 db_info['url'], db_info['migrate_version'])
590 human_value['version'] = "{} {}".format(db_info['type'], db_info['version'])
593 human_value['version'] = "{} {}".format(db_info['type'], db_info['version'])
591 return SysInfoRes(value=db_info, state=state, human_value=human_value)
594 return SysInfoRes(value=db_info, state=state, human_value=human_value)
592
595
593
596
594 def server_info(environ):
597 def server_info(environ):
595 import rhodecode
598 import rhodecode
596 from rhodecode.lib.base import get_server_ip_addr, get_server_port
599 from rhodecode.lib.base import get_server_ip_addr, get_server_port
597
600
598 value = {
601 value = {
599 'server_ip': '%s:%s' % (
602 'server_ip': '%s:%s' % (
600 get_server_ip_addr(environ, log_errors=False),
603 get_server_ip_addr(environ, log_errors=False),
601 get_server_port(environ)
604 get_server_port(environ)
602 ),
605 ),
603 'server_id': rhodecode.CONFIG.get('instance_id'),
606 'server_id': rhodecode.CONFIG.get('instance_id'),
604 }
607 }
605 return SysInfoRes(value=value)
608 return SysInfoRes(value=value)
606
609
607
610
608 def get_system_info(environ):
611 def get_system_info(environ):
609 environ = environ or {}
612 environ = environ or {}
610 return {
613 return {
611 'rhodecode_app': SysInfo(rhodecode_app_info)(),
614 'rhodecode_app': SysInfo(rhodecode_app_info)(),
612 'rhodecode_config': SysInfo(rhodecode_config)(),
615 'rhodecode_config': SysInfo(rhodecode_config)(),
613 'python': SysInfo(python_info)(),
616 'python': SysInfo(python_info)(),
614 'py_modules': SysInfo(py_modules)(),
617 'py_modules': SysInfo(py_modules)(),
615
618
616 'platform': SysInfo(platform_type)(),
619 'platform': SysInfo(platform_type)(),
617 'server': SysInfo(server_info, environ=environ)(),
620 'server': SysInfo(server_info, environ=environ)(),
618 'database': SysInfo(database_info)(),
621 'database': SysInfo(database_info)(),
619
622
620 'storage': SysInfo(storage)(),
623 'storage': SysInfo(storage)(),
621 'storage_inodes': SysInfo(storage_inodes)(),
624 'storage_inodes': SysInfo(storage_inodes)(),
622 'storage_archive': SysInfo(storage_archives)(),
625 'storage_archive': SysInfo(storage_archives)(),
623 'storage_gist': SysInfo(storage_gist)(),
626 'storage_gist': SysInfo(storage_gist)(),
624 'storage_temp': SysInfo(storage_temp)(),
627 'storage_temp': SysInfo(storage_temp)(),
625
628
626 'search': SysInfo(search_info)(),
629 'search': SysInfo(search_info)(),
627
630
628 'uptime': SysInfo(uptime)(),
631 'uptime': SysInfo(uptime)(),
629 'load': SysInfo(machine_load)(),
632 'load': SysInfo(machine_load)(),
630 'cpu': SysInfo(cpu)(),
633 'cpu': SysInfo(cpu)(),
631 'memory': SysInfo(memory)(),
634 'memory': SysInfo(memory)(),
632
635
633 'vcs_backends': SysInfo(vcs_backends)(),
636 'vcs_backends': SysInfo(vcs_backends)(),
634 'vcs_server': SysInfo(vcs_server)(),
637 'vcs_server': SysInfo(vcs_server)(),
635
638
636 'git': SysInfo(git_info)(),
639 'git': SysInfo(git_info)(),
637 'hg': SysInfo(hg_info)(),
640 'hg': SysInfo(hg_info)(),
638 'svn': SysInfo(svn_info)(),
641 'svn': SysInfo(svn_info)(),
639 }
642 }
@@ -1,3818 +1,3823 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 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 re
26 import os
26 import os
27 import time
27 import time
28 import hashlib
28 import hashlib
29 import logging
29 import logging
30 import datetime
30 import datetime
31 import warnings
31 import warnings
32 import ipaddress
32 import ipaddress
33 import functools
33 import functools
34 import traceback
34 import traceback
35 import collections
35 import collections
36
36
37
37
38 from sqlalchemy import *
38 from sqlalchemy import *
39 from sqlalchemy.ext.declarative import declared_attr
39 from sqlalchemy.ext.declarative import declared_attr
40 from sqlalchemy.ext.hybrid import hybrid_property
40 from sqlalchemy.ext.hybrid import hybrid_property
41 from sqlalchemy.orm import (
41 from sqlalchemy.orm import (
42 relationship, joinedload, class_mapper, validates, aliased)
42 relationship, joinedload, class_mapper, validates, aliased)
43 from sqlalchemy.sql.expression import true
43 from sqlalchemy.sql.expression import true
44 from beaker.cache import cache_region
44 from beaker.cache import cache_region
45 from webob.exc import HTTPNotFound
45 from webob.exc import HTTPNotFound
46 from zope.cachedescriptors.property import Lazy as LazyProperty
46 from zope.cachedescriptors.property import Lazy as LazyProperty
47
47
48 from pylons import url
48 from pylons import url
49 from pylons.i18n.translation import lazy_ugettext as _
49 from pylons.i18n.translation import lazy_ugettext as _
50
50
51 from rhodecode.lib.vcs import get_vcs_instance
51 from rhodecode.lib.vcs import get_vcs_instance
52 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
52 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
53 from rhodecode.lib.utils2 import (
53 from rhodecode.lib.utils2 import (
54 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
54 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
55 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
55 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
56 glob2re, StrictAttributeDict)
56 glob2re, StrictAttributeDict)
57 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType
57 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType
58 from rhodecode.lib.ext_json import json
58 from rhodecode.lib.ext_json import json
59 from rhodecode.lib.caching_query import FromCache
59 from rhodecode.lib.caching_query import FromCache
60 from rhodecode.lib.encrypt import AESCipher
60 from rhodecode.lib.encrypt import AESCipher
61
61
62 from rhodecode.model.meta import Base, Session
62 from rhodecode.model.meta import Base, Session
63
63
64 URL_SEP = '/'
64 URL_SEP = '/'
65 log = logging.getLogger(__name__)
65 log = logging.getLogger(__name__)
66
66
67 # =============================================================================
67 # =============================================================================
68 # BASE CLASSES
68 # BASE CLASSES
69 # =============================================================================
69 # =============================================================================
70
70
71 # this is propagated from .ini file rhodecode.encrypted_values.secret or
71 # this is propagated from .ini file rhodecode.encrypted_values.secret or
72 # beaker.session.secret if first is not set.
72 # beaker.session.secret if first is not set.
73 # and initialized at environment.py
73 # and initialized at environment.py
74 ENCRYPTION_KEY = None
74 ENCRYPTION_KEY = None
75
75
76 # used to sort permissions by types, '#' used here is not allowed to be in
76 # used to sort permissions by types, '#' used here is not allowed to be in
77 # usernames, and it's very early in sorted string.printable table.
77 # usernames, and it's very early in sorted string.printable table.
78 PERMISSION_TYPE_SORT = {
78 PERMISSION_TYPE_SORT = {
79 'admin': '####',
79 'admin': '####',
80 'write': '###',
80 'write': '###',
81 'read': '##',
81 'read': '##',
82 'none': '#',
82 'none': '#',
83 }
83 }
84
84
85
85
86 def display_sort(obj):
86 def display_sort(obj):
87 """
87 """
88 Sort function used to sort permissions in .permissions() function of
88 Sort function used to sort permissions in .permissions() function of
89 Repository, RepoGroup, UserGroup. Also it put the default user in front
89 Repository, RepoGroup, UserGroup. Also it put the default user in front
90 of all other resources
90 of all other resources
91 """
91 """
92
92
93 if obj.username == User.DEFAULT_USER:
93 if obj.username == User.DEFAULT_USER:
94 return '#####'
94 return '#####'
95 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
95 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
96 return prefix + obj.username
96 return prefix + obj.username
97
97
98
98
99 def _hash_key(k):
99 def _hash_key(k):
100 return md5_safe(k)
100 return md5_safe(k)
101
101
102
102
103 class EncryptedTextValue(TypeDecorator):
103 class EncryptedTextValue(TypeDecorator):
104 """
104 """
105 Special column for encrypted long text data, use like::
105 Special column for encrypted long text data, use like::
106
106
107 value = Column("encrypted_value", EncryptedValue(), nullable=False)
107 value = Column("encrypted_value", EncryptedValue(), nullable=False)
108
108
109 This column is intelligent so if value is in unencrypted form it return
109 This column is intelligent so if value is in unencrypted form it return
110 unencrypted form, but on save it always encrypts
110 unencrypted form, but on save it always encrypts
111 """
111 """
112 impl = Text
112 impl = Text
113
113
114 def process_bind_param(self, value, dialect):
114 def process_bind_param(self, value, dialect):
115 if not value:
115 if not value:
116 return value
116 return value
117 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
117 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
118 # protect against double encrypting if someone manually starts
118 # protect against double encrypting if someone manually starts
119 # doing
119 # doing
120 raise ValueError('value needs to be in unencrypted format, ie. '
120 raise ValueError('value needs to be in unencrypted format, ie. '
121 'not starting with enc$aes')
121 'not starting with enc$aes')
122 return 'enc$aes_hmac$%s' % AESCipher(
122 return 'enc$aes_hmac$%s' % AESCipher(
123 ENCRYPTION_KEY, hmac=True).encrypt(value)
123 ENCRYPTION_KEY, hmac=True).encrypt(value)
124
124
125 def process_result_value(self, value, dialect):
125 def process_result_value(self, value, dialect):
126 import rhodecode
126 import rhodecode
127
127
128 if not value:
128 if not value:
129 return value
129 return value
130
130
131 parts = value.split('$', 3)
131 parts = value.split('$', 3)
132 if not len(parts) == 3:
132 if not len(parts) == 3:
133 # probably not encrypted values
133 # probably not encrypted values
134 return value
134 return value
135 else:
135 else:
136 if parts[0] != 'enc':
136 if parts[0] != 'enc':
137 # parts ok but without our header ?
137 # parts ok but without our header ?
138 return value
138 return value
139 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
139 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
140 'rhodecode.encrypted_values.strict') or True)
140 'rhodecode.encrypted_values.strict') or True)
141 # at that stage we know it's our encryption
141 # at that stage we know it's our encryption
142 if parts[1] == 'aes':
142 if parts[1] == 'aes':
143 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
143 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
144 elif parts[1] == 'aes_hmac':
144 elif parts[1] == 'aes_hmac':
145 decrypted_data = AESCipher(
145 decrypted_data = AESCipher(
146 ENCRYPTION_KEY, hmac=True,
146 ENCRYPTION_KEY, hmac=True,
147 strict_verification=enc_strict_mode).decrypt(parts[2])
147 strict_verification=enc_strict_mode).decrypt(parts[2])
148 else:
148 else:
149 raise ValueError(
149 raise ValueError(
150 'Encryption type part is wrong, must be `aes` '
150 'Encryption type part is wrong, must be `aes` '
151 'or `aes_hmac`, got `%s` instead' % (parts[1]))
151 'or `aes_hmac`, got `%s` instead' % (parts[1]))
152 return decrypted_data
152 return decrypted_data
153
153
154
154
155 class BaseModel(object):
155 class BaseModel(object):
156 """
156 """
157 Base Model for all classes
157 Base Model for all classes
158 """
158 """
159
159
160 @classmethod
160 @classmethod
161 def _get_keys(cls):
161 def _get_keys(cls):
162 """return column names for this model """
162 """return column names for this model """
163 return class_mapper(cls).c.keys()
163 return class_mapper(cls).c.keys()
164
164
165 def get_dict(self):
165 def get_dict(self):
166 """
166 """
167 return dict with keys and values corresponding
167 return dict with keys and values corresponding
168 to this model data """
168 to this model data """
169
169
170 d = {}
170 d = {}
171 for k in self._get_keys():
171 for k in self._get_keys():
172 d[k] = getattr(self, k)
172 d[k] = getattr(self, k)
173
173
174 # also use __json__() if present to get additional fields
174 # also use __json__() if present to get additional fields
175 _json_attr = getattr(self, '__json__', None)
175 _json_attr = getattr(self, '__json__', None)
176 if _json_attr:
176 if _json_attr:
177 # update with attributes from __json__
177 # update with attributes from __json__
178 if callable(_json_attr):
178 if callable(_json_attr):
179 _json_attr = _json_attr()
179 _json_attr = _json_attr()
180 for k, val in _json_attr.iteritems():
180 for k, val in _json_attr.iteritems():
181 d[k] = val
181 d[k] = val
182 return d
182 return d
183
183
184 def get_appstruct(self):
184 def get_appstruct(self):
185 """return list with keys and values tuples corresponding
185 """return list with keys and values tuples corresponding
186 to this model data """
186 to this model data """
187
187
188 l = []
188 l = []
189 for k in self._get_keys():
189 for k in self._get_keys():
190 l.append((k, getattr(self, k),))
190 l.append((k, getattr(self, k),))
191 return l
191 return l
192
192
193 def populate_obj(self, populate_dict):
193 def populate_obj(self, populate_dict):
194 """populate model with data from given populate_dict"""
194 """populate model with data from given populate_dict"""
195
195
196 for k in self._get_keys():
196 for k in self._get_keys():
197 if k in populate_dict:
197 if k in populate_dict:
198 setattr(self, k, populate_dict[k])
198 setattr(self, k, populate_dict[k])
199
199
200 @classmethod
200 @classmethod
201 def query(cls):
201 def query(cls):
202 return Session().query(cls)
202 return Session().query(cls)
203
203
204 @classmethod
204 @classmethod
205 def get(cls, id_):
205 def get(cls, id_):
206 if id_:
206 if id_:
207 return cls.query().get(id_)
207 return cls.query().get(id_)
208
208
209 @classmethod
209 @classmethod
210 def get_or_404(cls, id_):
210 def get_or_404(cls, id_):
211 try:
211 try:
212 id_ = int(id_)
212 id_ = int(id_)
213 except (TypeError, ValueError):
213 except (TypeError, ValueError):
214 raise HTTPNotFound
214 raise HTTPNotFound
215
215
216 res = cls.query().get(id_)
216 res = cls.query().get(id_)
217 if not res:
217 if not res:
218 raise HTTPNotFound
218 raise HTTPNotFound
219 return res
219 return res
220
220
221 @classmethod
221 @classmethod
222 def getAll(cls):
222 def getAll(cls):
223 # deprecated and left for backward compatibility
223 # deprecated and left for backward compatibility
224 return cls.get_all()
224 return cls.get_all()
225
225
226 @classmethod
226 @classmethod
227 def get_all(cls):
227 def get_all(cls):
228 return cls.query().all()
228 return cls.query().all()
229
229
230 @classmethod
230 @classmethod
231 def delete(cls, id_):
231 def delete(cls, id_):
232 obj = cls.query().get(id_)
232 obj = cls.query().get(id_)
233 Session().delete(obj)
233 Session().delete(obj)
234
234
235 @classmethod
235 @classmethod
236 def identity_cache(cls, session, attr_name, value):
236 def identity_cache(cls, session, attr_name, value):
237 exist_in_session = []
237 exist_in_session = []
238 for (item_cls, pkey), instance in session.identity_map.items():
238 for (item_cls, pkey), instance in session.identity_map.items():
239 if cls == item_cls and getattr(instance, attr_name) == value:
239 if cls == item_cls and getattr(instance, attr_name) == value:
240 exist_in_session.append(instance)
240 exist_in_session.append(instance)
241 if exist_in_session:
241 if exist_in_session:
242 if len(exist_in_session) == 1:
242 if len(exist_in_session) == 1:
243 return exist_in_session[0]
243 return exist_in_session[0]
244 log.exception(
244 log.exception(
245 'multiple objects with attr %s and '
245 'multiple objects with attr %s and '
246 'value %s found with same name: %r',
246 'value %s found with same name: %r',
247 attr_name, value, exist_in_session)
247 attr_name, value, exist_in_session)
248
248
249 def __repr__(self):
249 def __repr__(self):
250 if hasattr(self, '__unicode__'):
250 if hasattr(self, '__unicode__'):
251 # python repr needs to return str
251 # python repr needs to return str
252 try:
252 try:
253 return safe_str(self.__unicode__())
253 return safe_str(self.__unicode__())
254 except UnicodeDecodeError:
254 except UnicodeDecodeError:
255 pass
255 pass
256 return '<DB:%s>' % (self.__class__.__name__)
256 return '<DB:%s>' % (self.__class__.__name__)
257
257
258
258
259 class RhodeCodeSetting(Base, BaseModel):
259 class RhodeCodeSetting(Base, BaseModel):
260 __tablename__ = 'rhodecode_settings'
260 __tablename__ = 'rhodecode_settings'
261 __table_args__ = (
261 __table_args__ = (
262 UniqueConstraint('app_settings_name'),
262 UniqueConstraint('app_settings_name'),
263 {'extend_existing': True, 'mysql_engine': 'InnoDB',
263 {'extend_existing': True, 'mysql_engine': 'InnoDB',
264 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
264 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
265 )
265 )
266
266
267 SETTINGS_TYPES = {
267 SETTINGS_TYPES = {
268 'str': safe_str,
268 'str': safe_str,
269 'int': safe_int,
269 'int': safe_int,
270 'unicode': safe_unicode,
270 'unicode': safe_unicode,
271 'bool': str2bool,
271 'bool': str2bool,
272 'list': functools.partial(aslist, sep=',')
272 'list': functools.partial(aslist, sep=',')
273 }
273 }
274 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
274 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
275 GLOBAL_CONF_KEY = 'app_settings'
275 GLOBAL_CONF_KEY = 'app_settings'
276
276
277 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
277 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
278 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
278 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
279 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
279 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
280 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
280 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
281
281
282 def __init__(self, key='', val='', type='unicode'):
282 def __init__(self, key='', val='', type='unicode'):
283 self.app_settings_name = key
283 self.app_settings_name = key
284 self.app_settings_type = type
284 self.app_settings_type = type
285 self.app_settings_value = val
285 self.app_settings_value = val
286
286
287 @validates('_app_settings_value')
287 @validates('_app_settings_value')
288 def validate_settings_value(self, key, val):
288 def validate_settings_value(self, key, val):
289 assert type(val) == unicode
289 assert type(val) == unicode
290 return val
290 return val
291
291
292 @hybrid_property
292 @hybrid_property
293 def app_settings_value(self):
293 def app_settings_value(self):
294 v = self._app_settings_value
294 v = self._app_settings_value
295 _type = self.app_settings_type
295 _type = self.app_settings_type
296 if _type:
296 if _type:
297 _type = self.app_settings_type.split('.')[0]
297 _type = self.app_settings_type.split('.')[0]
298 # decode the encrypted value
298 # decode the encrypted value
299 if 'encrypted' in self.app_settings_type:
299 if 'encrypted' in self.app_settings_type:
300 cipher = EncryptedTextValue()
300 cipher = EncryptedTextValue()
301 v = safe_unicode(cipher.process_result_value(v, None))
301 v = safe_unicode(cipher.process_result_value(v, None))
302
302
303 converter = self.SETTINGS_TYPES.get(_type) or \
303 converter = self.SETTINGS_TYPES.get(_type) or \
304 self.SETTINGS_TYPES['unicode']
304 self.SETTINGS_TYPES['unicode']
305 return converter(v)
305 return converter(v)
306
306
307 @app_settings_value.setter
307 @app_settings_value.setter
308 def app_settings_value(self, val):
308 def app_settings_value(self, val):
309 """
309 """
310 Setter that will always make sure we use unicode in app_settings_value
310 Setter that will always make sure we use unicode in app_settings_value
311
311
312 :param val:
312 :param val:
313 """
313 """
314 val = safe_unicode(val)
314 val = safe_unicode(val)
315 # encode the encrypted value
315 # encode the encrypted value
316 if 'encrypted' in self.app_settings_type:
316 if 'encrypted' in self.app_settings_type:
317 cipher = EncryptedTextValue()
317 cipher = EncryptedTextValue()
318 val = safe_unicode(cipher.process_bind_param(val, None))
318 val = safe_unicode(cipher.process_bind_param(val, None))
319 self._app_settings_value = val
319 self._app_settings_value = val
320
320
321 @hybrid_property
321 @hybrid_property
322 def app_settings_type(self):
322 def app_settings_type(self):
323 return self._app_settings_type
323 return self._app_settings_type
324
324
325 @app_settings_type.setter
325 @app_settings_type.setter
326 def app_settings_type(self, val):
326 def app_settings_type(self, val):
327 if val.split('.')[0] not in self.SETTINGS_TYPES:
327 if val.split('.')[0] not in self.SETTINGS_TYPES:
328 raise Exception('type must be one of %s got %s'
328 raise Exception('type must be one of %s got %s'
329 % (self.SETTINGS_TYPES.keys(), val))
329 % (self.SETTINGS_TYPES.keys(), val))
330 self._app_settings_type = val
330 self._app_settings_type = val
331
331
332 def __unicode__(self):
332 def __unicode__(self):
333 return u"<%s('%s:%s[%s]')>" % (
333 return u"<%s('%s:%s[%s]')>" % (
334 self.__class__.__name__,
334 self.__class__.__name__,
335 self.app_settings_name, self.app_settings_value,
335 self.app_settings_name, self.app_settings_value,
336 self.app_settings_type
336 self.app_settings_type
337 )
337 )
338
338
339
339
340 class RhodeCodeUi(Base, BaseModel):
340 class RhodeCodeUi(Base, BaseModel):
341 __tablename__ = 'rhodecode_ui'
341 __tablename__ = 'rhodecode_ui'
342 __table_args__ = (
342 __table_args__ = (
343 UniqueConstraint('ui_key'),
343 UniqueConstraint('ui_key'),
344 {'extend_existing': True, 'mysql_engine': 'InnoDB',
344 {'extend_existing': True, 'mysql_engine': 'InnoDB',
345 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
345 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
346 )
346 )
347
347
348 HOOK_REPO_SIZE = 'changegroup.repo_size'
348 HOOK_REPO_SIZE = 'changegroup.repo_size'
349 # HG
349 # HG
350 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
350 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
351 HOOK_PULL = 'outgoing.pull_logger'
351 HOOK_PULL = 'outgoing.pull_logger'
352 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
352 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
353 HOOK_PUSH = 'changegroup.push_logger'
353 HOOK_PUSH = 'changegroup.push_logger'
354
354
355 # TODO: johbo: Unify way how hooks are configured for git and hg,
355 # TODO: johbo: Unify way how hooks are configured for git and hg,
356 # git part is currently hardcoded.
356 # git part is currently hardcoded.
357
357
358 # SVN PATTERNS
358 # SVN PATTERNS
359 SVN_BRANCH_ID = 'vcs_svn_branch'
359 SVN_BRANCH_ID = 'vcs_svn_branch'
360 SVN_TAG_ID = 'vcs_svn_tag'
360 SVN_TAG_ID = 'vcs_svn_tag'
361
361
362 ui_id = Column(
362 ui_id = Column(
363 "ui_id", Integer(), nullable=False, unique=True, default=None,
363 "ui_id", Integer(), nullable=False, unique=True, default=None,
364 primary_key=True)
364 primary_key=True)
365 ui_section = Column(
365 ui_section = Column(
366 "ui_section", String(255), nullable=True, unique=None, default=None)
366 "ui_section", String(255), nullable=True, unique=None, default=None)
367 ui_key = Column(
367 ui_key = Column(
368 "ui_key", String(255), nullable=True, unique=None, default=None)
368 "ui_key", String(255), nullable=True, unique=None, default=None)
369 ui_value = Column(
369 ui_value = Column(
370 "ui_value", String(255), nullable=True, unique=None, default=None)
370 "ui_value", String(255), nullable=True, unique=None, default=None)
371 ui_active = Column(
371 ui_active = Column(
372 "ui_active", Boolean(), nullable=True, unique=None, default=True)
372 "ui_active", Boolean(), nullable=True, unique=None, default=True)
373
373
374 def __repr__(self):
374 def __repr__(self):
375 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
375 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
376 self.ui_key, self.ui_value)
376 self.ui_key, self.ui_value)
377
377
378
378
379 class RepoRhodeCodeSetting(Base, BaseModel):
379 class RepoRhodeCodeSetting(Base, BaseModel):
380 __tablename__ = 'repo_rhodecode_settings'
380 __tablename__ = 'repo_rhodecode_settings'
381 __table_args__ = (
381 __table_args__ = (
382 UniqueConstraint(
382 UniqueConstraint(
383 'app_settings_name', 'repository_id',
383 'app_settings_name', 'repository_id',
384 name='uq_repo_rhodecode_setting_name_repo_id'),
384 name='uq_repo_rhodecode_setting_name_repo_id'),
385 {'extend_existing': True, 'mysql_engine': 'InnoDB',
385 {'extend_existing': True, 'mysql_engine': 'InnoDB',
386 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
386 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
387 )
387 )
388
388
389 repository_id = Column(
389 repository_id = Column(
390 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
390 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
391 nullable=False)
391 nullable=False)
392 app_settings_id = Column(
392 app_settings_id = Column(
393 "app_settings_id", Integer(), nullable=False, unique=True,
393 "app_settings_id", Integer(), nullable=False, unique=True,
394 default=None, primary_key=True)
394 default=None, primary_key=True)
395 app_settings_name = Column(
395 app_settings_name = Column(
396 "app_settings_name", String(255), nullable=True, unique=None,
396 "app_settings_name", String(255), nullable=True, unique=None,
397 default=None)
397 default=None)
398 _app_settings_value = Column(
398 _app_settings_value = Column(
399 "app_settings_value", String(4096), nullable=True, unique=None,
399 "app_settings_value", String(4096), nullable=True, unique=None,
400 default=None)
400 default=None)
401 _app_settings_type = Column(
401 _app_settings_type = Column(
402 "app_settings_type", String(255), nullable=True, unique=None,
402 "app_settings_type", String(255), nullable=True, unique=None,
403 default=None)
403 default=None)
404
404
405 repository = relationship('Repository')
405 repository = relationship('Repository')
406
406
407 def __init__(self, repository_id, key='', val='', type='unicode'):
407 def __init__(self, repository_id, key='', val='', type='unicode'):
408 self.repository_id = repository_id
408 self.repository_id = repository_id
409 self.app_settings_name = key
409 self.app_settings_name = key
410 self.app_settings_type = type
410 self.app_settings_type = type
411 self.app_settings_value = val
411 self.app_settings_value = val
412
412
413 @validates('_app_settings_value')
413 @validates('_app_settings_value')
414 def validate_settings_value(self, key, val):
414 def validate_settings_value(self, key, val):
415 assert type(val) == unicode
415 assert type(val) == unicode
416 return val
416 return val
417
417
418 @hybrid_property
418 @hybrid_property
419 def app_settings_value(self):
419 def app_settings_value(self):
420 v = self._app_settings_value
420 v = self._app_settings_value
421 type_ = self.app_settings_type
421 type_ = self.app_settings_type
422 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
422 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
423 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
423 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
424 return converter(v)
424 return converter(v)
425
425
426 @app_settings_value.setter
426 @app_settings_value.setter
427 def app_settings_value(self, val):
427 def app_settings_value(self, val):
428 """
428 """
429 Setter that will always make sure we use unicode in app_settings_value
429 Setter that will always make sure we use unicode in app_settings_value
430
430
431 :param val:
431 :param val:
432 """
432 """
433 self._app_settings_value = safe_unicode(val)
433 self._app_settings_value = safe_unicode(val)
434
434
435 @hybrid_property
435 @hybrid_property
436 def app_settings_type(self):
436 def app_settings_type(self):
437 return self._app_settings_type
437 return self._app_settings_type
438
438
439 @app_settings_type.setter
439 @app_settings_type.setter
440 def app_settings_type(self, val):
440 def app_settings_type(self, val):
441 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
441 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
442 if val not in SETTINGS_TYPES:
442 if val not in SETTINGS_TYPES:
443 raise Exception('type must be one of %s got %s'
443 raise Exception('type must be one of %s got %s'
444 % (SETTINGS_TYPES.keys(), val))
444 % (SETTINGS_TYPES.keys(), val))
445 self._app_settings_type = val
445 self._app_settings_type = val
446
446
447 def __unicode__(self):
447 def __unicode__(self):
448 return u"<%s('%s:%s:%s[%s]')>" % (
448 return u"<%s('%s:%s:%s[%s]')>" % (
449 self.__class__.__name__, self.repository.repo_name,
449 self.__class__.__name__, self.repository.repo_name,
450 self.app_settings_name, self.app_settings_value,
450 self.app_settings_name, self.app_settings_value,
451 self.app_settings_type
451 self.app_settings_type
452 )
452 )
453
453
454
454
455 class RepoRhodeCodeUi(Base, BaseModel):
455 class RepoRhodeCodeUi(Base, BaseModel):
456 __tablename__ = 'repo_rhodecode_ui'
456 __tablename__ = 'repo_rhodecode_ui'
457 __table_args__ = (
457 __table_args__ = (
458 UniqueConstraint(
458 UniqueConstraint(
459 'repository_id', 'ui_section', 'ui_key',
459 'repository_id', 'ui_section', 'ui_key',
460 name='uq_repo_rhodecode_ui_repository_id_section_key'),
460 name='uq_repo_rhodecode_ui_repository_id_section_key'),
461 {'extend_existing': True, 'mysql_engine': 'InnoDB',
461 {'extend_existing': True, 'mysql_engine': 'InnoDB',
462 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
462 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
463 )
463 )
464
464
465 repository_id = Column(
465 repository_id = Column(
466 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
466 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
467 nullable=False)
467 nullable=False)
468 ui_id = Column(
468 ui_id = Column(
469 "ui_id", Integer(), nullable=False, unique=True, default=None,
469 "ui_id", Integer(), nullable=False, unique=True, default=None,
470 primary_key=True)
470 primary_key=True)
471 ui_section = Column(
471 ui_section = Column(
472 "ui_section", String(255), nullable=True, unique=None, default=None)
472 "ui_section", String(255), nullable=True, unique=None, default=None)
473 ui_key = Column(
473 ui_key = Column(
474 "ui_key", String(255), nullable=True, unique=None, default=None)
474 "ui_key", String(255), nullable=True, unique=None, default=None)
475 ui_value = Column(
475 ui_value = Column(
476 "ui_value", String(255), nullable=True, unique=None, default=None)
476 "ui_value", String(255), nullable=True, unique=None, default=None)
477 ui_active = Column(
477 ui_active = Column(
478 "ui_active", Boolean(), nullable=True, unique=None, default=True)
478 "ui_active", Boolean(), nullable=True, unique=None, default=True)
479
479
480 repository = relationship('Repository')
480 repository = relationship('Repository')
481
481
482 def __repr__(self):
482 def __repr__(self):
483 return '<%s[%s:%s]%s=>%s]>' % (
483 return '<%s[%s:%s]%s=>%s]>' % (
484 self.__class__.__name__, self.repository.repo_name,
484 self.__class__.__name__, self.repository.repo_name,
485 self.ui_section, self.ui_key, self.ui_value)
485 self.ui_section, self.ui_key, self.ui_value)
486
486
487
487
488 class User(Base, BaseModel):
488 class User(Base, BaseModel):
489 __tablename__ = 'users'
489 __tablename__ = 'users'
490 __table_args__ = (
490 __table_args__ = (
491 UniqueConstraint('username'), UniqueConstraint('email'),
491 UniqueConstraint('username'), UniqueConstraint('email'),
492 Index('u_username_idx', 'username'),
492 Index('u_username_idx', 'username'),
493 Index('u_email_idx', 'email'),
493 Index('u_email_idx', 'email'),
494 {'extend_existing': True, 'mysql_engine': 'InnoDB',
494 {'extend_existing': True, 'mysql_engine': 'InnoDB',
495 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
495 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
496 )
496 )
497 DEFAULT_USER = 'default'
497 DEFAULT_USER = 'default'
498 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
498 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
499 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
499 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
500
500
501 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
501 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
502 username = Column("username", String(255), nullable=True, unique=None, default=None)
502 username = Column("username", String(255), nullable=True, unique=None, default=None)
503 password = Column("password", String(255), nullable=True, unique=None, default=None)
503 password = Column("password", String(255), nullable=True, unique=None, default=None)
504 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
504 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
505 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
505 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
506 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
506 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
507 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
507 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
508 _email = Column("email", String(255), nullable=True, unique=None, default=None)
508 _email = Column("email", String(255), nullable=True, unique=None, default=None)
509 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
509 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
510 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
510 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
511 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
511 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
512 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
512 api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
513 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
513 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
514 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
514 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
515 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
515 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
516
516
517 user_log = relationship('UserLog')
517 user_log = relationship('UserLog')
518 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
518 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
519
519
520 repositories = relationship('Repository')
520 repositories = relationship('Repository')
521 repository_groups = relationship('RepoGroup')
521 repository_groups = relationship('RepoGroup')
522 user_groups = relationship('UserGroup')
522 user_groups = relationship('UserGroup')
523
523
524 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
524 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
525 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
525 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
526
526
527 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
527 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
528 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
528 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
529 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
529 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
530
530
531 group_member = relationship('UserGroupMember', cascade='all')
531 group_member = relationship('UserGroupMember', cascade='all')
532
532
533 notifications = relationship('UserNotification', cascade='all')
533 notifications = relationship('UserNotification', cascade='all')
534 # notifications assigned to this user
534 # notifications assigned to this user
535 user_created_notifications = relationship('Notification', cascade='all')
535 user_created_notifications = relationship('Notification', cascade='all')
536 # comments created by this user
536 # comments created by this user
537 user_comments = relationship('ChangesetComment', cascade='all')
537 user_comments = relationship('ChangesetComment', cascade='all')
538 # user profile extra info
538 # user profile extra info
539 user_emails = relationship('UserEmailMap', cascade='all')
539 user_emails = relationship('UserEmailMap', cascade='all')
540 user_ip_map = relationship('UserIpMap', cascade='all')
540 user_ip_map = relationship('UserIpMap', cascade='all')
541 user_auth_tokens = relationship('UserApiKeys', cascade='all')
541 user_auth_tokens = relationship('UserApiKeys', cascade='all')
542 # gists
542 # gists
543 user_gists = relationship('Gist', cascade='all')
543 user_gists = relationship('Gist', cascade='all')
544 # user pull requests
544 # user pull requests
545 user_pull_requests = relationship('PullRequest', cascade='all')
545 user_pull_requests = relationship('PullRequest', cascade='all')
546 # external identities
546 # external identities
547 extenal_identities = relationship(
547 extenal_identities = relationship(
548 'ExternalIdentity',
548 'ExternalIdentity',
549 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
549 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
550 cascade='all')
550 cascade='all')
551
551
552 def __unicode__(self):
552 def __unicode__(self):
553 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
553 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
554 self.user_id, self.username)
554 self.user_id, self.username)
555
555
556 @hybrid_property
556 @hybrid_property
557 def email(self):
557 def email(self):
558 return self._email
558 return self._email
559
559
560 @email.setter
560 @email.setter
561 def email(self, val):
561 def email(self, val):
562 self._email = val.lower() if val else None
562 self._email = val.lower() if val else None
563
563
564 @property
564 @property
565 def firstname(self):
565 def firstname(self):
566 # alias for future
566 # alias for future
567 return self.name
567 return self.name
568
568
569 @property
569 @property
570 def emails(self):
570 def emails(self):
571 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
571 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
572 return [self.email] + [x.email for x in other]
572 return [self.email] + [x.email for x in other]
573
573
574 @property
574 @property
575 def auth_tokens(self):
575 def auth_tokens(self):
576 return [self.api_key] + [x.api_key for x in self.extra_auth_tokens]
576 return [self.api_key] + [x.api_key for x in self.extra_auth_tokens]
577
577
578 @property
578 @property
579 def extra_auth_tokens(self):
579 def extra_auth_tokens(self):
580 return UserApiKeys.query().filter(UserApiKeys.user == self).all()
580 return UserApiKeys.query().filter(UserApiKeys.user == self).all()
581
581
582 @property
582 @property
583 def feed_token(self):
583 def feed_token(self):
584 feed_tokens = UserApiKeys.query()\
584 feed_tokens = UserApiKeys.query()\
585 .filter(UserApiKeys.user == self)\
585 .filter(UserApiKeys.user == self)\
586 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
586 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
587 .all()
587 .all()
588 if feed_tokens:
588 if feed_tokens:
589 return feed_tokens[0].api_key
589 return feed_tokens[0].api_key
590 else:
590 else:
591 # use the main token so we don't end up with nothing...
591 # use the main token so we don't end up with nothing...
592 return self.api_key
592 return self.api_key
593
593
594 @classmethod
594 @classmethod
595 def extra_valid_auth_tokens(cls, user, role=None):
595 def extra_valid_auth_tokens(cls, user, role=None):
596 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
596 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
597 .filter(or_(UserApiKeys.expires == -1,
597 .filter(or_(UserApiKeys.expires == -1,
598 UserApiKeys.expires >= time.time()))
598 UserApiKeys.expires >= time.time()))
599 if role:
599 if role:
600 tokens = tokens.filter(or_(UserApiKeys.role == role,
600 tokens = tokens.filter(or_(UserApiKeys.role == role,
601 UserApiKeys.role == UserApiKeys.ROLE_ALL))
601 UserApiKeys.role == UserApiKeys.ROLE_ALL))
602 return tokens.all()
602 return tokens.all()
603
603
604 @property
604 @property
605 def builtin_token_roles(self):
605 def builtin_token_roles(self):
606 return map(UserApiKeys._get_role_name, [
606 return map(UserApiKeys._get_role_name, [
607 UserApiKeys.ROLE_API, UserApiKeys.ROLE_FEED, UserApiKeys.ROLE_HTTP
607 UserApiKeys.ROLE_API, UserApiKeys.ROLE_FEED, UserApiKeys.ROLE_HTTP
608 ])
608 ])
609
609
610 @property
610 @property
611 def ip_addresses(self):
611 def ip_addresses(self):
612 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
612 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
613 return [x.ip_addr for x in ret]
613 return [x.ip_addr for x in ret]
614
614
615 @property
615 @property
616 def username_and_name(self):
616 def username_and_name(self):
617 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
617 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
618
618
619 @property
619 @property
620 def username_or_name_or_email(self):
620 def username_or_name_or_email(self):
621 full_name = self.full_name if self.full_name is not ' ' else None
621 full_name = self.full_name if self.full_name is not ' ' else None
622 return self.username or full_name or self.email
622 return self.username or full_name or self.email
623
623
624 @property
624 @property
625 def full_name(self):
625 def full_name(self):
626 return '%s %s' % (self.firstname, self.lastname)
626 return '%s %s' % (self.firstname, self.lastname)
627
627
628 @property
628 @property
629 def full_name_or_username(self):
629 def full_name_or_username(self):
630 return ('%s %s' % (self.firstname, self.lastname)
630 return ('%s %s' % (self.firstname, self.lastname)
631 if (self.firstname and self.lastname) else self.username)
631 if (self.firstname and self.lastname) else self.username)
632
632
633 @property
633 @property
634 def full_contact(self):
634 def full_contact(self):
635 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
635 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
636
636
637 @property
637 @property
638 def short_contact(self):
638 def short_contact(self):
639 return '%s %s' % (self.firstname, self.lastname)
639 return '%s %s' % (self.firstname, self.lastname)
640
640
641 @property
641 @property
642 def is_admin(self):
642 def is_admin(self):
643 return self.admin
643 return self.admin
644
644
645 @property
645 @property
646 def AuthUser(self):
646 def AuthUser(self):
647 """
647 """
648 Returns instance of AuthUser for this user
648 Returns instance of AuthUser for this user
649 """
649 """
650 from rhodecode.lib.auth import AuthUser
650 from rhodecode.lib.auth import AuthUser
651 return AuthUser(user_id=self.user_id, api_key=self.api_key,
651 return AuthUser(user_id=self.user_id, api_key=self.api_key,
652 username=self.username)
652 username=self.username)
653
653
654 @hybrid_property
654 @hybrid_property
655 def user_data(self):
655 def user_data(self):
656 if not self._user_data:
656 if not self._user_data:
657 return {}
657 return {}
658
658
659 try:
659 try:
660 return json.loads(self._user_data)
660 return json.loads(self._user_data)
661 except TypeError:
661 except TypeError:
662 return {}
662 return {}
663
663
664 @user_data.setter
664 @user_data.setter
665 def user_data(self, val):
665 def user_data(self, val):
666 if not isinstance(val, dict):
666 if not isinstance(val, dict):
667 raise Exception('user_data must be dict, got %s' % type(val))
667 raise Exception('user_data must be dict, got %s' % type(val))
668 try:
668 try:
669 self._user_data = json.dumps(val)
669 self._user_data = json.dumps(val)
670 except Exception:
670 except Exception:
671 log.error(traceback.format_exc())
671 log.error(traceback.format_exc())
672
672
673 @classmethod
673 @classmethod
674 def get_by_username(cls, username, case_insensitive=False,
674 def get_by_username(cls, username, case_insensitive=False,
675 cache=False, identity_cache=False):
675 cache=False, identity_cache=False):
676 session = Session()
676 session = Session()
677
677
678 if case_insensitive:
678 if case_insensitive:
679 q = cls.query().filter(
679 q = cls.query().filter(
680 func.lower(cls.username) == func.lower(username))
680 func.lower(cls.username) == func.lower(username))
681 else:
681 else:
682 q = cls.query().filter(cls.username == username)
682 q = cls.query().filter(cls.username == username)
683
683
684 if cache:
684 if cache:
685 if identity_cache:
685 if identity_cache:
686 val = cls.identity_cache(session, 'username', username)
686 val = cls.identity_cache(session, 'username', username)
687 if val:
687 if val:
688 return val
688 return val
689 else:
689 else:
690 q = q.options(
690 q = q.options(
691 FromCache("sql_cache_short",
691 FromCache("sql_cache_short",
692 "get_user_by_name_%s" % _hash_key(username)))
692 "get_user_by_name_%s" % _hash_key(username)))
693
693
694 return q.scalar()
694 return q.scalar()
695
695
696 @classmethod
696 @classmethod
697 def get_by_auth_token(cls, auth_token, cache=False, fallback=True):
697 def get_by_auth_token(cls, auth_token, cache=False, fallback=True):
698 q = cls.query().filter(cls.api_key == auth_token)
698 q = cls.query().filter(cls.api_key == auth_token)
699
699
700 if cache:
700 if cache:
701 q = q.options(FromCache("sql_cache_short",
701 q = q.options(FromCache("sql_cache_short",
702 "get_auth_token_%s" % auth_token))
702 "get_auth_token_%s" % auth_token))
703 res = q.scalar()
703 res = q.scalar()
704
704
705 if fallback and not res:
705 if fallback and not res:
706 #fallback to additional keys
706 #fallback to additional keys
707 _res = UserApiKeys.query()\
707 _res = UserApiKeys.query()\
708 .filter(UserApiKeys.api_key == auth_token)\
708 .filter(UserApiKeys.api_key == auth_token)\
709 .filter(or_(UserApiKeys.expires == -1,
709 .filter(or_(UserApiKeys.expires == -1,
710 UserApiKeys.expires >= time.time()))\
710 UserApiKeys.expires >= time.time()))\
711 .first()
711 .first()
712 if _res:
712 if _res:
713 res = _res.user
713 res = _res.user
714 return res
714 return res
715
715
716 @classmethod
716 @classmethod
717 def get_by_email(cls, email, case_insensitive=False, cache=False):
717 def get_by_email(cls, email, case_insensitive=False, cache=False):
718
718
719 if case_insensitive:
719 if case_insensitive:
720 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
720 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
721
721
722 else:
722 else:
723 q = cls.query().filter(cls.email == email)
723 q = cls.query().filter(cls.email == email)
724
724
725 if cache:
725 if cache:
726 q = q.options(FromCache("sql_cache_short",
726 q = q.options(FromCache("sql_cache_short",
727 "get_email_key_%s" % _hash_key(email)))
727 "get_email_key_%s" % _hash_key(email)))
728
728
729 ret = q.scalar()
729 ret = q.scalar()
730 if ret is None:
730 if ret is None:
731 q = UserEmailMap.query()
731 q = UserEmailMap.query()
732 # try fetching in alternate email map
732 # try fetching in alternate email map
733 if case_insensitive:
733 if case_insensitive:
734 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
734 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
735 else:
735 else:
736 q = q.filter(UserEmailMap.email == email)
736 q = q.filter(UserEmailMap.email == email)
737 q = q.options(joinedload(UserEmailMap.user))
737 q = q.options(joinedload(UserEmailMap.user))
738 if cache:
738 if cache:
739 q = q.options(FromCache("sql_cache_short",
739 q = q.options(FromCache("sql_cache_short",
740 "get_email_map_key_%s" % email))
740 "get_email_map_key_%s" % email))
741 ret = getattr(q.scalar(), 'user', None)
741 ret = getattr(q.scalar(), 'user', None)
742
742
743 return ret
743 return ret
744
744
745 @classmethod
745 @classmethod
746 def get_from_cs_author(cls, author):
746 def get_from_cs_author(cls, author):
747 """
747 """
748 Tries to get User objects out of commit author string
748 Tries to get User objects out of commit author string
749
749
750 :param author:
750 :param author:
751 """
751 """
752 from rhodecode.lib.helpers import email, author_name
752 from rhodecode.lib.helpers import email, author_name
753 # Valid email in the attribute passed, see if they're in the system
753 # Valid email in the attribute passed, see if they're in the system
754 _email = email(author)
754 _email = email(author)
755 if _email:
755 if _email:
756 user = cls.get_by_email(_email, case_insensitive=True)
756 user = cls.get_by_email(_email, case_insensitive=True)
757 if user:
757 if user:
758 return user
758 return user
759 # Maybe we can match by username?
759 # Maybe we can match by username?
760 _author = author_name(author)
760 _author = author_name(author)
761 user = cls.get_by_username(_author, case_insensitive=True)
761 user = cls.get_by_username(_author, case_insensitive=True)
762 if user:
762 if user:
763 return user
763 return user
764
764
765 def update_userdata(self, **kwargs):
765 def update_userdata(self, **kwargs):
766 usr = self
766 usr = self
767 old = usr.user_data
767 old = usr.user_data
768 old.update(**kwargs)
768 old.update(**kwargs)
769 usr.user_data = old
769 usr.user_data = old
770 Session().add(usr)
770 Session().add(usr)
771 log.debug('updated userdata with ', kwargs)
771 log.debug('updated userdata with ', kwargs)
772
772
773 def update_lastlogin(self):
773 def update_lastlogin(self):
774 """Update user lastlogin"""
774 """Update user lastlogin"""
775 self.last_login = datetime.datetime.now()
775 self.last_login = datetime.datetime.now()
776 Session().add(self)
776 Session().add(self)
777 log.debug('updated user %s lastlogin', self.username)
777 log.debug('updated user %s lastlogin', self.username)
778
778
779 def update_lastactivity(self):
779 def update_lastactivity(self):
780 """Update user lastactivity"""
780 """Update user lastactivity"""
781 usr = self
781 usr = self
782 old = usr.user_data
782 old = usr.user_data
783 old.update({'last_activity': time.time()})
783 old.update({'last_activity': time.time()})
784 usr.user_data = old
784 usr.user_data = old
785 Session().add(usr)
785 Session().add(usr)
786 log.debug('updated user %s lastactivity', usr.username)
786 log.debug('updated user %s lastactivity', usr.username)
787
787
788 def update_password(self, new_password, change_api_key=False):
788 def update_password(self, new_password, change_api_key=False):
789 from rhodecode.lib.auth import get_crypt_password,generate_auth_token
789 from rhodecode.lib.auth import get_crypt_password,generate_auth_token
790
790
791 self.password = get_crypt_password(new_password)
791 self.password = get_crypt_password(new_password)
792 if change_api_key:
792 if change_api_key:
793 self.api_key = generate_auth_token(self.username)
793 self.api_key = generate_auth_token(self.username)
794 Session().add(self)
794 Session().add(self)
795
795
796 @classmethod
796 @classmethod
797 def get_first_super_admin(cls):
797 def get_first_super_admin(cls):
798 user = User.query().filter(User.admin == true()).first()
798 user = User.query().filter(User.admin == true()).first()
799 if user is None:
799 if user is None:
800 raise Exception('FATAL: Missing administrative account!')
800 raise Exception('FATAL: Missing administrative account!')
801 return user
801 return user
802
802
803 @classmethod
803 @classmethod
804 def get_all_super_admins(cls):
804 def get_all_super_admins(cls):
805 """
805 """
806 Returns all admin accounts sorted by username
806 Returns all admin accounts sorted by username
807 """
807 """
808 return User.query().filter(User.admin == true())\
808 return User.query().filter(User.admin == true())\
809 .order_by(User.username.asc()).all()
809 .order_by(User.username.asc()).all()
810
810
811 @classmethod
811 @classmethod
812 def get_default_user(cls, cache=False):
812 def get_default_user(cls, cache=False):
813 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
813 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
814 if user is None:
814 if user is None:
815 raise Exception('FATAL: Missing default account!')
815 raise Exception('FATAL: Missing default account!')
816 return user
816 return user
817
817
818 def _get_default_perms(self, user, suffix=''):
818 def _get_default_perms(self, user, suffix=''):
819 from rhodecode.model.permission import PermissionModel
819 from rhodecode.model.permission import PermissionModel
820 return PermissionModel().get_default_perms(user.user_perms, suffix)
820 return PermissionModel().get_default_perms(user.user_perms, suffix)
821
821
822 def get_default_perms(self, suffix=''):
822 def get_default_perms(self, suffix=''):
823 return self._get_default_perms(self, suffix)
823 return self._get_default_perms(self, suffix)
824
824
825 def get_api_data(self, include_secrets=False, details='full'):
825 def get_api_data(self, include_secrets=False, details='full'):
826 """
826 """
827 Common function for generating user related data for API
827 Common function for generating user related data for API
828
828
829 :param include_secrets: By default secrets in the API data will be replaced
829 :param include_secrets: By default secrets in the API data will be replaced
830 by a placeholder value to prevent exposing this data by accident. In case
830 by a placeholder value to prevent exposing this data by accident. In case
831 this data shall be exposed, set this flag to ``True``.
831 this data shall be exposed, set this flag to ``True``.
832
832
833 :param details: details can be 'basic|full' basic gives only a subset of
833 :param details: details can be 'basic|full' basic gives only a subset of
834 the available user information that includes user_id, name and emails.
834 the available user information that includes user_id, name and emails.
835 """
835 """
836 user = self
836 user = self
837 user_data = self.user_data
837 user_data = self.user_data
838 data = {
838 data = {
839 'user_id': user.user_id,
839 'user_id': user.user_id,
840 'username': user.username,
840 'username': user.username,
841 'firstname': user.name,
841 'firstname': user.name,
842 'lastname': user.lastname,
842 'lastname': user.lastname,
843 'email': user.email,
843 'email': user.email,
844 'emails': user.emails,
844 'emails': user.emails,
845 }
845 }
846 if details == 'basic':
846 if details == 'basic':
847 return data
847 return data
848
848
849 api_key_length = 40
849 api_key_length = 40
850 api_key_replacement = '*' * api_key_length
850 api_key_replacement = '*' * api_key_length
851
851
852 extras = {
852 extras = {
853 'api_key': api_key_replacement,
853 'api_key': api_key_replacement,
854 'api_keys': [api_key_replacement],
854 'api_keys': [api_key_replacement],
855 'active': user.active,
855 'active': user.active,
856 'admin': user.admin,
856 'admin': user.admin,
857 'extern_type': user.extern_type,
857 'extern_type': user.extern_type,
858 'extern_name': user.extern_name,
858 'extern_name': user.extern_name,
859 'last_login': user.last_login,
859 'last_login': user.last_login,
860 'ip_addresses': user.ip_addresses,
860 'ip_addresses': user.ip_addresses,
861 'language': user_data.get('language')
861 'language': user_data.get('language')
862 }
862 }
863 data.update(extras)
863 data.update(extras)
864
864
865 if include_secrets:
865 if include_secrets:
866 data['api_key'] = user.api_key
866 data['api_key'] = user.api_key
867 data['api_keys'] = user.auth_tokens
867 data['api_keys'] = user.auth_tokens
868 return data
868 return data
869
869
870 def __json__(self):
870 def __json__(self):
871 data = {
871 data = {
872 'full_name': self.full_name,
872 'full_name': self.full_name,
873 'full_name_or_username': self.full_name_or_username,
873 'full_name_or_username': self.full_name_or_username,
874 'short_contact': self.short_contact,
874 'short_contact': self.short_contact,
875 'full_contact': self.full_contact,
875 'full_contact': self.full_contact,
876 }
876 }
877 data.update(self.get_api_data())
877 data.update(self.get_api_data())
878 return data
878 return data
879
879
880
880
881 class UserApiKeys(Base, BaseModel):
881 class UserApiKeys(Base, BaseModel):
882 __tablename__ = 'user_api_keys'
882 __tablename__ = 'user_api_keys'
883 __table_args__ = (
883 __table_args__ = (
884 Index('uak_api_key_idx', 'api_key'),
884 Index('uak_api_key_idx', 'api_key'),
885 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
885 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
886 UniqueConstraint('api_key'),
886 UniqueConstraint('api_key'),
887 {'extend_existing': True, 'mysql_engine': 'InnoDB',
887 {'extend_existing': True, 'mysql_engine': 'InnoDB',
888 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
888 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
889 )
889 )
890 __mapper_args__ = {}
890 __mapper_args__ = {}
891
891
892 # ApiKey role
892 # ApiKey role
893 ROLE_ALL = 'token_role_all'
893 ROLE_ALL = 'token_role_all'
894 ROLE_HTTP = 'token_role_http'
894 ROLE_HTTP = 'token_role_http'
895 ROLE_VCS = 'token_role_vcs'
895 ROLE_VCS = 'token_role_vcs'
896 ROLE_API = 'token_role_api'
896 ROLE_API = 'token_role_api'
897 ROLE_FEED = 'token_role_feed'
897 ROLE_FEED = 'token_role_feed'
898 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
898 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
899
899
900 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
900 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
901 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
901 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
902 api_key = Column("api_key", String(255), nullable=False, unique=True)
902 api_key = Column("api_key", String(255), nullable=False, unique=True)
903 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
903 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
904 expires = Column('expires', Float(53), nullable=False)
904 expires = Column('expires', Float(53), nullable=False)
905 role = Column('role', String(255), nullable=True)
905 role = Column('role', String(255), nullable=True)
906 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
906 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
907
907
908 user = relationship('User', lazy='joined')
908 user = relationship('User', lazy='joined')
909
909
910 @classmethod
910 @classmethod
911 def _get_role_name(cls, role):
911 def _get_role_name(cls, role):
912 return {
912 return {
913 cls.ROLE_ALL: _('all'),
913 cls.ROLE_ALL: _('all'),
914 cls.ROLE_HTTP: _('http/web interface'),
914 cls.ROLE_HTTP: _('http/web interface'),
915 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
915 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
916 cls.ROLE_API: _('api calls'),
916 cls.ROLE_API: _('api calls'),
917 cls.ROLE_FEED: _('feed access'),
917 cls.ROLE_FEED: _('feed access'),
918 }.get(role, role)
918 }.get(role, role)
919
919
920 @property
920 @property
921 def expired(self):
921 def expired(self):
922 if self.expires == -1:
922 if self.expires == -1:
923 return False
923 return False
924 return time.time() > self.expires
924 return time.time() > self.expires
925
925
926 @property
926 @property
927 def role_humanized(self):
927 def role_humanized(self):
928 return self._get_role_name(self.role)
928 return self._get_role_name(self.role)
929
929
930
930
931 class UserEmailMap(Base, BaseModel):
931 class UserEmailMap(Base, BaseModel):
932 __tablename__ = 'user_email_map'
932 __tablename__ = 'user_email_map'
933 __table_args__ = (
933 __table_args__ = (
934 Index('uem_email_idx', 'email'),
934 Index('uem_email_idx', 'email'),
935 UniqueConstraint('email'),
935 UniqueConstraint('email'),
936 {'extend_existing': True, 'mysql_engine': 'InnoDB',
936 {'extend_existing': True, 'mysql_engine': 'InnoDB',
937 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
937 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
938 )
938 )
939 __mapper_args__ = {}
939 __mapper_args__ = {}
940
940
941 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
941 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
942 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
942 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
943 _email = Column("email", String(255), nullable=True, unique=False, default=None)
943 _email = Column("email", String(255), nullable=True, unique=False, default=None)
944 user = relationship('User', lazy='joined')
944 user = relationship('User', lazy='joined')
945
945
946 @validates('_email')
946 @validates('_email')
947 def validate_email(self, key, email):
947 def validate_email(self, key, email):
948 # check if this email is not main one
948 # check if this email is not main one
949 main_email = Session().query(User).filter(User.email == email).scalar()
949 main_email = Session().query(User).filter(User.email == email).scalar()
950 if main_email is not None:
950 if main_email is not None:
951 raise AttributeError('email %s is present is user table' % email)
951 raise AttributeError('email %s is present is user table' % email)
952 return email
952 return email
953
953
954 @hybrid_property
954 @hybrid_property
955 def email(self):
955 def email(self):
956 return self._email
956 return self._email
957
957
958 @email.setter
958 @email.setter
959 def email(self, val):
959 def email(self, val):
960 self._email = val.lower() if val else None
960 self._email = val.lower() if val else None
961
961
962
962
963 class UserIpMap(Base, BaseModel):
963 class UserIpMap(Base, BaseModel):
964 __tablename__ = 'user_ip_map'
964 __tablename__ = 'user_ip_map'
965 __table_args__ = (
965 __table_args__ = (
966 UniqueConstraint('user_id', 'ip_addr'),
966 UniqueConstraint('user_id', 'ip_addr'),
967 {'extend_existing': True, 'mysql_engine': 'InnoDB',
967 {'extend_existing': True, 'mysql_engine': 'InnoDB',
968 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
968 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
969 )
969 )
970 __mapper_args__ = {}
970 __mapper_args__ = {}
971
971
972 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
972 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
973 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
973 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
974 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
974 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
975 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
975 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
976 description = Column("description", String(10000), nullable=True, unique=None, default=None)
976 description = Column("description", String(10000), nullable=True, unique=None, default=None)
977 user = relationship('User', lazy='joined')
977 user = relationship('User', lazy='joined')
978
978
979 @classmethod
979 @classmethod
980 def _get_ip_range(cls, ip_addr):
980 def _get_ip_range(cls, ip_addr):
981 net = ipaddress.ip_network(ip_addr, strict=False)
981 net = ipaddress.ip_network(ip_addr, strict=False)
982 return [str(net.network_address), str(net.broadcast_address)]
982 return [str(net.network_address), str(net.broadcast_address)]
983
983
984 def __json__(self):
984 def __json__(self):
985 return {
985 return {
986 'ip_addr': self.ip_addr,
986 'ip_addr': self.ip_addr,
987 'ip_range': self._get_ip_range(self.ip_addr),
987 'ip_range': self._get_ip_range(self.ip_addr),
988 }
988 }
989
989
990 def __unicode__(self):
990 def __unicode__(self):
991 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
991 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
992 self.user_id, self.ip_addr)
992 self.user_id, self.ip_addr)
993
993
994 class UserLog(Base, BaseModel):
994 class UserLog(Base, BaseModel):
995 __tablename__ = 'user_logs'
995 __tablename__ = 'user_logs'
996 __table_args__ = (
996 __table_args__ = (
997 {'extend_existing': True, 'mysql_engine': 'InnoDB',
997 {'extend_existing': True, 'mysql_engine': 'InnoDB',
998 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
998 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
999 )
999 )
1000 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1000 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1001 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1001 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1002 username = Column("username", String(255), nullable=True, unique=None, default=None)
1002 username = Column("username", String(255), nullable=True, unique=None, default=None)
1003 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
1003 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
1004 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1004 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1005 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1005 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1006 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1006 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1007 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1007 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1008
1008
1009 def __unicode__(self):
1009 def __unicode__(self):
1010 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1010 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1011 self.repository_name,
1011 self.repository_name,
1012 self.action)
1012 self.action)
1013
1013
1014 @property
1014 @property
1015 def action_as_day(self):
1015 def action_as_day(self):
1016 return datetime.date(*self.action_date.timetuple()[:3])
1016 return datetime.date(*self.action_date.timetuple()[:3])
1017
1017
1018 user = relationship('User')
1018 user = relationship('User')
1019 repository = relationship('Repository', cascade='')
1019 repository = relationship('Repository', cascade='')
1020
1020
1021
1021
1022 class UserGroup(Base, BaseModel):
1022 class UserGroup(Base, BaseModel):
1023 __tablename__ = 'users_groups'
1023 __tablename__ = 'users_groups'
1024 __table_args__ = (
1024 __table_args__ = (
1025 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1025 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1026 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1026 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1027 )
1027 )
1028
1028
1029 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1029 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1030 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1030 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1031 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1031 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1032 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1032 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1033 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1033 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1034 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1034 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1035 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1035 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1036 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1036 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1037
1037
1038 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1038 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1039 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1039 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1040 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1040 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1041 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1041 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1042 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1042 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1043 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1043 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1044
1044
1045 user = relationship('User')
1045 user = relationship('User')
1046
1046
1047 @hybrid_property
1047 @hybrid_property
1048 def group_data(self):
1048 def group_data(self):
1049 if not self._group_data:
1049 if not self._group_data:
1050 return {}
1050 return {}
1051
1051
1052 try:
1052 try:
1053 return json.loads(self._group_data)
1053 return json.loads(self._group_data)
1054 except TypeError:
1054 except TypeError:
1055 return {}
1055 return {}
1056
1056
1057 @group_data.setter
1057 @group_data.setter
1058 def group_data(self, val):
1058 def group_data(self, val):
1059 try:
1059 try:
1060 self._group_data = json.dumps(val)
1060 self._group_data = json.dumps(val)
1061 except Exception:
1061 except Exception:
1062 log.error(traceback.format_exc())
1062 log.error(traceback.format_exc())
1063
1063
1064 def __unicode__(self):
1064 def __unicode__(self):
1065 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1065 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1066 self.users_group_id,
1066 self.users_group_id,
1067 self.users_group_name)
1067 self.users_group_name)
1068
1068
1069 @classmethod
1069 @classmethod
1070 def get_by_group_name(cls, group_name, cache=False,
1070 def get_by_group_name(cls, group_name, cache=False,
1071 case_insensitive=False):
1071 case_insensitive=False):
1072 if case_insensitive:
1072 if case_insensitive:
1073 q = cls.query().filter(func.lower(cls.users_group_name) ==
1073 q = cls.query().filter(func.lower(cls.users_group_name) ==
1074 func.lower(group_name))
1074 func.lower(group_name))
1075
1075
1076 else:
1076 else:
1077 q = cls.query().filter(cls.users_group_name == group_name)
1077 q = cls.query().filter(cls.users_group_name == group_name)
1078 if cache:
1078 if cache:
1079 q = q.options(FromCache(
1079 q = q.options(FromCache(
1080 "sql_cache_short",
1080 "sql_cache_short",
1081 "get_group_%s" % _hash_key(group_name)))
1081 "get_group_%s" % _hash_key(group_name)))
1082 return q.scalar()
1082 return q.scalar()
1083
1083
1084 @classmethod
1084 @classmethod
1085 def get(cls, user_group_id, cache=False):
1085 def get(cls, user_group_id, cache=False):
1086 user_group = cls.query()
1086 user_group = cls.query()
1087 if cache:
1087 if cache:
1088 user_group = user_group.options(FromCache("sql_cache_short",
1088 user_group = user_group.options(FromCache("sql_cache_short",
1089 "get_users_group_%s" % user_group_id))
1089 "get_users_group_%s" % user_group_id))
1090 return user_group.get(user_group_id)
1090 return user_group.get(user_group_id)
1091
1091
1092 def permissions(self, with_admins=True, with_owner=True):
1092 def permissions(self, with_admins=True, with_owner=True):
1093 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1093 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1094 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1094 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1095 joinedload(UserUserGroupToPerm.user),
1095 joinedload(UserUserGroupToPerm.user),
1096 joinedload(UserUserGroupToPerm.permission),)
1096 joinedload(UserUserGroupToPerm.permission),)
1097
1097
1098 # get owners and admins and permissions. We do a trick of re-writing
1098 # get owners and admins and permissions. We do a trick of re-writing
1099 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1099 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1100 # has a global reference and changing one object propagates to all
1100 # has a global reference and changing one object propagates to all
1101 # others. This means if admin is also an owner admin_row that change
1101 # others. This means if admin is also an owner admin_row that change
1102 # would propagate to both objects
1102 # would propagate to both objects
1103 perm_rows = []
1103 perm_rows = []
1104 for _usr in q.all():
1104 for _usr in q.all():
1105 usr = AttributeDict(_usr.user.get_dict())
1105 usr = AttributeDict(_usr.user.get_dict())
1106 usr.permission = _usr.permission.permission_name
1106 usr.permission = _usr.permission.permission_name
1107 perm_rows.append(usr)
1107 perm_rows.append(usr)
1108
1108
1109 # filter the perm rows by 'default' first and then sort them by
1109 # filter the perm rows by 'default' first and then sort them by
1110 # admin,write,read,none permissions sorted again alphabetically in
1110 # admin,write,read,none permissions sorted again alphabetically in
1111 # each group
1111 # each group
1112 perm_rows = sorted(perm_rows, key=display_sort)
1112 perm_rows = sorted(perm_rows, key=display_sort)
1113
1113
1114 _admin_perm = 'usergroup.admin'
1114 _admin_perm = 'usergroup.admin'
1115 owner_row = []
1115 owner_row = []
1116 if with_owner:
1116 if with_owner:
1117 usr = AttributeDict(self.user.get_dict())
1117 usr = AttributeDict(self.user.get_dict())
1118 usr.owner_row = True
1118 usr.owner_row = True
1119 usr.permission = _admin_perm
1119 usr.permission = _admin_perm
1120 owner_row.append(usr)
1120 owner_row.append(usr)
1121
1121
1122 super_admin_rows = []
1122 super_admin_rows = []
1123 if with_admins:
1123 if with_admins:
1124 for usr in User.get_all_super_admins():
1124 for usr in User.get_all_super_admins():
1125 # if this admin is also owner, don't double the record
1125 # if this admin is also owner, don't double the record
1126 if usr.user_id == owner_row[0].user_id:
1126 if usr.user_id == owner_row[0].user_id:
1127 owner_row[0].admin_row = True
1127 owner_row[0].admin_row = True
1128 else:
1128 else:
1129 usr = AttributeDict(usr.get_dict())
1129 usr = AttributeDict(usr.get_dict())
1130 usr.admin_row = True
1130 usr.admin_row = True
1131 usr.permission = _admin_perm
1131 usr.permission = _admin_perm
1132 super_admin_rows.append(usr)
1132 super_admin_rows.append(usr)
1133
1133
1134 return super_admin_rows + owner_row + perm_rows
1134 return super_admin_rows + owner_row + perm_rows
1135
1135
1136 def permission_user_groups(self):
1136 def permission_user_groups(self):
1137 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1137 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1138 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1138 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1139 joinedload(UserGroupUserGroupToPerm.target_user_group),
1139 joinedload(UserGroupUserGroupToPerm.target_user_group),
1140 joinedload(UserGroupUserGroupToPerm.permission),)
1140 joinedload(UserGroupUserGroupToPerm.permission),)
1141
1141
1142 perm_rows = []
1142 perm_rows = []
1143 for _user_group in q.all():
1143 for _user_group in q.all():
1144 usr = AttributeDict(_user_group.user_group.get_dict())
1144 usr = AttributeDict(_user_group.user_group.get_dict())
1145 usr.permission = _user_group.permission.permission_name
1145 usr.permission = _user_group.permission.permission_name
1146 perm_rows.append(usr)
1146 perm_rows.append(usr)
1147
1147
1148 return perm_rows
1148 return perm_rows
1149
1149
1150 def _get_default_perms(self, user_group, suffix=''):
1150 def _get_default_perms(self, user_group, suffix=''):
1151 from rhodecode.model.permission import PermissionModel
1151 from rhodecode.model.permission import PermissionModel
1152 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1152 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1153
1153
1154 def get_default_perms(self, suffix=''):
1154 def get_default_perms(self, suffix=''):
1155 return self._get_default_perms(self, suffix)
1155 return self._get_default_perms(self, suffix)
1156
1156
1157 def get_api_data(self, with_group_members=True, include_secrets=False):
1157 def get_api_data(self, with_group_members=True, include_secrets=False):
1158 """
1158 """
1159 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1159 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1160 basically forwarded.
1160 basically forwarded.
1161
1161
1162 """
1162 """
1163 user_group = self
1163 user_group = self
1164
1164
1165 data = {
1165 data = {
1166 'users_group_id': user_group.users_group_id,
1166 'users_group_id': user_group.users_group_id,
1167 'group_name': user_group.users_group_name,
1167 'group_name': user_group.users_group_name,
1168 'group_description': user_group.user_group_description,
1168 'group_description': user_group.user_group_description,
1169 'active': user_group.users_group_active,
1169 'active': user_group.users_group_active,
1170 'owner': user_group.user.username,
1170 'owner': user_group.user.username,
1171 }
1171 }
1172 if with_group_members:
1172 if with_group_members:
1173 users = []
1173 users = []
1174 for user in user_group.members:
1174 for user in user_group.members:
1175 user = user.user
1175 user = user.user
1176 users.append(user.get_api_data(include_secrets=include_secrets))
1176 users.append(user.get_api_data(include_secrets=include_secrets))
1177 data['users'] = users
1177 data['users'] = users
1178
1178
1179 return data
1179 return data
1180
1180
1181
1181
1182 class UserGroupMember(Base, BaseModel):
1182 class UserGroupMember(Base, BaseModel):
1183 __tablename__ = 'users_groups_members'
1183 __tablename__ = 'users_groups_members'
1184 __table_args__ = (
1184 __table_args__ = (
1185 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1185 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1186 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1186 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1187 )
1187 )
1188
1188
1189 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1189 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1190 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1190 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1191 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1191 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1192
1192
1193 user = relationship('User', lazy='joined')
1193 user = relationship('User', lazy='joined')
1194 users_group = relationship('UserGroup')
1194 users_group = relationship('UserGroup')
1195
1195
1196 def __init__(self, gr_id='', u_id=''):
1196 def __init__(self, gr_id='', u_id=''):
1197 self.users_group_id = gr_id
1197 self.users_group_id = gr_id
1198 self.user_id = u_id
1198 self.user_id = u_id
1199
1199
1200
1200
1201 class RepositoryField(Base, BaseModel):
1201 class RepositoryField(Base, BaseModel):
1202 __tablename__ = 'repositories_fields'
1202 __tablename__ = 'repositories_fields'
1203 __table_args__ = (
1203 __table_args__ = (
1204 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1204 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1205 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1205 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1206 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1206 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1207 )
1207 )
1208 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1208 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1209
1209
1210 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1210 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1211 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1211 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1212 field_key = Column("field_key", String(250))
1212 field_key = Column("field_key", String(250))
1213 field_label = Column("field_label", String(1024), nullable=False)
1213 field_label = Column("field_label", String(1024), nullable=False)
1214 field_value = Column("field_value", String(10000), nullable=False)
1214 field_value = Column("field_value", String(10000), nullable=False)
1215 field_desc = Column("field_desc", String(1024), nullable=False)
1215 field_desc = Column("field_desc", String(1024), nullable=False)
1216 field_type = Column("field_type", String(255), nullable=False, unique=None)
1216 field_type = Column("field_type", String(255), nullable=False, unique=None)
1217 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1217 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1218
1218
1219 repository = relationship('Repository')
1219 repository = relationship('Repository')
1220
1220
1221 @property
1221 @property
1222 def field_key_prefixed(self):
1222 def field_key_prefixed(self):
1223 return 'ex_%s' % self.field_key
1223 return 'ex_%s' % self.field_key
1224
1224
1225 @classmethod
1225 @classmethod
1226 def un_prefix_key(cls, key):
1226 def un_prefix_key(cls, key):
1227 if key.startswith(cls.PREFIX):
1227 if key.startswith(cls.PREFIX):
1228 return key[len(cls.PREFIX):]
1228 return key[len(cls.PREFIX):]
1229 return key
1229 return key
1230
1230
1231 @classmethod
1231 @classmethod
1232 def get_by_key_name(cls, key, repo):
1232 def get_by_key_name(cls, key, repo):
1233 row = cls.query()\
1233 row = cls.query()\
1234 .filter(cls.repository == repo)\
1234 .filter(cls.repository == repo)\
1235 .filter(cls.field_key == key).scalar()
1235 .filter(cls.field_key == key).scalar()
1236 return row
1236 return row
1237
1237
1238
1238
1239 class Repository(Base, BaseModel):
1239 class Repository(Base, BaseModel):
1240 __tablename__ = 'repositories'
1240 __tablename__ = 'repositories'
1241 __table_args__ = (
1241 __table_args__ = (
1242 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1242 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1243 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1243 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1244 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1244 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1245 )
1245 )
1246 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1246 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1247 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1247 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1248
1248
1249 STATE_CREATED = 'repo_state_created'
1249 STATE_CREATED = 'repo_state_created'
1250 STATE_PENDING = 'repo_state_pending'
1250 STATE_PENDING = 'repo_state_pending'
1251 STATE_ERROR = 'repo_state_error'
1251 STATE_ERROR = 'repo_state_error'
1252
1252
1253 LOCK_AUTOMATIC = 'lock_auto'
1253 LOCK_AUTOMATIC = 'lock_auto'
1254 LOCK_API = 'lock_api'
1254 LOCK_API = 'lock_api'
1255 LOCK_WEB = 'lock_web'
1255 LOCK_WEB = 'lock_web'
1256 LOCK_PULL = 'lock_pull'
1256 LOCK_PULL = 'lock_pull'
1257
1257
1258 NAME_SEP = URL_SEP
1258 NAME_SEP = URL_SEP
1259
1259
1260 repo_id = Column(
1260 repo_id = Column(
1261 "repo_id", Integer(), nullable=False, unique=True, default=None,
1261 "repo_id", Integer(), nullable=False, unique=True, default=None,
1262 primary_key=True)
1262 primary_key=True)
1263 _repo_name = Column(
1263 _repo_name = Column(
1264 "repo_name", Text(), nullable=False, default=None)
1264 "repo_name", Text(), nullable=False, default=None)
1265 _repo_name_hash = Column(
1265 _repo_name_hash = Column(
1266 "repo_name_hash", String(255), nullable=False, unique=True)
1266 "repo_name_hash", String(255), nullable=False, unique=True)
1267 repo_state = Column("repo_state", String(255), nullable=True)
1267 repo_state = Column("repo_state", String(255), nullable=True)
1268
1268
1269 clone_uri = Column(
1269 clone_uri = Column(
1270 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1270 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1271 default=None)
1271 default=None)
1272 repo_type = Column(
1272 repo_type = Column(
1273 "repo_type", String(255), nullable=False, unique=False, default=None)
1273 "repo_type", String(255), nullable=False, unique=False, default=None)
1274 user_id = Column(
1274 user_id = Column(
1275 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1275 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1276 unique=False, default=None)
1276 unique=False, default=None)
1277 private = Column(
1277 private = Column(
1278 "private", Boolean(), nullable=True, unique=None, default=None)
1278 "private", Boolean(), nullable=True, unique=None, default=None)
1279 enable_statistics = Column(
1279 enable_statistics = Column(
1280 "statistics", Boolean(), nullable=True, unique=None, default=True)
1280 "statistics", Boolean(), nullable=True, unique=None, default=True)
1281 enable_downloads = Column(
1281 enable_downloads = Column(
1282 "downloads", Boolean(), nullable=True, unique=None, default=True)
1282 "downloads", Boolean(), nullable=True, unique=None, default=True)
1283 description = Column(
1283 description = Column(
1284 "description", String(10000), nullable=True, unique=None, default=None)
1284 "description", String(10000), nullable=True, unique=None, default=None)
1285 created_on = Column(
1285 created_on = Column(
1286 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1286 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1287 default=datetime.datetime.now)
1287 default=datetime.datetime.now)
1288 updated_on = Column(
1288 updated_on = Column(
1289 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1289 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1290 default=datetime.datetime.now)
1290 default=datetime.datetime.now)
1291 _landing_revision = Column(
1291 _landing_revision = Column(
1292 "landing_revision", String(255), nullable=False, unique=False,
1292 "landing_revision", String(255), nullable=False, unique=False,
1293 default=None)
1293 default=None)
1294 enable_locking = Column(
1294 enable_locking = Column(
1295 "enable_locking", Boolean(), nullable=False, unique=None,
1295 "enable_locking", Boolean(), nullable=False, unique=None,
1296 default=False)
1296 default=False)
1297 _locked = Column(
1297 _locked = Column(
1298 "locked", String(255), nullable=True, unique=False, default=None)
1298 "locked", String(255), nullable=True, unique=False, default=None)
1299 _changeset_cache = Column(
1299 _changeset_cache = Column(
1300 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1300 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1301
1301
1302 fork_id = Column(
1302 fork_id = Column(
1303 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1303 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1304 nullable=True, unique=False, default=None)
1304 nullable=True, unique=False, default=None)
1305 group_id = Column(
1305 group_id = Column(
1306 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1306 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1307 unique=False, default=None)
1307 unique=False, default=None)
1308
1308
1309 user = relationship('User', lazy='joined')
1309 user = relationship('User', lazy='joined')
1310 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1310 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1311 group = relationship('RepoGroup', lazy='joined')
1311 group = relationship('RepoGroup', lazy='joined')
1312 repo_to_perm = relationship(
1312 repo_to_perm = relationship(
1313 'UserRepoToPerm', cascade='all',
1313 'UserRepoToPerm', cascade='all',
1314 order_by='UserRepoToPerm.repo_to_perm_id')
1314 order_by='UserRepoToPerm.repo_to_perm_id')
1315 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1315 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1316 stats = relationship('Statistics', cascade='all', uselist=False)
1316 stats = relationship('Statistics', cascade='all', uselist=False)
1317
1317
1318 followers = relationship(
1318 followers = relationship(
1319 'UserFollowing',
1319 'UserFollowing',
1320 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1320 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1321 cascade='all')
1321 cascade='all')
1322 extra_fields = relationship(
1322 extra_fields = relationship(
1323 'RepositoryField', cascade="all, delete, delete-orphan")
1323 'RepositoryField', cascade="all, delete, delete-orphan")
1324 logs = relationship('UserLog')
1324 logs = relationship('UserLog')
1325 comments = relationship(
1325 comments = relationship(
1326 'ChangesetComment', cascade="all, delete, delete-orphan")
1326 'ChangesetComment', cascade="all, delete, delete-orphan")
1327 pull_requests_source = relationship(
1327 pull_requests_source = relationship(
1328 'PullRequest',
1328 'PullRequest',
1329 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1329 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1330 cascade="all, delete, delete-orphan")
1330 cascade="all, delete, delete-orphan")
1331 pull_requests_target = relationship(
1331 pull_requests_target = relationship(
1332 'PullRequest',
1332 'PullRequest',
1333 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1333 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1334 cascade="all, delete, delete-orphan")
1334 cascade="all, delete, delete-orphan")
1335 ui = relationship('RepoRhodeCodeUi', cascade="all")
1335 ui = relationship('RepoRhodeCodeUi', cascade="all")
1336 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1336 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1337 integrations = relationship('Integration',
1337 integrations = relationship('Integration',
1338 cascade="all, delete, delete-orphan")
1338 cascade="all, delete, delete-orphan")
1339
1339
1340 def __unicode__(self):
1340 def __unicode__(self):
1341 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1341 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1342 safe_unicode(self.repo_name))
1342 safe_unicode(self.repo_name))
1343
1343
1344 @hybrid_property
1344 @hybrid_property
1345 def landing_rev(self):
1345 def landing_rev(self):
1346 # always should return [rev_type, rev]
1346 # always should return [rev_type, rev]
1347 if self._landing_revision:
1347 if self._landing_revision:
1348 _rev_info = self._landing_revision.split(':')
1348 _rev_info = self._landing_revision.split(':')
1349 if len(_rev_info) < 2:
1349 if len(_rev_info) < 2:
1350 _rev_info.insert(0, 'rev')
1350 _rev_info.insert(0, 'rev')
1351 return [_rev_info[0], _rev_info[1]]
1351 return [_rev_info[0], _rev_info[1]]
1352 return [None, None]
1352 return [None, None]
1353
1353
1354 @landing_rev.setter
1354 @landing_rev.setter
1355 def landing_rev(self, val):
1355 def landing_rev(self, val):
1356 if ':' not in val:
1356 if ':' not in val:
1357 raise ValueError('value must be delimited with `:` and consist '
1357 raise ValueError('value must be delimited with `:` and consist '
1358 'of <rev_type>:<rev>, got %s instead' % val)
1358 'of <rev_type>:<rev>, got %s instead' % val)
1359 self._landing_revision = val
1359 self._landing_revision = val
1360
1360
1361 @hybrid_property
1361 @hybrid_property
1362 def locked(self):
1362 def locked(self):
1363 if self._locked:
1363 if self._locked:
1364 user_id, timelocked, reason = self._locked.split(':')
1364 user_id, timelocked, reason = self._locked.split(':')
1365 lock_values = int(user_id), timelocked, reason
1365 lock_values = int(user_id), timelocked, reason
1366 else:
1366 else:
1367 lock_values = [None, None, None]
1367 lock_values = [None, None, None]
1368 return lock_values
1368 return lock_values
1369
1369
1370 @locked.setter
1370 @locked.setter
1371 def locked(self, val):
1371 def locked(self, val):
1372 if val and isinstance(val, (list, tuple)):
1372 if val and isinstance(val, (list, tuple)):
1373 self._locked = ':'.join(map(str, val))
1373 self._locked = ':'.join(map(str, val))
1374 else:
1374 else:
1375 self._locked = None
1375 self._locked = None
1376
1376
1377 @hybrid_property
1377 @hybrid_property
1378 def changeset_cache(self):
1378 def changeset_cache(self):
1379 from rhodecode.lib.vcs.backends.base import EmptyCommit
1379 from rhodecode.lib.vcs.backends.base import EmptyCommit
1380 dummy = EmptyCommit().__json__()
1380 dummy = EmptyCommit().__json__()
1381 if not self._changeset_cache:
1381 if not self._changeset_cache:
1382 return dummy
1382 return dummy
1383 try:
1383 try:
1384 return json.loads(self._changeset_cache)
1384 return json.loads(self._changeset_cache)
1385 except TypeError:
1385 except TypeError:
1386 return dummy
1386 return dummy
1387 except Exception:
1387 except Exception:
1388 log.error(traceback.format_exc())
1388 log.error(traceback.format_exc())
1389 return dummy
1389 return dummy
1390
1390
1391 @changeset_cache.setter
1391 @changeset_cache.setter
1392 def changeset_cache(self, val):
1392 def changeset_cache(self, val):
1393 try:
1393 try:
1394 self._changeset_cache = json.dumps(val)
1394 self._changeset_cache = json.dumps(val)
1395 except Exception:
1395 except Exception:
1396 log.error(traceback.format_exc())
1396 log.error(traceback.format_exc())
1397
1397
1398 @hybrid_property
1398 @hybrid_property
1399 def repo_name(self):
1399 def repo_name(self):
1400 return self._repo_name
1400 return self._repo_name
1401
1401
1402 @repo_name.setter
1402 @repo_name.setter
1403 def repo_name(self, value):
1403 def repo_name(self, value):
1404 self._repo_name = value
1404 self._repo_name = value
1405 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1405 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1406
1406
1407 @classmethod
1407 @classmethod
1408 def normalize_repo_name(cls, repo_name):
1408 def normalize_repo_name(cls, repo_name):
1409 """
1409 """
1410 Normalizes os specific repo_name to the format internally stored inside
1410 Normalizes os specific repo_name to the format internally stored inside
1411 database using URL_SEP
1411 database using URL_SEP
1412
1412
1413 :param cls:
1413 :param cls:
1414 :param repo_name:
1414 :param repo_name:
1415 """
1415 """
1416 return cls.NAME_SEP.join(repo_name.split(os.sep))
1416 return cls.NAME_SEP.join(repo_name.split(os.sep))
1417
1417
1418 @classmethod
1418 @classmethod
1419 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1419 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1420 session = Session()
1420 session = Session()
1421 q = session.query(cls).filter(cls.repo_name == repo_name)
1421 q = session.query(cls).filter(cls.repo_name == repo_name)
1422
1422
1423 if cache:
1423 if cache:
1424 if identity_cache:
1424 if identity_cache:
1425 val = cls.identity_cache(session, 'repo_name', repo_name)
1425 val = cls.identity_cache(session, 'repo_name', repo_name)
1426 if val:
1426 if val:
1427 return val
1427 return val
1428 else:
1428 else:
1429 q = q.options(
1429 q = q.options(
1430 FromCache("sql_cache_short",
1430 FromCache("sql_cache_short",
1431 "get_repo_by_name_%s" % _hash_key(repo_name)))
1431 "get_repo_by_name_%s" % _hash_key(repo_name)))
1432
1432
1433 return q.scalar()
1433 return q.scalar()
1434
1434
1435 @classmethod
1435 @classmethod
1436 def get_by_full_path(cls, repo_full_path):
1436 def get_by_full_path(cls, repo_full_path):
1437 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1437 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1438 repo_name = cls.normalize_repo_name(repo_name)
1438 repo_name = cls.normalize_repo_name(repo_name)
1439 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1439 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1440
1440
1441 @classmethod
1441 @classmethod
1442 def get_repo_forks(cls, repo_id):
1442 def get_repo_forks(cls, repo_id):
1443 return cls.query().filter(Repository.fork_id == repo_id)
1443 return cls.query().filter(Repository.fork_id == repo_id)
1444
1444
1445 @classmethod
1445 @classmethod
1446 def base_path(cls):
1446 def base_path(cls):
1447 """
1447 """
1448 Returns base path when all repos are stored
1448 Returns base path when all repos are stored
1449
1449
1450 :param cls:
1450 :param cls:
1451 """
1451 """
1452 q = Session().query(RhodeCodeUi)\
1452 q = Session().query(RhodeCodeUi)\
1453 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1453 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1454 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1454 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1455 return q.one().ui_value
1455 return q.one().ui_value
1456
1456
1457 @classmethod
1457 @classmethod
1458 def is_valid(cls, repo_name):
1458 def is_valid(cls, repo_name):
1459 """
1459 """
1460 returns True if given repo name is a valid filesystem repository
1460 returns True if given repo name is a valid filesystem repository
1461
1461
1462 :param cls:
1462 :param cls:
1463 :param repo_name:
1463 :param repo_name:
1464 """
1464 """
1465 from rhodecode.lib.utils import is_valid_repo
1465 from rhodecode.lib.utils import is_valid_repo
1466
1466
1467 return is_valid_repo(repo_name, cls.base_path())
1467 return is_valid_repo(repo_name, cls.base_path())
1468
1468
1469 @classmethod
1469 @classmethod
1470 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1470 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1471 case_insensitive=True):
1471 case_insensitive=True):
1472 q = Repository.query()
1472 q = Repository.query()
1473
1473
1474 if not isinstance(user_id, Optional):
1474 if not isinstance(user_id, Optional):
1475 q = q.filter(Repository.user_id == user_id)
1475 q = q.filter(Repository.user_id == user_id)
1476
1476
1477 if not isinstance(group_id, Optional):
1477 if not isinstance(group_id, Optional):
1478 q = q.filter(Repository.group_id == group_id)
1478 q = q.filter(Repository.group_id == group_id)
1479
1479
1480 if case_insensitive:
1480 if case_insensitive:
1481 q = q.order_by(func.lower(Repository.repo_name))
1481 q = q.order_by(func.lower(Repository.repo_name))
1482 else:
1482 else:
1483 q = q.order_by(Repository.repo_name)
1483 q = q.order_by(Repository.repo_name)
1484 return q.all()
1484 return q.all()
1485
1485
1486 @property
1486 @property
1487 def forks(self):
1487 def forks(self):
1488 """
1488 """
1489 Return forks of this repo
1489 Return forks of this repo
1490 """
1490 """
1491 return Repository.get_repo_forks(self.repo_id)
1491 return Repository.get_repo_forks(self.repo_id)
1492
1492
1493 @property
1493 @property
1494 def parent(self):
1494 def parent(self):
1495 """
1495 """
1496 Returns fork parent
1496 Returns fork parent
1497 """
1497 """
1498 return self.fork
1498 return self.fork
1499
1499
1500 @property
1500 @property
1501 def just_name(self):
1501 def just_name(self):
1502 return self.repo_name.split(self.NAME_SEP)[-1]
1502 return self.repo_name.split(self.NAME_SEP)[-1]
1503
1503
1504 @property
1504 @property
1505 def groups_with_parents(self):
1505 def groups_with_parents(self):
1506 groups = []
1506 groups = []
1507 if self.group is None:
1507 if self.group is None:
1508 return groups
1508 return groups
1509
1509
1510 cur_gr = self.group
1510 cur_gr = self.group
1511 groups.insert(0, cur_gr)
1511 groups.insert(0, cur_gr)
1512 while 1:
1512 while 1:
1513 gr = getattr(cur_gr, 'parent_group', None)
1513 gr = getattr(cur_gr, 'parent_group', None)
1514 cur_gr = cur_gr.parent_group
1514 cur_gr = cur_gr.parent_group
1515 if gr is None:
1515 if gr is None:
1516 break
1516 break
1517 groups.insert(0, gr)
1517 groups.insert(0, gr)
1518
1518
1519 return groups
1519 return groups
1520
1520
1521 @property
1521 @property
1522 def groups_and_repo(self):
1522 def groups_and_repo(self):
1523 return self.groups_with_parents, self
1523 return self.groups_with_parents, self
1524
1524
1525 @LazyProperty
1525 @LazyProperty
1526 def repo_path(self):
1526 def repo_path(self):
1527 """
1527 """
1528 Returns base full path for that repository means where it actually
1528 Returns base full path for that repository means where it actually
1529 exists on a filesystem
1529 exists on a filesystem
1530 """
1530 """
1531 q = Session().query(RhodeCodeUi).filter(
1531 q = Session().query(RhodeCodeUi).filter(
1532 RhodeCodeUi.ui_key == self.NAME_SEP)
1532 RhodeCodeUi.ui_key == self.NAME_SEP)
1533 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1533 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1534 return q.one().ui_value
1534 return q.one().ui_value
1535
1535
1536 @property
1536 @property
1537 def repo_full_path(self):
1537 def repo_full_path(self):
1538 p = [self.repo_path]
1538 p = [self.repo_path]
1539 # we need to split the name by / since this is how we store the
1539 # we need to split the name by / since this is how we store the
1540 # names in the database, but that eventually needs to be converted
1540 # names in the database, but that eventually needs to be converted
1541 # into a valid system path
1541 # into a valid system path
1542 p += self.repo_name.split(self.NAME_SEP)
1542 p += self.repo_name.split(self.NAME_SEP)
1543 return os.path.join(*map(safe_unicode, p))
1543 return os.path.join(*map(safe_unicode, p))
1544
1544
1545 @property
1545 @property
1546 def cache_keys(self):
1546 def cache_keys(self):
1547 """
1547 """
1548 Returns associated cache keys for that repo
1548 Returns associated cache keys for that repo
1549 """
1549 """
1550 return CacheKey.query()\
1550 return CacheKey.query()\
1551 .filter(CacheKey.cache_args == self.repo_name)\
1551 .filter(CacheKey.cache_args == self.repo_name)\
1552 .order_by(CacheKey.cache_key)\
1552 .order_by(CacheKey.cache_key)\
1553 .all()
1553 .all()
1554
1554
1555 def get_new_name(self, repo_name):
1555 def get_new_name(self, repo_name):
1556 """
1556 """
1557 returns new full repository name based on assigned group and new new
1557 returns new full repository name based on assigned group and new new
1558
1558
1559 :param group_name:
1559 :param group_name:
1560 """
1560 """
1561 path_prefix = self.group.full_path_splitted if self.group else []
1561 path_prefix = self.group.full_path_splitted if self.group else []
1562 return self.NAME_SEP.join(path_prefix + [repo_name])
1562 return self.NAME_SEP.join(path_prefix + [repo_name])
1563
1563
1564 @property
1564 @property
1565 def _config(self):
1565 def _config(self):
1566 """
1566 """
1567 Returns db based config object.
1567 Returns db based config object.
1568 """
1568 """
1569 from rhodecode.lib.utils import make_db_config
1569 from rhodecode.lib.utils import make_db_config
1570 return make_db_config(clear_session=False, repo=self)
1570 return make_db_config(clear_session=False, repo=self)
1571
1571
1572 def permissions(self, with_admins=True, with_owner=True):
1572 def permissions(self, with_admins=True, with_owner=True):
1573 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1573 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1574 q = q.options(joinedload(UserRepoToPerm.repository),
1574 q = q.options(joinedload(UserRepoToPerm.repository),
1575 joinedload(UserRepoToPerm.user),
1575 joinedload(UserRepoToPerm.user),
1576 joinedload(UserRepoToPerm.permission),)
1576 joinedload(UserRepoToPerm.permission),)
1577
1577
1578 # get owners and admins and permissions. We do a trick of re-writing
1578 # get owners and admins and permissions. We do a trick of re-writing
1579 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1579 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1580 # has a global reference and changing one object propagates to all
1580 # has a global reference and changing one object propagates to all
1581 # others. This means if admin is also an owner admin_row that change
1581 # others. This means if admin is also an owner admin_row that change
1582 # would propagate to both objects
1582 # would propagate to both objects
1583 perm_rows = []
1583 perm_rows = []
1584 for _usr in q.all():
1584 for _usr in q.all():
1585 usr = AttributeDict(_usr.user.get_dict())
1585 usr = AttributeDict(_usr.user.get_dict())
1586 usr.permission = _usr.permission.permission_name
1586 usr.permission = _usr.permission.permission_name
1587 perm_rows.append(usr)
1587 perm_rows.append(usr)
1588
1588
1589 # filter the perm rows by 'default' first and then sort them by
1589 # filter the perm rows by 'default' first and then sort them by
1590 # admin,write,read,none permissions sorted again alphabetically in
1590 # admin,write,read,none permissions sorted again alphabetically in
1591 # each group
1591 # each group
1592 perm_rows = sorted(perm_rows, key=display_sort)
1592 perm_rows = sorted(perm_rows, key=display_sort)
1593
1593
1594 _admin_perm = 'repository.admin'
1594 _admin_perm = 'repository.admin'
1595 owner_row = []
1595 owner_row = []
1596 if with_owner:
1596 if with_owner:
1597 usr = AttributeDict(self.user.get_dict())
1597 usr = AttributeDict(self.user.get_dict())
1598 usr.owner_row = True
1598 usr.owner_row = True
1599 usr.permission = _admin_perm
1599 usr.permission = _admin_perm
1600 owner_row.append(usr)
1600 owner_row.append(usr)
1601
1601
1602 super_admin_rows = []
1602 super_admin_rows = []
1603 if with_admins:
1603 if with_admins:
1604 for usr in User.get_all_super_admins():
1604 for usr in User.get_all_super_admins():
1605 # if this admin is also owner, don't double the record
1605 # if this admin is also owner, don't double the record
1606 if usr.user_id == owner_row[0].user_id:
1606 if usr.user_id == owner_row[0].user_id:
1607 owner_row[0].admin_row = True
1607 owner_row[0].admin_row = True
1608 else:
1608 else:
1609 usr = AttributeDict(usr.get_dict())
1609 usr = AttributeDict(usr.get_dict())
1610 usr.admin_row = True
1610 usr.admin_row = True
1611 usr.permission = _admin_perm
1611 usr.permission = _admin_perm
1612 super_admin_rows.append(usr)
1612 super_admin_rows.append(usr)
1613
1613
1614 return super_admin_rows + owner_row + perm_rows
1614 return super_admin_rows + owner_row + perm_rows
1615
1615
1616 def permission_user_groups(self):
1616 def permission_user_groups(self):
1617 q = UserGroupRepoToPerm.query().filter(
1617 q = UserGroupRepoToPerm.query().filter(
1618 UserGroupRepoToPerm.repository == self)
1618 UserGroupRepoToPerm.repository == self)
1619 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1619 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1620 joinedload(UserGroupRepoToPerm.users_group),
1620 joinedload(UserGroupRepoToPerm.users_group),
1621 joinedload(UserGroupRepoToPerm.permission),)
1621 joinedload(UserGroupRepoToPerm.permission),)
1622
1622
1623 perm_rows = []
1623 perm_rows = []
1624 for _user_group in q.all():
1624 for _user_group in q.all():
1625 usr = AttributeDict(_user_group.users_group.get_dict())
1625 usr = AttributeDict(_user_group.users_group.get_dict())
1626 usr.permission = _user_group.permission.permission_name
1626 usr.permission = _user_group.permission.permission_name
1627 perm_rows.append(usr)
1627 perm_rows.append(usr)
1628
1628
1629 return perm_rows
1629 return perm_rows
1630
1630
1631 def get_api_data(self, include_secrets=False):
1631 def get_api_data(self, include_secrets=False):
1632 """
1632 """
1633 Common function for generating repo api data
1633 Common function for generating repo api data
1634
1634
1635 :param include_secrets: See :meth:`User.get_api_data`.
1635 :param include_secrets: See :meth:`User.get_api_data`.
1636
1636
1637 """
1637 """
1638 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1638 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1639 # move this methods on models level.
1639 # move this methods on models level.
1640 from rhodecode.model.settings import SettingsModel
1640 from rhodecode.model.settings import SettingsModel
1641
1641
1642 repo = self
1642 repo = self
1643 _user_id, _time, _reason = self.locked
1643 _user_id, _time, _reason = self.locked
1644
1644
1645 data = {
1645 data = {
1646 'repo_id': repo.repo_id,
1646 'repo_id': repo.repo_id,
1647 'repo_name': repo.repo_name,
1647 'repo_name': repo.repo_name,
1648 'repo_type': repo.repo_type,
1648 'repo_type': repo.repo_type,
1649 'clone_uri': repo.clone_uri or '',
1649 'clone_uri': repo.clone_uri or '',
1650 'url': url('summary_home', repo_name=self.repo_name, qualified=True),
1650 'url': url('summary_home', repo_name=self.repo_name, qualified=True),
1651 'private': repo.private,
1651 'private': repo.private,
1652 'created_on': repo.created_on,
1652 'created_on': repo.created_on,
1653 'description': repo.description,
1653 'description': repo.description,
1654 'landing_rev': repo.landing_rev,
1654 'landing_rev': repo.landing_rev,
1655 'owner': repo.user.username,
1655 'owner': repo.user.username,
1656 'fork_of': repo.fork.repo_name if repo.fork else None,
1656 'fork_of': repo.fork.repo_name if repo.fork else None,
1657 'enable_statistics': repo.enable_statistics,
1657 'enable_statistics': repo.enable_statistics,
1658 'enable_locking': repo.enable_locking,
1658 'enable_locking': repo.enable_locking,
1659 'enable_downloads': repo.enable_downloads,
1659 'enable_downloads': repo.enable_downloads,
1660 'last_changeset': repo.changeset_cache,
1660 'last_changeset': repo.changeset_cache,
1661 'locked_by': User.get(_user_id).get_api_data(
1661 'locked_by': User.get(_user_id).get_api_data(
1662 include_secrets=include_secrets) if _user_id else None,
1662 include_secrets=include_secrets) if _user_id else None,
1663 'locked_date': time_to_datetime(_time) if _time else None,
1663 'locked_date': time_to_datetime(_time) if _time else None,
1664 'lock_reason': _reason if _reason else None,
1664 'lock_reason': _reason if _reason else None,
1665 }
1665 }
1666
1666
1667 # TODO: mikhail: should be per-repo settings here
1667 # TODO: mikhail: should be per-repo settings here
1668 rc_config = SettingsModel().get_all_settings()
1668 rc_config = SettingsModel().get_all_settings()
1669 repository_fields = str2bool(
1669 repository_fields = str2bool(
1670 rc_config.get('rhodecode_repository_fields'))
1670 rc_config.get('rhodecode_repository_fields'))
1671 if repository_fields:
1671 if repository_fields:
1672 for f in self.extra_fields:
1672 for f in self.extra_fields:
1673 data[f.field_key_prefixed] = f.field_value
1673 data[f.field_key_prefixed] = f.field_value
1674
1674
1675 return data
1675 return data
1676
1676
1677 @classmethod
1677 @classmethod
1678 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1678 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1679 if not lock_time:
1679 if not lock_time:
1680 lock_time = time.time()
1680 lock_time = time.time()
1681 if not lock_reason:
1681 if not lock_reason:
1682 lock_reason = cls.LOCK_AUTOMATIC
1682 lock_reason = cls.LOCK_AUTOMATIC
1683 repo.locked = [user_id, lock_time, lock_reason]
1683 repo.locked = [user_id, lock_time, lock_reason]
1684 Session().add(repo)
1684 Session().add(repo)
1685 Session().commit()
1685 Session().commit()
1686
1686
1687 @classmethod
1687 @classmethod
1688 def unlock(cls, repo):
1688 def unlock(cls, repo):
1689 repo.locked = None
1689 repo.locked = None
1690 Session().add(repo)
1690 Session().add(repo)
1691 Session().commit()
1691 Session().commit()
1692
1692
1693 @classmethod
1693 @classmethod
1694 def getlock(cls, repo):
1694 def getlock(cls, repo):
1695 return repo.locked
1695 return repo.locked
1696
1696
1697 def is_user_lock(self, user_id):
1697 def is_user_lock(self, user_id):
1698 if self.lock[0]:
1698 if self.lock[0]:
1699 lock_user_id = safe_int(self.lock[0])
1699 lock_user_id = safe_int(self.lock[0])
1700 user_id = safe_int(user_id)
1700 user_id = safe_int(user_id)
1701 # both are ints, and they are equal
1701 # both are ints, and they are equal
1702 return all([lock_user_id, user_id]) and lock_user_id == user_id
1702 return all([lock_user_id, user_id]) and lock_user_id == user_id
1703
1703
1704 return False
1704 return False
1705
1705
1706 def get_locking_state(self, action, user_id, only_when_enabled=True):
1706 def get_locking_state(self, action, user_id, only_when_enabled=True):
1707 """
1707 """
1708 Checks locking on this repository, if locking is enabled and lock is
1708 Checks locking on this repository, if locking is enabled and lock is
1709 present returns a tuple of make_lock, locked, locked_by.
1709 present returns a tuple of make_lock, locked, locked_by.
1710 make_lock can have 3 states None (do nothing) True, make lock
1710 make_lock can have 3 states None (do nothing) True, make lock
1711 False release lock, This value is later propagated to hooks, which
1711 False release lock, This value is later propagated to hooks, which
1712 do the locking. Think about this as signals passed to hooks what to do.
1712 do the locking. Think about this as signals passed to hooks what to do.
1713
1713
1714 """
1714 """
1715 # TODO: johbo: This is part of the business logic and should be moved
1715 # TODO: johbo: This is part of the business logic and should be moved
1716 # into the RepositoryModel.
1716 # into the RepositoryModel.
1717
1717
1718 if action not in ('push', 'pull'):
1718 if action not in ('push', 'pull'):
1719 raise ValueError("Invalid action value: %s" % repr(action))
1719 raise ValueError("Invalid action value: %s" % repr(action))
1720
1720
1721 # defines if locked error should be thrown to user
1721 # defines if locked error should be thrown to user
1722 currently_locked = False
1722 currently_locked = False
1723 # defines if new lock should be made, tri-state
1723 # defines if new lock should be made, tri-state
1724 make_lock = None
1724 make_lock = None
1725 repo = self
1725 repo = self
1726 user = User.get(user_id)
1726 user = User.get(user_id)
1727
1727
1728 lock_info = repo.locked
1728 lock_info = repo.locked
1729
1729
1730 if repo and (repo.enable_locking or not only_when_enabled):
1730 if repo and (repo.enable_locking or not only_when_enabled):
1731 if action == 'push':
1731 if action == 'push':
1732 # check if it's already locked !, if it is compare users
1732 # check if it's already locked !, if it is compare users
1733 locked_by_user_id = lock_info[0]
1733 locked_by_user_id = lock_info[0]
1734 if user.user_id == locked_by_user_id:
1734 if user.user_id == locked_by_user_id:
1735 log.debug(
1735 log.debug(
1736 'Got `push` action from user %s, now unlocking', user)
1736 'Got `push` action from user %s, now unlocking', user)
1737 # unlock if we have push from user who locked
1737 # unlock if we have push from user who locked
1738 make_lock = False
1738 make_lock = False
1739 else:
1739 else:
1740 # we're not the same user who locked, ban with
1740 # we're not the same user who locked, ban with
1741 # code defined in settings (default is 423 HTTP Locked) !
1741 # code defined in settings (default is 423 HTTP Locked) !
1742 log.debug('Repo %s is currently locked by %s', repo, user)
1742 log.debug('Repo %s is currently locked by %s', repo, user)
1743 currently_locked = True
1743 currently_locked = True
1744 elif action == 'pull':
1744 elif action == 'pull':
1745 # [0] user [1] date
1745 # [0] user [1] date
1746 if lock_info[0] and lock_info[1]:
1746 if lock_info[0] and lock_info[1]:
1747 log.debug('Repo %s is currently locked by %s', repo, user)
1747 log.debug('Repo %s is currently locked by %s', repo, user)
1748 currently_locked = True
1748 currently_locked = True
1749 else:
1749 else:
1750 log.debug('Setting lock on repo %s by %s', repo, user)
1750 log.debug('Setting lock on repo %s by %s', repo, user)
1751 make_lock = True
1751 make_lock = True
1752
1752
1753 else:
1753 else:
1754 log.debug('Repository %s do not have locking enabled', repo)
1754 log.debug('Repository %s do not have locking enabled', repo)
1755
1755
1756 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1756 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1757 make_lock, currently_locked, lock_info)
1757 make_lock, currently_locked, lock_info)
1758
1758
1759 from rhodecode.lib.auth import HasRepoPermissionAny
1759 from rhodecode.lib.auth import HasRepoPermissionAny
1760 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1760 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1761 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1761 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1762 # if we don't have at least write permission we cannot make a lock
1762 # if we don't have at least write permission we cannot make a lock
1763 log.debug('lock state reset back to FALSE due to lack '
1763 log.debug('lock state reset back to FALSE due to lack '
1764 'of at least read permission')
1764 'of at least read permission')
1765 make_lock = False
1765 make_lock = False
1766
1766
1767 return make_lock, currently_locked, lock_info
1767 return make_lock, currently_locked, lock_info
1768
1768
1769 @property
1769 @property
1770 def last_db_change(self):
1770 def last_db_change(self):
1771 return self.updated_on
1771 return self.updated_on
1772
1772
1773 @property
1773 @property
1774 def clone_uri_hidden(self):
1774 def clone_uri_hidden(self):
1775 clone_uri = self.clone_uri
1775 clone_uri = self.clone_uri
1776 if clone_uri:
1776 if clone_uri:
1777 import urlobject
1777 import urlobject
1778 url_obj = urlobject.URLObject(clone_uri)
1778 url_obj = urlobject.URLObject(clone_uri)
1779 if url_obj.password:
1779 if url_obj.password:
1780 clone_uri = url_obj.with_password('*****')
1780 clone_uri = url_obj.with_password('*****')
1781 return clone_uri
1781 return clone_uri
1782
1782
1783 def clone_url(self, **override):
1783 def clone_url(self, **override):
1784 qualified_home_url = url('home', qualified=True)
1784 qualified_home_url = url('home', qualified=True)
1785
1785
1786 uri_tmpl = None
1786 uri_tmpl = None
1787 if 'with_id' in override:
1787 if 'with_id' in override:
1788 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1788 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1789 del override['with_id']
1789 del override['with_id']
1790
1790
1791 if 'uri_tmpl' in override:
1791 if 'uri_tmpl' in override:
1792 uri_tmpl = override['uri_tmpl']
1792 uri_tmpl = override['uri_tmpl']
1793 del override['uri_tmpl']
1793 del override['uri_tmpl']
1794
1794
1795 # we didn't override our tmpl from **overrides
1795 # we didn't override our tmpl from **overrides
1796 if not uri_tmpl:
1796 if not uri_tmpl:
1797 uri_tmpl = self.DEFAULT_CLONE_URI
1797 uri_tmpl = self.DEFAULT_CLONE_URI
1798 try:
1798 try:
1799 from pylons import tmpl_context as c
1799 from pylons import tmpl_context as c
1800 uri_tmpl = c.clone_uri_tmpl
1800 uri_tmpl = c.clone_uri_tmpl
1801 except Exception:
1801 except Exception:
1802 # in any case if we call this outside of request context,
1802 # in any case if we call this outside of request context,
1803 # ie, not having tmpl_context set up
1803 # ie, not having tmpl_context set up
1804 pass
1804 pass
1805
1805
1806 return get_clone_url(uri_tmpl=uri_tmpl,
1806 return get_clone_url(uri_tmpl=uri_tmpl,
1807 qualifed_home_url=qualified_home_url,
1807 qualifed_home_url=qualified_home_url,
1808 repo_name=self.repo_name,
1808 repo_name=self.repo_name,
1809 repo_id=self.repo_id, **override)
1809 repo_id=self.repo_id, **override)
1810
1810
1811 def set_state(self, state):
1811 def set_state(self, state):
1812 self.repo_state = state
1812 self.repo_state = state
1813 Session().add(self)
1813 Session().add(self)
1814 #==========================================================================
1814 #==========================================================================
1815 # SCM PROPERTIES
1815 # SCM PROPERTIES
1816 #==========================================================================
1816 #==========================================================================
1817
1817
1818 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
1818 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
1819 return get_commit_safe(
1819 return get_commit_safe(
1820 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
1820 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
1821
1821
1822 def get_changeset(self, rev=None, pre_load=None):
1822 def get_changeset(self, rev=None, pre_load=None):
1823 warnings.warn("Use get_commit", DeprecationWarning)
1823 warnings.warn("Use get_commit", DeprecationWarning)
1824 commit_id = None
1824 commit_id = None
1825 commit_idx = None
1825 commit_idx = None
1826 if isinstance(rev, basestring):
1826 if isinstance(rev, basestring):
1827 commit_id = rev
1827 commit_id = rev
1828 else:
1828 else:
1829 commit_idx = rev
1829 commit_idx = rev
1830 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
1830 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
1831 pre_load=pre_load)
1831 pre_load=pre_load)
1832
1832
1833 def get_landing_commit(self):
1833 def get_landing_commit(self):
1834 """
1834 """
1835 Returns landing commit, or if that doesn't exist returns the tip
1835 Returns landing commit, or if that doesn't exist returns the tip
1836 """
1836 """
1837 _rev_type, _rev = self.landing_rev
1837 _rev_type, _rev = self.landing_rev
1838 commit = self.get_commit(_rev)
1838 commit = self.get_commit(_rev)
1839 if isinstance(commit, EmptyCommit):
1839 if isinstance(commit, EmptyCommit):
1840 return self.get_commit()
1840 return self.get_commit()
1841 return commit
1841 return commit
1842
1842
1843 def update_commit_cache(self, cs_cache=None, config=None):
1843 def update_commit_cache(self, cs_cache=None, config=None):
1844 """
1844 """
1845 Update cache of last changeset for repository, keys should be::
1845 Update cache of last changeset for repository, keys should be::
1846
1846
1847 short_id
1847 short_id
1848 raw_id
1848 raw_id
1849 revision
1849 revision
1850 parents
1850 parents
1851 message
1851 message
1852 date
1852 date
1853 author
1853 author
1854
1854
1855 :param cs_cache:
1855 :param cs_cache:
1856 """
1856 """
1857 from rhodecode.lib.vcs.backends.base import BaseChangeset
1857 from rhodecode.lib.vcs.backends.base import BaseChangeset
1858 if cs_cache is None:
1858 if cs_cache is None:
1859 # use no-cache version here
1859 # use no-cache version here
1860 scm_repo = self.scm_instance(cache=False, config=config)
1860 scm_repo = self.scm_instance(cache=False, config=config)
1861 if scm_repo:
1861 if scm_repo:
1862 cs_cache = scm_repo.get_commit(
1862 cs_cache = scm_repo.get_commit(
1863 pre_load=["author", "date", "message", "parents"])
1863 pre_load=["author", "date", "message", "parents"])
1864 else:
1864 else:
1865 cs_cache = EmptyCommit()
1865 cs_cache = EmptyCommit()
1866
1866
1867 if isinstance(cs_cache, BaseChangeset):
1867 if isinstance(cs_cache, BaseChangeset):
1868 cs_cache = cs_cache.__json__()
1868 cs_cache = cs_cache.__json__()
1869
1869
1870 def is_outdated(new_cs_cache):
1870 def is_outdated(new_cs_cache):
1871 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
1871 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
1872 new_cs_cache['revision'] != self.changeset_cache['revision']):
1872 new_cs_cache['revision'] != self.changeset_cache['revision']):
1873 return True
1873 return True
1874 return False
1874 return False
1875
1875
1876 # check if we have maybe already latest cached revision
1876 # check if we have maybe already latest cached revision
1877 if is_outdated(cs_cache) or not self.changeset_cache:
1877 if is_outdated(cs_cache) or not self.changeset_cache:
1878 _default = datetime.datetime.fromtimestamp(0)
1878 _default = datetime.datetime.fromtimestamp(0)
1879 last_change = cs_cache.get('date') or _default
1879 last_change = cs_cache.get('date') or _default
1880 log.debug('updated repo %s with new cs cache %s',
1880 log.debug('updated repo %s with new cs cache %s',
1881 self.repo_name, cs_cache)
1881 self.repo_name, cs_cache)
1882 self.updated_on = last_change
1882 self.updated_on = last_change
1883 self.changeset_cache = cs_cache
1883 self.changeset_cache = cs_cache
1884 Session().add(self)
1884 Session().add(self)
1885 Session().commit()
1885 Session().commit()
1886 else:
1886 else:
1887 log.debug('Skipping update_commit_cache for repo:`%s` '
1887 log.debug('Skipping update_commit_cache for repo:`%s` '
1888 'commit already with latest changes', self.repo_name)
1888 'commit already with latest changes', self.repo_name)
1889
1889
1890 @property
1890 @property
1891 def tip(self):
1891 def tip(self):
1892 return self.get_commit('tip')
1892 return self.get_commit('tip')
1893
1893
1894 @property
1894 @property
1895 def author(self):
1895 def author(self):
1896 return self.tip.author
1896 return self.tip.author
1897
1897
1898 @property
1898 @property
1899 def last_change(self):
1899 def last_change(self):
1900 return self.scm_instance().last_change
1900 return self.scm_instance().last_change
1901
1901
1902 def get_comments(self, revisions=None):
1902 def get_comments(self, revisions=None):
1903 """
1903 """
1904 Returns comments for this repository grouped by revisions
1904 Returns comments for this repository grouped by revisions
1905
1905
1906 :param revisions: filter query by revisions only
1906 :param revisions: filter query by revisions only
1907 """
1907 """
1908 cmts = ChangesetComment.query()\
1908 cmts = ChangesetComment.query()\
1909 .filter(ChangesetComment.repo == self)
1909 .filter(ChangesetComment.repo == self)
1910 if revisions:
1910 if revisions:
1911 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1911 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1912 grouped = collections.defaultdict(list)
1912 grouped = collections.defaultdict(list)
1913 for cmt in cmts.all():
1913 for cmt in cmts.all():
1914 grouped[cmt.revision].append(cmt)
1914 grouped[cmt.revision].append(cmt)
1915 return grouped
1915 return grouped
1916
1916
1917 def statuses(self, revisions=None):
1917 def statuses(self, revisions=None):
1918 """
1918 """
1919 Returns statuses for this repository
1919 Returns statuses for this repository
1920
1920
1921 :param revisions: list of revisions to get statuses for
1921 :param revisions: list of revisions to get statuses for
1922 """
1922 """
1923 statuses = ChangesetStatus.query()\
1923 statuses = ChangesetStatus.query()\
1924 .filter(ChangesetStatus.repo == self)\
1924 .filter(ChangesetStatus.repo == self)\
1925 .filter(ChangesetStatus.version == 0)
1925 .filter(ChangesetStatus.version == 0)
1926
1926
1927 if revisions:
1927 if revisions:
1928 # Try doing the filtering in chunks to avoid hitting limits
1928 # Try doing the filtering in chunks to avoid hitting limits
1929 size = 500
1929 size = 500
1930 status_results = []
1930 status_results = []
1931 for chunk in xrange(0, len(revisions), size):
1931 for chunk in xrange(0, len(revisions), size):
1932 status_results += statuses.filter(
1932 status_results += statuses.filter(
1933 ChangesetStatus.revision.in_(
1933 ChangesetStatus.revision.in_(
1934 revisions[chunk: chunk+size])
1934 revisions[chunk: chunk+size])
1935 ).all()
1935 ).all()
1936 else:
1936 else:
1937 status_results = statuses.all()
1937 status_results = statuses.all()
1938
1938
1939 grouped = {}
1939 grouped = {}
1940
1940
1941 # maybe we have open new pullrequest without a status?
1941 # maybe we have open new pullrequest without a status?
1942 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1942 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1943 status_lbl = ChangesetStatus.get_status_lbl(stat)
1943 status_lbl = ChangesetStatus.get_status_lbl(stat)
1944 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
1944 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
1945 for rev in pr.revisions:
1945 for rev in pr.revisions:
1946 pr_id = pr.pull_request_id
1946 pr_id = pr.pull_request_id
1947 pr_repo = pr.target_repo.repo_name
1947 pr_repo = pr.target_repo.repo_name
1948 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1948 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1949
1949
1950 for stat in status_results:
1950 for stat in status_results:
1951 pr_id = pr_repo = None
1951 pr_id = pr_repo = None
1952 if stat.pull_request:
1952 if stat.pull_request:
1953 pr_id = stat.pull_request.pull_request_id
1953 pr_id = stat.pull_request.pull_request_id
1954 pr_repo = stat.pull_request.target_repo.repo_name
1954 pr_repo = stat.pull_request.target_repo.repo_name
1955 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1955 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1956 pr_id, pr_repo]
1956 pr_id, pr_repo]
1957 return grouped
1957 return grouped
1958
1958
1959 # ==========================================================================
1959 # ==========================================================================
1960 # SCM CACHE INSTANCE
1960 # SCM CACHE INSTANCE
1961 # ==========================================================================
1961 # ==========================================================================
1962
1962
1963 def scm_instance(self, **kwargs):
1963 def scm_instance(self, **kwargs):
1964 import rhodecode
1964 import rhodecode
1965
1965
1966 # Passing a config will not hit the cache currently only used
1966 # Passing a config will not hit the cache currently only used
1967 # for repo2dbmapper
1967 # for repo2dbmapper
1968 config = kwargs.pop('config', None)
1968 config = kwargs.pop('config', None)
1969 cache = kwargs.pop('cache', None)
1969 cache = kwargs.pop('cache', None)
1970 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1970 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1971 # if cache is NOT defined use default global, else we have a full
1971 # if cache is NOT defined use default global, else we have a full
1972 # control over cache behaviour
1972 # control over cache behaviour
1973 if cache is None and full_cache and not config:
1973 if cache is None and full_cache and not config:
1974 return self._get_instance_cached()
1974 return self._get_instance_cached()
1975 return self._get_instance(cache=bool(cache), config=config)
1975 return self._get_instance(cache=bool(cache), config=config)
1976
1976
1977 def _get_instance_cached(self):
1977 def _get_instance_cached(self):
1978 @cache_region('long_term')
1978 @cache_region('long_term')
1979 def _get_repo(cache_key):
1979 def _get_repo(cache_key):
1980 return self._get_instance()
1980 return self._get_instance()
1981
1981
1982 invalidator_context = CacheKey.repo_context_cache(
1982 invalidator_context = CacheKey.repo_context_cache(
1983 _get_repo, self.repo_name, None, thread_scoped=True)
1983 _get_repo, self.repo_name, None, thread_scoped=True)
1984
1984
1985 with invalidator_context as context:
1985 with invalidator_context as context:
1986 context.invalidate()
1986 context.invalidate()
1987 repo = context.compute()
1987 repo = context.compute()
1988
1988
1989 return repo
1989 return repo
1990
1990
1991 def _get_instance(self, cache=True, config=None):
1991 def _get_instance(self, cache=True, config=None):
1992 config = config or self._config
1992 config = config or self._config
1993 custom_wire = {
1993 custom_wire = {
1994 'cache': cache # controls the vcs.remote cache
1994 'cache': cache # controls the vcs.remote cache
1995 }
1995 }
1996 repo = get_vcs_instance(
1996 repo = get_vcs_instance(
1997 repo_path=safe_str(self.repo_full_path),
1997 repo_path=safe_str(self.repo_full_path),
1998 config=config,
1998 config=config,
1999 with_wire=custom_wire,
1999 with_wire=custom_wire,
2000 create=False,
2000 create=False,
2001 _vcs_alias=self.repo_type)
2001 _vcs_alias=self.repo_type)
2002
2002
2003 return repo
2003 return repo
2004
2004
2005 def __json__(self):
2005 def __json__(self):
2006 return {'landing_rev': self.landing_rev}
2006 return {'landing_rev': self.landing_rev}
2007
2007
2008 def get_dict(self):
2008 def get_dict(self):
2009
2009
2010 # Since we transformed `repo_name` to a hybrid property, we need to
2010 # Since we transformed `repo_name` to a hybrid property, we need to
2011 # keep compatibility with the code which uses `repo_name` field.
2011 # keep compatibility with the code which uses `repo_name` field.
2012
2012
2013 result = super(Repository, self).get_dict()
2013 result = super(Repository, self).get_dict()
2014 result['repo_name'] = result.pop('_repo_name', None)
2014 result['repo_name'] = result.pop('_repo_name', None)
2015 return result
2015 return result
2016
2016
2017
2017
2018 class RepoGroup(Base, BaseModel):
2018 class RepoGroup(Base, BaseModel):
2019 __tablename__ = 'groups'
2019 __tablename__ = 'groups'
2020 __table_args__ = (
2020 __table_args__ = (
2021 UniqueConstraint('group_name', 'group_parent_id'),
2021 UniqueConstraint('group_name', 'group_parent_id'),
2022 CheckConstraint('group_id != group_parent_id'),
2022 CheckConstraint('group_id != group_parent_id'),
2023 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2023 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2024 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2024 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2025 )
2025 )
2026 __mapper_args__ = {'order_by': 'group_name'}
2026 __mapper_args__ = {'order_by': 'group_name'}
2027
2027
2028 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2028 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2029
2029
2030 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2030 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2031 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2031 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2032 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2032 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2033 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2033 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2034 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2034 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2035 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2035 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2036 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2036 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2037 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2037 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2038
2038
2039 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2039 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2040 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2040 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2041 parent_group = relationship('RepoGroup', remote_side=group_id)
2041 parent_group = relationship('RepoGroup', remote_side=group_id)
2042 user = relationship('User')
2042 user = relationship('User')
2043 integrations = relationship('Integration',
2043 integrations = relationship('Integration',
2044 cascade="all, delete, delete-orphan")
2044 cascade="all, delete, delete-orphan")
2045
2045
2046 def __init__(self, group_name='', parent_group=None):
2046 def __init__(self, group_name='', parent_group=None):
2047 self.group_name = group_name
2047 self.group_name = group_name
2048 self.parent_group = parent_group
2048 self.parent_group = parent_group
2049
2049
2050 def __unicode__(self):
2050 def __unicode__(self):
2051 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
2051 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
2052 self.group_name)
2052 self.group_name)
2053
2053
2054 @classmethod
2054 @classmethod
2055 def _generate_choice(cls, repo_group):
2055 def _generate_choice(cls, repo_group):
2056 from webhelpers.html import literal as _literal
2056 from webhelpers.html import literal as _literal
2057 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2057 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2058 return repo_group.group_id, _name(repo_group.full_path_splitted)
2058 return repo_group.group_id, _name(repo_group.full_path_splitted)
2059
2059
2060 @classmethod
2060 @classmethod
2061 def groups_choices(cls, groups=None, show_empty_group=True):
2061 def groups_choices(cls, groups=None, show_empty_group=True):
2062 if not groups:
2062 if not groups:
2063 groups = cls.query().all()
2063 groups = cls.query().all()
2064
2064
2065 repo_groups = []
2065 repo_groups = []
2066 if show_empty_group:
2066 if show_empty_group:
2067 repo_groups = [('-1', u'-- %s --' % _('No parent'))]
2067 repo_groups = [('-1', u'-- %s --' % _('No parent'))]
2068
2068
2069 repo_groups.extend([cls._generate_choice(x) for x in groups])
2069 repo_groups.extend([cls._generate_choice(x) for x in groups])
2070
2070
2071 repo_groups = sorted(
2071 repo_groups = sorted(
2072 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2072 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2073 return repo_groups
2073 return repo_groups
2074
2074
2075 @classmethod
2075 @classmethod
2076 def url_sep(cls):
2076 def url_sep(cls):
2077 return URL_SEP
2077 return URL_SEP
2078
2078
2079 @classmethod
2079 @classmethod
2080 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2080 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2081 if case_insensitive:
2081 if case_insensitive:
2082 gr = cls.query().filter(func.lower(cls.group_name)
2082 gr = cls.query().filter(func.lower(cls.group_name)
2083 == func.lower(group_name))
2083 == func.lower(group_name))
2084 else:
2084 else:
2085 gr = cls.query().filter(cls.group_name == group_name)
2085 gr = cls.query().filter(cls.group_name == group_name)
2086 if cache:
2086 if cache:
2087 gr = gr.options(FromCache(
2087 gr = gr.options(FromCache(
2088 "sql_cache_short",
2088 "sql_cache_short",
2089 "get_group_%s" % _hash_key(group_name)))
2089 "get_group_%s" % _hash_key(group_name)))
2090 return gr.scalar()
2090 return gr.scalar()
2091
2091
2092 @classmethod
2092 @classmethod
2093 def get_user_personal_repo_group(cls, user_id):
2093 def get_user_personal_repo_group(cls, user_id):
2094 user = User.get(user_id)
2094 user = User.get(user_id)
2095 return cls.query()\
2095 return cls.query()\
2096 .filter(cls.personal == true())\
2096 .filter(cls.personal == true())\
2097 .filter(cls.user == user).scalar()
2097 .filter(cls.user == user).scalar()
2098
2098
2099 @classmethod
2099 @classmethod
2100 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2100 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2101 case_insensitive=True):
2101 case_insensitive=True):
2102 q = RepoGroup.query()
2102 q = RepoGroup.query()
2103
2103
2104 if not isinstance(user_id, Optional):
2104 if not isinstance(user_id, Optional):
2105 q = q.filter(RepoGroup.user_id == user_id)
2105 q = q.filter(RepoGroup.user_id == user_id)
2106
2106
2107 if not isinstance(group_id, Optional):
2107 if not isinstance(group_id, Optional):
2108 q = q.filter(RepoGroup.group_parent_id == group_id)
2108 q = q.filter(RepoGroup.group_parent_id == group_id)
2109
2109
2110 if case_insensitive:
2110 if case_insensitive:
2111 q = q.order_by(func.lower(RepoGroup.group_name))
2111 q = q.order_by(func.lower(RepoGroup.group_name))
2112 else:
2112 else:
2113 q = q.order_by(RepoGroup.group_name)
2113 q = q.order_by(RepoGroup.group_name)
2114 return q.all()
2114 return q.all()
2115
2115
2116 @property
2116 @property
2117 def parents(self):
2117 def parents(self):
2118 parents_recursion_limit = 10
2118 parents_recursion_limit = 10
2119 groups = []
2119 groups = []
2120 if self.parent_group is None:
2120 if self.parent_group is None:
2121 return groups
2121 return groups
2122 cur_gr = self.parent_group
2122 cur_gr = self.parent_group
2123 groups.insert(0, cur_gr)
2123 groups.insert(0, cur_gr)
2124 cnt = 0
2124 cnt = 0
2125 while 1:
2125 while 1:
2126 cnt += 1
2126 cnt += 1
2127 gr = getattr(cur_gr, 'parent_group', None)
2127 gr = getattr(cur_gr, 'parent_group', None)
2128 cur_gr = cur_gr.parent_group
2128 cur_gr = cur_gr.parent_group
2129 if gr is None:
2129 if gr is None:
2130 break
2130 break
2131 if cnt == parents_recursion_limit:
2131 if cnt == parents_recursion_limit:
2132 # this will prevent accidental infinit loops
2132 # this will prevent accidental infinit loops
2133 log.error(('more than %s parents found for group %s, stopping '
2133 log.error(('more than %s parents found for group %s, stopping '
2134 'recursive parent fetching' % (parents_recursion_limit, self)))
2134 'recursive parent fetching' % (parents_recursion_limit, self)))
2135 break
2135 break
2136
2136
2137 groups.insert(0, gr)
2137 groups.insert(0, gr)
2138 return groups
2138 return groups
2139
2139
2140 @property
2140 @property
2141 def children(self):
2141 def children(self):
2142 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2142 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2143
2143
2144 @property
2144 @property
2145 def name(self):
2145 def name(self):
2146 return self.group_name.split(RepoGroup.url_sep())[-1]
2146 return self.group_name.split(RepoGroup.url_sep())[-1]
2147
2147
2148 @property
2148 @property
2149 def full_path(self):
2149 def full_path(self):
2150 return self.group_name
2150 return self.group_name
2151
2151
2152 @property
2152 @property
2153 def full_path_splitted(self):
2153 def full_path_splitted(self):
2154 return self.group_name.split(RepoGroup.url_sep())
2154 return self.group_name.split(RepoGroup.url_sep())
2155
2155
2156 @property
2156 @property
2157 def repositories(self):
2157 def repositories(self):
2158 return Repository.query()\
2158 return Repository.query()\
2159 .filter(Repository.group == self)\
2159 .filter(Repository.group == self)\
2160 .order_by(Repository.repo_name)
2160 .order_by(Repository.repo_name)
2161
2161
2162 @property
2162 @property
2163 def repositories_recursive_count(self):
2163 def repositories_recursive_count(self):
2164 cnt = self.repositories.count()
2164 cnt = self.repositories.count()
2165
2165
2166 def children_count(group):
2166 def children_count(group):
2167 cnt = 0
2167 cnt = 0
2168 for child in group.children:
2168 for child in group.children:
2169 cnt += child.repositories.count()
2169 cnt += child.repositories.count()
2170 cnt += children_count(child)
2170 cnt += children_count(child)
2171 return cnt
2171 return cnt
2172
2172
2173 return cnt + children_count(self)
2173 return cnt + children_count(self)
2174
2174
2175 def _recursive_objects(self, include_repos=True):
2175 def _recursive_objects(self, include_repos=True):
2176 all_ = []
2176 all_ = []
2177
2177
2178 def _get_members(root_gr):
2178 def _get_members(root_gr):
2179 if include_repos:
2179 if include_repos:
2180 for r in root_gr.repositories:
2180 for r in root_gr.repositories:
2181 all_.append(r)
2181 all_.append(r)
2182 childs = root_gr.children.all()
2182 childs = root_gr.children.all()
2183 if childs:
2183 if childs:
2184 for gr in childs:
2184 for gr in childs:
2185 all_.append(gr)
2185 all_.append(gr)
2186 _get_members(gr)
2186 _get_members(gr)
2187
2187
2188 _get_members(self)
2188 _get_members(self)
2189 return [self] + all_
2189 return [self] + all_
2190
2190
2191 def recursive_groups_and_repos(self):
2191 def recursive_groups_and_repos(self):
2192 """
2192 """
2193 Recursive return all groups, with repositories in those groups
2193 Recursive return all groups, with repositories in those groups
2194 """
2194 """
2195 return self._recursive_objects()
2195 return self._recursive_objects()
2196
2196
2197 def recursive_groups(self):
2197 def recursive_groups(self):
2198 """
2198 """
2199 Returns all children groups for this group including children of children
2199 Returns all children groups for this group including children of children
2200 """
2200 """
2201 return self._recursive_objects(include_repos=False)
2201 return self._recursive_objects(include_repos=False)
2202
2202
2203 def get_new_name(self, group_name):
2203 def get_new_name(self, group_name):
2204 """
2204 """
2205 returns new full group name based on parent and new name
2205 returns new full group name based on parent and new name
2206
2206
2207 :param group_name:
2207 :param group_name:
2208 """
2208 """
2209 path_prefix = (self.parent_group.full_path_splitted if
2209 path_prefix = (self.parent_group.full_path_splitted if
2210 self.parent_group else [])
2210 self.parent_group else [])
2211 return RepoGroup.url_sep().join(path_prefix + [group_name])
2211 return RepoGroup.url_sep().join(path_prefix + [group_name])
2212
2212
2213 def permissions(self, with_admins=True, with_owner=True):
2213 def permissions(self, with_admins=True, with_owner=True):
2214 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2214 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2215 q = q.options(joinedload(UserRepoGroupToPerm.group),
2215 q = q.options(joinedload(UserRepoGroupToPerm.group),
2216 joinedload(UserRepoGroupToPerm.user),
2216 joinedload(UserRepoGroupToPerm.user),
2217 joinedload(UserRepoGroupToPerm.permission),)
2217 joinedload(UserRepoGroupToPerm.permission),)
2218
2218
2219 # get owners and admins and permissions. We do a trick of re-writing
2219 # get owners and admins and permissions. We do a trick of re-writing
2220 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2220 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2221 # has a global reference and changing one object propagates to all
2221 # has a global reference and changing one object propagates to all
2222 # others. This means if admin is also an owner admin_row that change
2222 # others. This means if admin is also an owner admin_row that change
2223 # would propagate to both objects
2223 # would propagate to both objects
2224 perm_rows = []
2224 perm_rows = []
2225 for _usr in q.all():
2225 for _usr in q.all():
2226 usr = AttributeDict(_usr.user.get_dict())
2226 usr = AttributeDict(_usr.user.get_dict())
2227 usr.permission = _usr.permission.permission_name
2227 usr.permission = _usr.permission.permission_name
2228 perm_rows.append(usr)
2228 perm_rows.append(usr)
2229
2229
2230 # filter the perm rows by 'default' first and then sort them by
2230 # filter the perm rows by 'default' first and then sort them by
2231 # admin,write,read,none permissions sorted again alphabetically in
2231 # admin,write,read,none permissions sorted again alphabetically in
2232 # each group
2232 # each group
2233 perm_rows = sorted(perm_rows, key=display_sort)
2233 perm_rows = sorted(perm_rows, key=display_sort)
2234
2234
2235 _admin_perm = 'group.admin'
2235 _admin_perm = 'group.admin'
2236 owner_row = []
2236 owner_row = []
2237 if with_owner:
2237 if with_owner:
2238 usr = AttributeDict(self.user.get_dict())
2238 usr = AttributeDict(self.user.get_dict())
2239 usr.owner_row = True
2239 usr.owner_row = True
2240 usr.permission = _admin_perm
2240 usr.permission = _admin_perm
2241 owner_row.append(usr)
2241 owner_row.append(usr)
2242
2242
2243 super_admin_rows = []
2243 super_admin_rows = []
2244 if with_admins:
2244 if with_admins:
2245 for usr in User.get_all_super_admins():
2245 for usr in User.get_all_super_admins():
2246 # if this admin is also owner, don't double the record
2246 # if this admin is also owner, don't double the record
2247 if usr.user_id == owner_row[0].user_id:
2247 if usr.user_id == owner_row[0].user_id:
2248 owner_row[0].admin_row = True
2248 owner_row[0].admin_row = True
2249 else:
2249 else:
2250 usr = AttributeDict(usr.get_dict())
2250 usr = AttributeDict(usr.get_dict())
2251 usr.admin_row = True
2251 usr.admin_row = True
2252 usr.permission = _admin_perm
2252 usr.permission = _admin_perm
2253 super_admin_rows.append(usr)
2253 super_admin_rows.append(usr)
2254
2254
2255 return super_admin_rows + owner_row + perm_rows
2255 return super_admin_rows + owner_row + perm_rows
2256
2256
2257 def permission_user_groups(self):
2257 def permission_user_groups(self):
2258 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2258 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2259 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2259 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2260 joinedload(UserGroupRepoGroupToPerm.users_group),
2260 joinedload(UserGroupRepoGroupToPerm.users_group),
2261 joinedload(UserGroupRepoGroupToPerm.permission),)
2261 joinedload(UserGroupRepoGroupToPerm.permission),)
2262
2262
2263 perm_rows = []
2263 perm_rows = []
2264 for _user_group in q.all():
2264 for _user_group in q.all():
2265 usr = AttributeDict(_user_group.users_group.get_dict())
2265 usr = AttributeDict(_user_group.users_group.get_dict())
2266 usr.permission = _user_group.permission.permission_name
2266 usr.permission = _user_group.permission.permission_name
2267 perm_rows.append(usr)
2267 perm_rows.append(usr)
2268
2268
2269 return perm_rows
2269 return perm_rows
2270
2270
2271 def get_api_data(self):
2271 def get_api_data(self):
2272 """
2272 """
2273 Common function for generating api data
2273 Common function for generating api data
2274
2274
2275 """
2275 """
2276 group = self
2276 group = self
2277 data = {
2277 data = {
2278 'group_id': group.group_id,
2278 'group_id': group.group_id,
2279 'group_name': group.group_name,
2279 'group_name': group.group_name,
2280 'group_description': group.group_description,
2280 'group_description': group.group_description,
2281 'parent_group': group.parent_group.group_name if group.parent_group else None,
2281 'parent_group': group.parent_group.group_name if group.parent_group else None,
2282 'repositories': [x.repo_name for x in group.repositories],
2282 'repositories': [x.repo_name for x in group.repositories],
2283 'owner': group.user.username,
2283 'owner': group.user.username,
2284 }
2284 }
2285 return data
2285 return data
2286
2286
2287
2287
2288 class Permission(Base, BaseModel):
2288 class Permission(Base, BaseModel):
2289 __tablename__ = 'permissions'
2289 __tablename__ = 'permissions'
2290 __table_args__ = (
2290 __table_args__ = (
2291 Index('p_perm_name_idx', 'permission_name'),
2291 Index('p_perm_name_idx', 'permission_name'),
2292 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2292 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2293 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2293 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2294 )
2294 )
2295 PERMS = [
2295 PERMS = [
2296 ('hg.admin', _('RhodeCode Super Administrator')),
2296 ('hg.admin', _('RhodeCode Super Administrator')),
2297
2297
2298 ('repository.none', _('Repository no access')),
2298 ('repository.none', _('Repository no access')),
2299 ('repository.read', _('Repository read access')),
2299 ('repository.read', _('Repository read access')),
2300 ('repository.write', _('Repository write access')),
2300 ('repository.write', _('Repository write access')),
2301 ('repository.admin', _('Repository admin access')),
2301 ('repository.admin', _('Repository admin access')),
2302
2302
2303 ('group.none', _('Repository group no access')),
2303 ('group.none', _('Repository group no access')),
2304 ('group.read', _('Repository group read access')),
2304 ('group.read', _('Repository group read access')),
2305 ('group.write', _('Repository group write access')),
2305 ('group.write', _('Repository group write access')),
2306 ('group.admin', _('Repository group admin access')),
2306 ('group.admin', _('Repository group admin access')),
2307
2307
2308 ('usergroup.none', _('User group no access')),
2308 ('usergroup.none', _('User group no access')),
2309 ('usergroup.read', _('User group read access')),
2309 ('usergroup.read', _('User group read access')),
2310 ('usergroup.write', _('User group write access')),
2310 ('usergroup.write', _('User group write access')),
2311 ('usergroup.admin', _('User group admin access')),
2311 ('usergroup.admin', _('User group admin access')),
2312
2312
2313 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2313 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2314 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2314 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2315
2315
2316 ('hg.usergroup.create.false', _('User Group creation disabled')),
2316 ('hg.usergroup.create.false', _('User Group creation disabled')),
2317 ('hg.usergroup.create.true', _('User Group creation enabled')),
2317 ('hg.usergroup.create.true', _('User Group creation enabled')),
2318
2318
2319 ('hg.create.none', _('Repository creation disabled')),
2319 ('hg.create.none', _('Repository creation disabled')),
2320 ('hg.create.repository', _('Repository creation enabled')),
2320 ('hg.create.repository', _('Repository creation enabled')),
2321 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2321 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2322 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2322 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2323
2323
2324 ('hg.fork.none', _('Repository forking disabled')),
2324 ('hg.fork.none', _('Repository forking disabled')),
2325 ('hg.fork.repository', _('Repository forking enabled')),
2325 ('hg.fork.repository', _('Repository forking enabled')),
2326
2326
2327 ('hg.register.none', _('Registration disabled')),
2327 ('hg.register.none', _('Registration disabled')),
2328 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2328 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2329 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2329 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2330
2330
2331 ('hg.password_reset.enabled', _('Password reset enabled')),
2331 ('hg.password_reset.enabled', _('Password reset enabled')),
2332 ('hg.password_reset.hidden', _('Password reset hidden')),
2332 ('hg.password_reset.hidden', _('Password reset hidden')),
2333 ('hg.password_reset.disabled', _('Password reset disabled')),
2333 ('hg.password_reset.disabled', _('Password reset disabled')),
2334
2334
2335 ('hg.extern_activate.manual', _('Manual activation of external account')),
2335 ('hg.extern_activate.manual', _('Manual activation of external account')),
2336 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2336 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2337
2337
2338 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2338 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2339 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2339 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2340 ]
2340 ]
2341
2341
2342 # definition of system default permissions for DEFAULT user
2342 # definition of system default permissions for DEFAULT user
2343 DEFAULT_USER_PERMISSIONS = [
2343 DEFAULT_USER_PERMISSIONS = [
2344 'repository.read',
2344 'repository.read',
2345 'group.read',
2345 'group.read',
2346 'usergroup.read',
2346 'usergroup.read',
2347 'hg.create.repository',
2347 'hg.create.repository',
2348 'hg.repogroup.create.false',
2348 'hg.repogroup.create.false',
2349 'hg.usergroup.create.false',
2349 'hg.usergroup.create.false',
2350 'hg.create.write_on_repogroup.true',
2350 'hg.create.write_on_repogroup.true',
2351 'hg.fork.repository',
2351 'hg.fork.repository',
2352 'hg.register.manual_activate',
2352 'hg.register.manual_activate',
2353 'hg.password_reset.enabled',
2353 'hg.password_reset.enabled',
2354 'hg.extern_activate.auto',
2354 'hg.extern_activate.auto',
2355 'hg.inherit_default_perms.true',
2355 'hg.inherit_default_perms.true',
2356 ]
2356 ]
2357
2357
2358 # defines which permissions are more important higher the more important
2358 # defines which permissions are more important higher the more important
2359 # Weight defines which permissions are more important.
2359 # Weight defines which permissions are more important.
2360 # The higher number the more important.
2360 # The higher number the more important.
2361 PERM_WEIGHTS = {
2361 PERM_WEIGHTS = {
2362 'repository.none': 0,
2362 'repository.none': 0,
2363 'repository.read': 1,
2363 'repository.read': 1,
2364 'repository.write': 3,
2364 'repository.write': 3,
2365 'repository.admin': 4,
2365 'repository.admin': 4,
2366
2366
2367 'group.none': 0,
2367 'group.none': 0,
2368 'group.read': 1,
2368 'group.read': 1,
2369 'group.write': 3,
2369 'group.write': 3,
2370 'group.admin': 4,
2370 'group.admin': 4,
2371
2371
2372 'usergroup.none': 0,
2372 'usergroup.none': 0,
2373 'usergroup.read': 1,
2373 'usergroup.read': 1,
2374 'usergroup.write': 3,
2374 'usergroup.write': 3,
2375 'usergroup.admin': 4,
2375 'usergroup.admin': 4,
2376
2376
2377 'hg.repogroup.create.false': 0,
2377 'hg.repogroup.create.false': 0,
2378 'hg.repogroup.create.true': 1,
2378 'hg.repogroup.create.true': 1,
2379
2379
2380 'hg.usergroup.create.false': 0,
2380 'hg.usergroup.create.false': 0,
2381 'hg.usergroup.create.true': 1,
2381 'hg.usergroup.create.true': 1,
2382
2382
2383 'hg.fork.none': 0,
2383 'hg.fork.none': 0,
2384 'hg.fork.repository': 1,
2384 'hg.fork.repository': 1,
2385 'hg.create.none': 0,
2385 'hg.create.none': 0,
2386 'hg.create.repository': 1
2386 'hg.create.repository': 1
2387 }
2387 }
2388
2388
2389 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2389 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2390 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2390 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2391 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2391 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2392
2392
2393 def __unicode__(self):
2393 def __unicode__(self):
2394 return u"<%s('%s:%s')>" % (
2394 return u"<%s('%s:%s')>" % (
2395 self.__class__.__name__, self.permission_id, self.permission_name
2395 self.__class__.__name__, self.permission_id, self.permission_name
2396 )
2396 )
2397
2397
2398 @classmethod
2398 @classmethod
2399 def get_by_key(cls, key):
2399 def get_by_key(cls, key):
2400 return cls.query().filter(cls.permission_name == key).scalar()
2400 return cls.query().filter(cls.permission_name == key).scalar()
2401
2401
2402 @classmethod
2402 @classmethod
2403 def get_default_repo_perms(cls, user_id, repo_id=None):
2403 def get_default_repo_perms(cls, user_id, repo_id=None):
2404 q = Session().query(UserRepoToPerm, Repository, Permission)\
2404 q = Session().query(UserRepoToPerm, Repository, Permission)\
2405 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2405 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2406 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2406 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2407 .filter(UserRepoToPerm.user_id == user_id)
2407 .filter(UserRepoToPerm.user_id == user_id)
2408 if repo_id:
2408 if repo_id:
2409 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2409 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2410 return q.all()
2410 return q.all()
2411
2411
2412 @classmethod
2412 @classmethod
2413 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2413 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2414 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2414 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2415 .join(
2415 .join(
2416 Permission,
2416 Permission,
2417 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2417 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2418 .join(
2418 .join(
2419 Repository,
2419 Repository,
2420 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2420 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2421 .join(
2421 .join(
2422 UserGroup,
2422 UserGroup,
2423 UserGroupRepoToPerm.users_group_id ==
2423 UserGroupRepoToPerm.users_group_id ==
2424 UserGroup.users_group_id)\
2424 UserGroup.users_group_id)\
2425 .join(
2425 .join(
2426 UserGroupMember,
2426 UserGroupMember,
2427 UserGroupRepoToPerm.users_group_id ==
2427 UserGroupRepoToPerm.users_group_id ==
2428 UserGroupMember.users_group_id)\
2428 UserGroupMember.users_group_id)\
2429 .filter(
2429 .filter(
2430 UserGroupMember.user_id == user_id,
2430 UserGroupMember.user_id == user_id,
2431 UserGroup.users_group_active == true())
2431 UserGroup.users_group_active == true())
2432 if repo_id:
2432 if repo_id:
2433 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2433 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2434 return q.all()
2434 return q.all()
2435
2435
2436 @classmethod
2436 @classmethod
2437 def get_default_group_perms(cls, user_id, repo_group_id=None):
2437 def get_default_group_perms(cls, user_id, repo_group_id=None):
2438 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2438 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2439 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2439 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2440 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2440 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2441 .filter(UserRepoGroupToPerm.user_id == user_id)
2441 .filter(UserRepoGroupToPerm.user_id == user_id)
2442 if repo_group_id:
2442 if repo_group_id:
2443 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2443 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2444 return q.all()
2444 return q.all()
2445
2445
2446 @classmethod
2446 @classmethod
2447 def get_default_group_perms_from_user_group(
2447 def get_default_group_perms_from_user_group(
2448 cls, user_id, repo_group_id=None):
2448 cls, user_id, repo_group_id=None):
2449 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2449 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2450 .join(
2450 .join(
2451 Permission,
2451 Permission,
2452 UserGroupRepoGroupToPerm.permission_id ==
2452 UserGroupRepoGroupToPerm.permission_id ==
2453 Permission.permission_id)\
2453 Permission.permission_id)\
2454 .join(
2454 .join(
2455 RepoGroup,
2455 RepoGroup,
2456 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2456 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2457 .join(
2457 .join(
2458 UserGroup,
2458 UserGroup,
2459 UserGroupRepoGroupToPerm.users_group_id ==
2459 UserGroupRepoGroupToPerm.users_group_id ==
2460 UserGroup.users_group_id)\
2460 UserGroup.users_group_id)\
2461 .join(
2461 .join(
2462 UserGroupMember,
2462 UserGroupMember,
2463 UserGroupRepoGroupToPerm.users_group_id ==
2463 UserGroupRepoGroupToPerm.users_group_id ==
2464 UserGroupMember.users_group_id)\
2464 UserGroupMember.users_group_id)\
2465 .filter(
2465 .filter(
2466 UserGroupMember.user_id == user_id,
2466 UserGroupMember.user_id == user_id,
2467 UserGroup.users_group_active == true())
2467 UserGroup.users_group_active == true())
2468 if repo_group_id:
2468 if repo_group_id:
2469 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2469 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2470 return q.all()
2470 return q.all()
2471
2471
2472 @classmethod
2472 @classmethod
2473 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2473 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2474 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2474 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2475 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2475 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2476 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2476 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2477 .filter(UserUserGroupToPerm.user_id == user_id)
2477 .filter(UserUserGroupToPerm.user_id == user_id)
2478 if user_group_id:
2478 if user_group_id:
2479 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2479 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2480 return q.all()
2480 return q.all()
2481
2481
2482 @classmethod
2482 @classmethod
2483 def get_default_user_group_perms_from_user_group(
2483 def get_default_user_group_perms_from_user_group(
2484 cls, user_id, user_group_id=None):
2484 cls, user_id, user_group_id=None):
2485 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2485 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2486 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2486 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2487 .join(
2487 .join(
2488 Permission,
2488 Permission,
2489 UserGroupUserGroupToPerm.permission_id ==
2489 UserGroupUserGroupToPerm.permission_id ==
2490 Permission.permission_id)\
2490 Permission.permission_id)\
2491 .join(
2491 .join(
2492 TargetUserGroup,
2492 TargetUserGroup,
2493 UserGroupUserGroupToPerm.target_user_group_id ==
2493 UserGroupUserGroupToPerm.target_user_group_id ==
2494 TargetUserGroup.users_group_id)\
2494 TargetUserGroup.users_group_id)\
2495 .join(
2495 .join(
2496 UserGroup,
2496 UserGroup,
2497 UserGroupUserGroupToPerm.user_group_id ==
2497 UserGroupUserGroupToPerm.user_group_id ==
2498 UserGroup.users_group_id)\
2498 UserGroup.users_group_id)\
2499 .join(
2499 .join(
2500 UserGroupMember,
2500 UserGroupMember,
2501 UserGroupUserGroupToPerm.user_group_id ==
2501 UserGroupUserGroupToPerm.user_group_id ==
2502 UserGroupMember.users_group_id)\
2502 UserGroupMember.users_group_id)\
2503 .filter(
2503 .filter(
2504 UserGroupMember.user_id == user_id,
2504 UserGroupMember.user_id == user_id,
2505 UserGroup.users_group_active == true())
2505 UserGroup.users_group_active == true())
2506 if user_group_id:
2506 if user_group_id:
2507 q = q.filter(
2507 q = q.filter(
2508 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2508 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2509
2509
2510 return q.all()
2510 return q.all()
2511
2511
2512
2512
2513 class UserRepoToPerm(Base, BaseModel):
2513 class UserRepoToPerm(Base, BaseModel):
2514 __tablename__ = 'repo_to_perm'
2514 __tablename__ = 'repo_to_perm'
2515 __table_args__ = (
2515 __table_args__ = (
2516 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2516 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2517 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2517 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2518 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2518 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2519 )
2519 )
2520 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2520 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2521 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2521 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2522 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2522 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2523 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2523 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2524
2524
2525 user = relationship('User')
2525 user = relationship('User')
2526 repository = relationship('Repository')
2526 repository = relationship('Repository')
2527 permission = relationship('Permission')
2527 permission = relationship('Permission')
2528
2528
2529 @classmethod
2529 @classmethod
2530 def create(cls, user, repository, permission):
2530 def create(cls, user, repository, permission):
2531 n = cls()
2531 n = cls()
2532 n.user = user
2532 n.user = user
2533 n.repository = repository
2533 n.repository = repository
2534 n.permission = permission
2534 n.permission = permission
2535 Session().add(n)
2535 Session().add(n)
2536 return n
2536 return n
2537
2537
2538 def __unicode__(self):
2538 def __unicode__(self):
2539 return u'<%s => %s >' % (self.user, self.repository)
2539 return u'<%s => %s >' % (self.user, self.repository)
2540
2540
2541
2541
2542 class UserUserGroupToPerm(Base, BaseModel):
2542 class UserUserGroupToPerm(Base, BaseModel):
2543 __tablename__ = 'user_user_group_to_perm'
2543 __tablename__ = 'user_user_group_to_perm'
2544 __table_args__ = (
2544 __table_args__ = (
2545 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2545 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2546 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2546 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2547 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2547 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2548 )
2548 )
2549 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2549 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2550 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2550 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2551 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2551 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2552 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2552 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2553
2553
2554 user = relationship('User')
2554 user = relationship('User')
2555 user_group = relationship('UserGroup')
2555 user_group = relationship('UserGroup')
2556 permission = relationship('Permission')
2556 permission = relationship('Permission')
2557
2557
2558 @classmethod
2558 @classmethod
2559 def create(cls, user, user_group, permission):
2559 def create(cls, user, user_group, permission):
2560 n = cls()
2560 n = cls()
2561 n.user = user
2561 n.user = user
2562 n.user_group = user_group
2562 n.user_group = user_group
2563 n.permission = permission
2563 n.permission = permission
2564 Session().add(n)
2564 Session().add(n)
2565 return n
2565 return n
2566
2566
2567 def __unicode__(self):
2567 def __unicode__(self):
2568 return u'<%s => %s >' % (self.user, self.user_group)
2568 return u'<%s => %s >' % (self.user, self.user_group)
2569
2569
2570
2570
2571 class UserToPerm(Base, BaseModel):
2571 class UserToPerm(Base, BaseModel):
2572 __tablename__ = 'user_to_perm'
2572 __tablename__ = 'user_to_perm'
2573 __table_args__ = (
2573 __table_args__ = (
2574 UniqueConstraint('user_id', 'permission_id'),
2574 UniqueConstraint('user_id', 'permission_id'),
2575 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2575 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2576 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2576 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2577 )
2577 )
2578 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2578 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2579 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2579 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2580 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2580 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2581
2581
2582 user = relationship('User')
2582 user = relationship('User')
2583 permission = relationship('Permission', lazy='joined')
2583 permission = relationship('Permission', lazy='joined')
2584
2584
2585 def __unicode__(self):
2585 def __unicode__(self):
2586 return u'<%s => %s >' % (self.user, self.permission)
2586 return u'<%s => %s >' % (self.user, self.permission)
2587
2587
2588
2588
2589 class UserGroupRepoToPerm(Base, BaseModel):
2589 class UserGroupRepoToPerm(Base, BaseModel):
2590 __tablename__ = 'users_group_repo_to_perm'
2590 __tablename__ = 'users_group_repo_to_perm'
2591 __table_args__ = (
2591 __table_args__ = (
2592 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2592 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2593 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2593 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2594 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2594 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2595 )
2595 )
2596 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2596 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2597 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2597 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2598 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2598 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2599 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2599 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2600
2600
2601 users_group = relationship('UserGroup')
2601 users_group = relationship('UserGroup')
2602 permission = relationship('Permission')
2602 permission = relationship('Permission')
2603 repository = relationship('Repository')
2603 repository = relationship('Repository')
2604
2604
2605 @classmethod
2605 @classmethod
2606 def create(cls, users_group, repository, permission):
2606 def create(cls, users_group, repository, permission):
2607 n = cls()
2607 n = cls()
2608 n.users_group = users_group
2608 n.users_group = users_group
2609 n.repository = repository
2609 n.repository = repository
2610 n.permission = permission
2610 n.permission = permission
2611 Session().add(n)
2611 Session().add(n)
2612 return n
2612 return n
2613
2613
2614 def __unicode__(self):
2614 def __unicode__(self):
2615 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2615 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2616
2616
2617
2617
2618 class UserGroupUserGroupToPerm(Base, BaseModel):
2618 class UserGroupUserGroupToPerm(Base, BaseModel):
2619 __tablename__ = 'user_group_user_group_to_perm'
2619 __tablename__ = 'user_group_user_group_to_perm'
2620 __table_args__ = (
2620 __table_args__ = (
2621 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2621 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2622 CheckConstraint('target_user_group_id != user_group_id'),
2622 CheckConstraint('target_user_group_id != user_group_id'),
2623 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2623 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2624 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2624 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2625 )
2625 )
2626 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)
2626 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)
2627 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2627 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2628 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2628 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2629 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2629 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2630
2630
2631 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2631 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2632 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2632 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2633 permission = relationship('Permission')
2633 permission = relationship('Permission')
2634
2634
2635 @classmethod
2635 @classmethod
2636 def create(cls, target_user_group, user_group, permission):
2636 def create(cls, target_user_group, user_group, permission):
2637 n = cls()
2637 n = cls()
2638 n.target_user_group = target_user_group
2638 n.target_user_group = target_user_group
2639 n.user_group = user_group
2639 n.user_group = user_group
2640 n.permission = permission
2640 n.permission = permission
2641 Session().add(n)
2641 Session().add(n)
2642 return n
2642 return n
2643
2643
2644 def __unicode__(self):
2644 def __unicode__(self):
2645 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2645 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2646
2646
2647
2647
2648 class UserGroupToPerm(Base, BaseModel):
2648 class UserGroupToPerm(Base, BaseModel):
2649 __tablename__ = 'users_group_to_perm'
2649 __tablename__ = 'users_group_to_perm'
2650 __table_args__ = (
2650 __table_args__ = (
2651 UniqueConstraint('users_group_id', 'permission_id',),
2651 UniqueConstraint('users_group_id', 'permission_id',),
2652 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2652 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2653 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2653 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2654 )
2654 )
2655 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2655 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2656 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2656 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2657 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2657 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2658
2658
2659 users_group = relationship('UserGroup')
2659 users_group = relationship('UserGroup')
2660 permission = relationship('Permission')
2660 permission = relationship('Permission')
2661
2661
2662
2662
2663 class UserRepoGroupToPerm(Base, BaseModel):
2663 class UserRepoGroupToPerm(Base, BaseModel):
2664 __tablename__ = 'user_repo_group_to_perm'
2664 __tablename__ = 'user_repo_group_to_perm'
2665 __table_args__ = (
2665 __table_args__ = (
2666 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2666 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2667 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2667 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2668 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2668 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2669 )
2669 )
2670
2670
2671 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2671 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2672 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2672 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2673 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2673 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2674 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2674 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2675
2675
2676 user = relationship('User')
2676 user = relationship('User')
2677 group = relationship('RepoGroup')
2677 group = relationship('RepoGroup')
2678 permission = relationship('Permission')
2678 permission = relationship('Permission')
2679
2679
2680 @classmethod
2680 @classmethod
2681 def create(cls, user, repository_group, permission):
2681 def create(cls, user, repository_group, permission):
2682 n = cls()
2682 n = cls()
2683 n.user = user
2683 n.user = user
2684 n.group = repository_group
2684 n.group = repository_group
2685 n.permission = permission
2685 n.permission = permission
2686 Session().add(n)
2686 Session().add(n)
2687 return n
2687 return n
2688
2688
2689
2689
2690 class UserGroupRepoGroupToPerm(Base, BaseModel):
2690 class UserGroupRepoGroupToPerm(Base, BaseModel):
2691 __tablename__ = 'users_group_repo_group_to_perm'
2691 __tablename__ = 'users_group_repo_group_to_perm'
2692 __table_args__ = (
2692 __table_args__ = (
2693 UniqueConstraint('users_group_id', 'group_id'),
2693 UniqueConstraint('users_group_id', 'group_id'),
2694 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2694 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2695 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2695 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2696 )
2696 )
2697
2697
2698 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)
2698 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)
2699 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2699 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2700 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2700 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2701 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2701 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2702
2702
2703 users_group = relationship('UserGroup')
2703 users_group = relationship('UserGroup')
2704 permission = relationship('Permission')
2704 permission = relationship('Permission')
2705 group = relationship('RepoGroup')
2705 group = relationship('RepoGroup')
2706
2706
2707 @classmethod
2707 @classmethod
2708 def create(cls, user_group, repository_group, permission):
2708 def create(cls, user_group, repository_group, permission):
2709 n = cls()
2709 n = cls()
2710 n.users_group = user_group
2710 n.users_group = user_group
2711 n.group = repository_group
2711 n.group = repository_group
2712 n.permission = permission
2712 n.permission = permission
2713 Session().add(n)
2713 Session().add(n)
2714 return n
2714 return n
2715
2715
2716 def __unicode__(self):
2716 def __unicode__(self):
2717 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2717 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2718
2718
2719
2719
2720 class Statistics(Base, BaseModel):
2720 class Statistics(Base, BaseModel):
2721 __tablename__ = 'statistics'
2721 __tablename__ = 'statistics'
2722 __table_args__ = (
2722 __table_args__ = (
2723 UniqueConstraint('repository_id'),
2723 UniqueConstraint('repository_id'),
2724 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2724 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2725 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2725 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2726 )
2726 )
2727 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2727 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2728 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2728 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2729 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2729 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2730 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2730 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2731 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2731 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2732 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2732 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2733
2733
2734 repository = relationship('Repository', single_parent=True)
2734 repository = relationship('Repository', single_parent=True)
2735
2735
2736
2736
2737 class UserFollowing(Base, BaseModel):
2737 class UserFollowing(Base, BaseModel):
2738 __tablename__ = 'user_followings'
2738 __tablename__ = 'user_followings'
2739 __table_args__ = (
2739 __table_args__ = (
2740 UniqueConstraint('user_id', 'follows_repository_id'),
2740 UniqueConstraint('user_id', 'follows_repository_id'),
2741 UniqueConstraint('user_id', 'follows_user_id'),
2741 UniqueConstraint('user_id', 'follows_user_id'),
2742 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2742 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2743 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2743 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2744 )
2744 )
2745
2745
2746 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2746 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2747 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2747 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2748 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2748 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2749 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2749 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2750 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2750 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2751
2751
2752 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2752 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2753
2753
2754 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2754 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2755 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2755 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2756
2756
2757 @classmethod
2757 @classmethod
2758 def get_repo_followers(cls, repo_id):
2758 def get_repo_followers(cls, repo_id):
2759 return cls.query().filter(cls.follows_repo_id == repo_id)
2759 return cls.query().filter(cls.follows_repo_id == repo_id)
2760
2760
2761
2761
2762 class CacheKey(Base, BaseModel):
2762 class CacheKey(Base, BaseModel):
2763 __tablename__ = 'cache_invalidation'
2763 __tablename__ = 'cache_invalidation'
2764 __table_args__ = (
2764 __table_args__ = (
2765 UniqueConstraint('cache_key'),
2765 UniqueConstraint('cache_key'),
2766 Index('key_idx', 'cache_key'),
2766 Index('key_idx', 'cache_key'),
2767 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2767 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2768 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2768 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2769 )
2769 )
2770 CACHE_TYPE_ATOM = 'ATOM'
2770 CACHE_TYPE_ATOM = 'ATOM'
2771 CACHE_TYPE_RSS = 'RSS'
2771 CACHE_TYPE_RSS = 'RSS'
2772 CACHE_TYPE_README = 'README'
2772 CACHE_TYPE_README = 'README'
2773
2773
2774 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2774 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2775 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2775 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2776 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2776 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2777 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2777 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2778
2778
2779 def __init__(self, cache_key, cache_args=''):
2779 def __init__(self, cache_key, cache_args=''):
2780 self.cache_key = cache_key
2780 self.cache_key = cache_key
2781 self.cache_args = cache_args
2781 self.cache_args = cache_args
2782 self.cache_active = False
2782 self.cache_active = False
2783
2783
2784 def __unicode__(self):
2784 def __unicode__(self):
2785 return u"<%s('%s:%s[%s]')>" % (
2785 return u"<%s('%s:%s[%s]')>" % (
2786 self.__class__.__name__,
2786 self.__class__.__name__,
2787 self.cache_id, self.cache_key, self.cache_active)
2787 self.cache_id, self.cache_key, self.cache_active)
2788
2788
2789 def _cache_key_partition(self):
2789 def _cache_key_partition(self):
2790 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2790 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2791 return prefix, repo_name, suffix
2791 return prefix, repo_name, suffix
2792
2792
2793 def get_prefix(self):
2793 def get_prefix(self):
2794 """
2794 """
2795 Try to extract prefix from existing cache key. The key could consist
2795 Try to extract prefix from existing cache key. The key could consist
2796 of prefix, repo_name, suffix
2796 of prefix, repo_name, suffix
2797 """
2797 """
2798 # this returns prefix, repo_name, suffix
2798 # this returns prefix, repo_name, suffix
2799 return self._cache_key_partition()[0]
2799 return self._cache_key_partition()[0]
2800
2800
2801 def get_suffix(self):
2801 def get_suffix(self):
2802 """
2802 """
2803 get suffix that might have been used in _get_cache_key to
2803 get suffix that might have been used in _get_cache_key to
2804 generate self.cache_key. Only used for informational purposes
2804 generate self.cache_key. Only used for informational purposes
2805 in repo_edit.mako.
2805 in repo_edit.mako.
2806 """
2806 """
2807 # prefix, repo_name, suffix
2807 # prefix, repo_name, suffix
2808 return self._cache_key_partition()[2]
2808 return self._cache_key_partition()[2]
2809
2809
2810 @classmethod
2810 @classmethod
2811 def delete_all_cache(cls):
2811 def delete_all_cache(cls):
2812 """
2812 """
2813 Delete all cache keys from database.
2813 Delete all cache keys from database.
2814 Should only be run when all instances are down and all entries
2814 Should only be run when all instances are down and all entries
2815 thus stale.
2815 thus stale.
2816 """
2816 """
2817 cls.query().delete()
2817 cls.query().delete()
2818 Session().commit()
2818 Session().commit()
2819
2819
2820 @classmethod
2820 @classmethod
2821 def get_cache_key(cls, repo_name, cache_type):
2821 def get_cache_key(cls, repo_name, cache_type):
2822 """
2822 """
2823
2823
2824 Generate a cache key for this process of RhodeCode instance.
2824 Generate a cache key for this process of RhodeCode instance.
2825 Prefix most likely will be process id or maybe explicitly set
2825 Prefix most likely will be process id or maybe explicitly set
2826 instance_id from .ini file.
2826 instance_id from .ini file.
2827 """
2827 """
2828 import rhodecode
2828 import rhodecode
2829 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
2829 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
2830
2830
2831 repo_as_unicode = safe_unicode(repo_name)
2831 repo_as_unicode = safe_unicode(repo_name)
2832 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
2832 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
2833 if cache_type else repo_as_unicode
2833 if cache_type else repo_as_unicode
2834
2834
2835 return u'{}{}'.format(prefix, key)
2835 return u'{}{}'.format(prefix, key)
2836
2836
2837 @classmethod
2837 @classmethod
2838 def set_invalidate(cls, repo_name, delete=False):
2838 def set_invalidate(cls, repo_name, delete=False):
2839 """
2839 """
2840 Mark all caches of a repo as invalid in the database.
2840 Mark all caches of a repo as invalid in the database.
2841 """
2841 """
2842
2842
2843 try:
2843 try:
2844 qry = Session().query(cls).filter(cls.cache_args == repo_name)
2844 qry = Session().query(cls).filter(cls.cache_args == repo_name)
2845 if delete:
2845 if delete:
2846 log.debug('cache objects deleted for repo %s',
2846 log.debug('cache objects deleted for repo %s',
2847 safe_str(repo_name))
2847 safe_str(repo_name))
2848 qry.delete()
2848 qry.delete()
2849 else:
2849 else:
2850 log.debug('cache objects marked as invalid for repo %s',
2850 log.debug('cache objects marked as invalid for repo %s',
2851 safe_str(repo_name))
2851 safe_str(repo_name))
2852 qry.update({"cache_active": False})
2852 qry.update({"cache_active": False})
2853
2853
2854 Session().commit()
2854 Session().commit()
2855 except Exception:
2855 except Exception:
2856 log.exception(
2856 log.exception(
2857 'Cache key invalidation failed for repository %s',
2857 'Cache key invalidation failed for repository %s',
2858 safe_str(repo_name))
2858 safe_str(repo_name))
2859 Session().rollback()
2859 Session().rollback()
2860
2860
2861 @classmethod
2861 @classmethod
2862 def get_active_cache(cls, cache_key):
2862 def get_active_cache(cls, cache_key):
2863 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2863 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2864 if inv_obj:
2864 if inv_obj:
2865 return inv_obj
2865 return inv_obj
2866 return None
2866 return None
2867
2867
2868 @classmethod
2868 @classmethod
2869 def repo_context_cache(cls, compute_func, repo_name, cache_type,
2869 def repo_context_cache(cls, compute_func, repo_name, cache_type,
2870 thread_scoped=False):
2870 thread_scoped=False):
2871 """
2871 """
2872 @cache_region('long_term')
2872 @cache_region('long_term')
2873 def _heavy_calculation(cache_key):
2873 def _heavy_calculation(cache_key):
2874 return 'result'
2874 return 'result'
2875
2875
2876 cache_context = CacheKey.repo_context_cache(
2876 cache_context = CacheKey.repo_context_cache(
2877 _heavy_calculation, repo_name, cache_type)
2877 _heavy_calculation, repo_name, cache_type)
2878
2878
2879 with cache_context as context:
2879 with cache_context as context:
2880 context.invalidate()
2880 context.invalidate()
2881 computed = context.compute()
2881 computed = context.compute()
2882
2882
2883 assert computed == 'result'
2883 assert computed == 'result'
2884 """
2884 """
2885 from rhodecode.lib import caches
2885 from rhodecode.lib import caches
2886 return caches.InvalidationContext(
2886 return caches.InvalidationContext(
2887 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
2887 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
2888
2888
2889
2889
2890 class ChangesetComment(Base, BaseModel):
2890 class ChangesetComment(Base, BaseModel):
2891 __tablename__ = 'changeset_comments'
2891 __tablename__ = 'changeset_comments'
2892 __table_args__ = (
2892 __table_args__ = (
2893 Index('cc_revision_idx', 'revision'),
2893 Index('cc_revision_idx', 'revision'),
2894 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2894 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2895 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2895 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2896 )
2896 )
2897
2897
2898 COMMENT_OUTDATED = u'comment_outdated'
2898 COMMENT_OUTDATED = u'comment_outdated'
2899
2899
2900 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
2900 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
2901 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2901 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2902 revision = Column('revision', String(40), nullable=True)
2902 revision = Column('revision', String(40), nullable=True)
2903 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2903 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
2904 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
2904 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
2905 line_no = Column('line_no', Unicode(10), nullable=True)
2905 line_no = Column('line_no', Unicode(10), nullable=True)
2906 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
2906 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
2907 f_path = Column('f_path', Unicode(1000), nullable=True)
2907 f_path = Column('f_path', Unicode(1000), nullable=True)
2908 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
2908 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
2909 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
2909 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
2910 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2910 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2911 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2911 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2912 renderer = Column('renderer', Unicode(64), nullable=True)
2912 renderer = Column('renderer', Unicode(64), nullable=True)
2913 display_state = Column('display_state', Unicode(128), nullable=True)
2913 display_state = Column('display_state', Unicode(128), nullable=True)
2914
2914
2915 author = relationship('User', lazy='joined')
2915 author = relationship('User', lazy='joined')
2916 repo = relationship('Repository')
2916 repo = relationship('Repository')
2917 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
2917 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
2918 pull_request = relationship('PullRequest', lazy='joined')
2918 pull_request = relationship('PullRequest', lazy='joined')
2919 pull_request_version = relationship('PullRequestVersion')
2919 pull_request_version = relationship('PullRequestVersion')
2920
2920
2921 @classmethod
2921 @classmethod
2922 def get_users(cls, revision=None, pull_request_id=None):
2922 def get_users(cls, revision=None, pull_request_id=None):
2923 """
2923 """
2924 Returns user associated with this ChangesetComment. ie those
2924 Returns user associated with this ChangesetComment. ie those
2925 who actually commented
2925 who actually commented
2926
2926
2927 :param cls:
2927 :param cls:
2928 :param revision:
2928 :param revision:
2929 """
2929 """
2930 q = Session().query(User)\
2930 q = Session().query(User)\
2931 .join(ChangesetComment.author)
2931 .join(ChangesetComment.author)
2932 if revision:
2932 if revision:
2933 q = q.filter(cls.revision == revision)
2933 q = q.filter(cls.revision == revision)
2934 elif pull_request_id:
2934 elif pull_request_id:
2935 q = q.filter(cls.pull_request_id == pull_request_id)
2935 q = q.filter(cls.pull_request_id == pull_request_id)
2936 return q.all()
2936 return q.all()
2937
2937
2938 @classmethod
2938 @classmethod
2939 def get_index_from_version(cls, pr_version, versions):
2939 def get_index_from_version(cls, pr_version, versions):
2940 num_versions = [x.pull_request_version_id for x in versions]
2940 num_versions = [x.pull_request_version_id for x in versions]
2941 try:
2941 try:
2942 return num_versions.index(pr_version) +1
2942 return num_versions.index(pr_version) +1
2943 except (IndexError, ValueError):
2943 except (IndexError, ValueError):
2944 return
2944 return
2945
2945
2946 @property
2946 @property
2947 def outdated(self):
2947 def outdated(self):
2948 return self.display_state == self.COMMENT_OUTDATED
2948 return self.display_state == self.COMMENT_OUTDATED
2949
2949
2950 def outdated_at_version(self, version):
2950 def outdated_at_version(self, version):
2951 """
2951 """
2952 Checks if comment is outdated for given pull request version
2952 Checks if comment is outdated for given pull request version
2953 """
2953 """
2954 return self.outdated and self.pull_request_version_id != version
2954 return self.outdated and self.pull_request_version_id != version
2955
2955
2956 def get_index_version(self, versions):
2956 def get_index_version(self, versions):
2957 return self.get_index_from_version(
2957 return self.get_index_from_version(
2958 self.pull_request_version_id, versions)
2958 self.pull_request_version_id, versions)
2959
2959
2960 def render(self, mentions=False):
2960 def render(self, mentions=False):
2961 from rhodecode.lib import helpers as h
2961 from rhodecode.lib import helpers as h
2962 return h.render(self.text, renderer=self.renderer, mentions=mentions)
2962 return h.render(self.text, renderer=self.renderer, mentions=mentions)
2963
2963
2964 def __repr__(self):
2964 def __repr__(self):
2965 if self.comment_id:
2965 if self.comment_id:
2966 return '<DB:ChangesetComment #%s>' % self.comment_id
2966 return '<DB:ChangesetComment #%s>' % self.comment_id
2967 else:
2967 else:
2968 return '<DB:ChangesetComment at %#x>' % id(self)
2968 return '<DB:ChangesetComment at %#x>' % id(self)
2969
2969
2970
2970
2971 class ChangesetStatus(Base, BaseModel):
2971 class ChangesetStatus(Base, BaseModel):
2972 __tablename__ = 'changeset_statuses'
2972 __tablename__ = 'changeset_statuses'
2973 __table_args__ = (
2973 __table_args__ = (
2974 Index('cs_revision_idx', 'revision'),
2974 Index('cs_revision_idx', 'revision'),
2975 Index('cs_version_idx', 'version'),
2975 Index('cs_version_idx', 'version'),
2976 UniqueConstraint('repo_id', 'revision', 'version'),
2976 UniqueConstraint('repo_id', 'revision', 'version'),
2977 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2977 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2978 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2978 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2979 )
2979 )
2980 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
2980 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
2981 STATUS_APPROVED = 'approved'
2981 STATUS_APPROVED = 'approved'
2982 STATUS_REJECTED = 'rejected'
2982 STATUS_REJECTED = 'rejected'
2983 STATUS_UNDER_REVIEW = 'under_review'
2983 STATUS_UNDER_REVIEW = 'under_review'
2984
2984
2985 STATUSES = [
2985 STATUSES = [
2986 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
2986 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
2987 (STATUS_APPROVED, _("Approved")),
2987 (STATUS_APPROVED, _("Approved")),
2988 (STATUS_REJECTED, _("Rejected")),
2988 (STATUS_REJECTED, _("Rejected")),
2989 (STATUS_UNDER_REVIEW, _("Under Review")),
2989 (STATUS_UNDER_REVIEW, _("Under Review")),
2990 ]
2990 ]
2991
2991
2992 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
2992 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
2993 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2993 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
2994 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
2994 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
2995 revision = Column('revision', String(40), nullable=False)
2995 revision = Column('revision', String(40), nullable=False)
2996 status = Column('status', String(128), nullable=False, default=DEFAULT)
2996 status = Column('status', String(128), nullable=False, default=DEFAULT)
2997 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
2997 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
2998 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
2998 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
2999 version = Column('version', Integer(), nullable=False, default=0)
2999 version = Column('version', Integer(), nullable=False, default=0)
3000 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3000 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3001
3001
3002 author = relationship('User', lazy='joined')
3002 author = relationship('User', lazy='joined')
3003 repo = relationship('Repository')
3003 repo = relationship('Repository')
3004 comment = relationship('ChangesetComment', lazy='joined')
3004 comment = relationship('ChangesetComment', lazy='joined')
3005 pull_request = relationship('PullRequest', lazy='joined')
3005 pull_request = relationship('PullRequest', lazy='joined')
3006
3006
3007 def __unicode__(self):
3007 def __unicode__(self):
3008 return u"<%s('%s[%s]:%s')>" % (
3008 return u"<%s('%s[%s]:%s')>" % (
3009 self.__class__.__name__,
3009 self.__class__.__name__,
3010 self.status, self.version, self.author
3010 self.status, self.version, self.author
3011 )
3011 )
3012
3012
3013 @classmethod
3013 @classmethod
3014 def get_status_lbl(cls, value):
3014 def get_status_lbl(cls, value):
3015 return dict(cls.STATUSES).get(value)
3015 return dict(cls.STATUSES).get(value)
3016
3016
3017 @property
3017 @property
3018 def status_lbl(self):
3018 def status_lbl(self):
3019 return ChangesetStatus.get_status_lbl(self.status)
3019 return ChangesetStatus.get_status_lbl(self.status)
3020
3020
3021
3021
3022 class _PullRequestBase(BaseModel):
3022 class _PullRequestBase(BaseModel):
3023 """
3023 """
3024 Common attributes of pull request and version entries.
3024 Common attributes of pull request and version entries.
3025 """
3025 """
3026
3026
3027 # .status values
3027 # .status values
3028 STATUS_NEW = u'new'
3028 STATUS_NEW = u'new'
3029 STATUS_OPEN = u'open'
3029 STATUS_OPEN = u'open'
3030 STATUS_CLOSED = u'closed'
3030 STATUS_CLOSED = u'closed'
3031
3031
3032 title = Column('title', Unicode(255), nullable=True)
3032 title = Column('title', Unicode(255), nullable=True)
3033 description = Column(
3033 description = Column(
3034 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3034 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3035 nullable=True)
3035 nullable=True)
3036 # new/open/closed status of pull request (not approve/reject/etc)
3036 # new/open/closed status of pull request (not approve/reject/etc)
3037 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3037 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3038 created_on = Column(
3038 created_on = Column(
3039 'created_on', DateTime(timezone=False), nullable=False,
3039 'created_on', DateTime(timezone=False), nullable=False,
3040 default=datetime.datetime.now)
3040 default=datetime.datetime.now)
3041 updated_on = Column(
3041 updated_on = Column(
3042 'updated_on', DateTime(timezone=False), nullable=False,
3042 'updated_on', DateTime(timezone=False), nullable=False,
3043 default=datetime.datetime.now)
3043 default=datetime.datetime.now)
3044
3044
3045 @declared_attr
3045 @declared_attr
3046 def user_id(cls):
3046 def user_id(cls):
3047 return Column(
3047 return Column(
3048 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3048 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3049 unique=None)
3049 unique=None)
3050
3050
3051 # 500 revisions max
3051 # 500 revisions max
3052 _revisions = Column(
3052 _revisions = Column(
3053 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3053 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3054
3054
3055 @declared_attr
3055 @declared_attr
3056 def source_repo_id(cls):
3056 def source_repo_id(cls):
3057 # TODO: dan: rename column to source_repo_id
3057 # TODO: dan: rename column to source_repo_id
3058 return Column(
3058 return Column(
3059 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3059 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3060 nullable=False)
3060 nullable=False)
3061
3061
3062 source_ref = Column('org_ref', Unicode(255), nullable=False)
3062 source_ref = Column('org_ref', Unicode(255), nullable=False)
3063
3063
3064 @declared_attr
3064 @declared_attr
3065 def target_repo_id(cls):
3065 def target_repo_id(cls):
3066 # TODO: dan: rename column to target_repo_id
3066 # TODO: dan: rename column to target_repo_id
3067 return Column(
3067 return Column(
3068 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3068 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3069 nullable=False)
3069 nullable=False)
3070
3070
3071 target_ref = Column('other_ref', Unicode(255), nullable=False)
3071 target_ref = Column('other_ref', Unicode(255), nullable=False)
3072 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3072 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3073
3073
3074 # TODO: dan: rename column to last_merge_source_rev
3074 # TODO: dan: rename column to last_merge_source_rev
3075 _last_merge_source_rev = Column(
3075 _last_merge_source_rev = Column(
3076 'last_merge_org_rev', String(40), nullable=True)
3076 'last_merge_org_rev', String(40), nullable=True)
3077 # TODO: dan: rename column to last_merge_target_rev
3077 # TODO: dan: rename column to last_merge_target_rev
3078 _last_merge_target_rev = Column(
3078 _last_merge_target_rev = Column(
3079 'last_merge_other_rev', String(40), nullable=True)
3079 'last_merge_other_rev', String(40), nullable=True)
3080 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3080 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3081 merge_rev = Column('merge_rev', String(40), nullable=True)
3081 merge_rev = Column('merge_rev', String(40), nullable=True)
3082
3082
3083 @hybrid_property
3083 @hybrid_property
3084 def revisions(self):
3084 def revisions(self):
3085 return self._revisions.split(':') if self._revisions else []
3085 return self._revisions.split(':') if self._revisions else []
3086
3086
3087 @revisions.setter
3087 @revisions.setter
3088 def revisions(self, val):
3088 def revisions(self, val):
3089 self._revisions = ':'.join(val)
3089 self._revisions = ':'.join(val)
3090
3090
3091 @declared_attr
3091 @declared_attr
3092 def author(cls):
3092 def author(cls):
3093 return relationship('User', lazy='joined')
3093 return relationship('User', lazy='joined')
3094
3094
3095 @declared_attr
3095 @declared_attr
3096 def source_repo(cls):
3096 def source_repo(cls):
3097 return relationship(
3097 return relationship(
3098 'Repository',
3098 'Repository',
3099 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3099 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3100
3100
3101 @property
3101 @property
3102 def source_ref_parts(self):
3102 def source_ref_parts(self):
3103 return self.unicode_to_reference(self.source_ref)
3103 return self.unicode_to_reference(self.source_ref)
3104
3104
3105 @declared_attr
3105 @declared_attr
3106 def target_repo(cls):
3106 def target_repo(cls):
3107 return relationship(
3107 return relationship(
3108 'Repository',
3108 'Repository',
3109 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3109 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3110
3110
3111 @property
3111 @property
3112 def target_ref_parts(self):
3112 def target_ref_parts(self):
3113 return self.unicode_to_reference(self.target_ref)
3113 return self.unicode_to_reference(self.target_ref)
3114
3114
3115 @property
3115 @property
3116 def shadow_merge_ref(self):
3116 def shadow_merge_ref(self):
3117 return self.unicode_to_reference(self._shadow_merge_ref)
3117 return self.unicode_to_reference(self._shadow_merge_ref)
3118
3118
3119 @shadow_merge_ref.setter
3119 @shadow_merge_ref.setter
3120 def shadow_merge_ref(self, ref):
3120 def shadow_merge_ref(self, ref):
3121 self._shadow_merge_ref = self.reference_to_unicode(ref)
3121 self._shadow_merge_ref = self.reference_to_unicode(ref)
3122
3122
3123 def unicode_to_reference(self, raw):
3123 def unicode_to_reference(self, raw):
3124 """
3124 """
3125 Convert a unicode (or string) to a reference object.
3125 Convert a unicode (or string) to a reference object.
3126 If unicode evaluates to False it returns None.
3126 If unicode evaluates to False it returns None.
3127 """
3127 """
3128 if raw:
3128 if raw:
3129 refs = raw.split(':')
3129 refs = raw.split(':')
3130 return Reference(*refs)
3130 return Reference(*refs)
3131 else:
3131 else:
3132 return None
3132 return None
3133
3133
3134 def reference_to_unicode(self, ref):
3134 def reference_to_unicode(self, ref):
3135 """
3135 """
3136 Convert a reference object to unicode.
3136 Convert a reference object to unicode.
3137 If reference is None it returns None.
3137 If reference is None it returns None.
3138 """
3138 """
3139 if ref:
3139 if ref:
3140 return u':'.join(ref)
3140 return u':'.join(ref)
3141 else:
3141 else:
3142 return None
3142 return None
3143
3143
3144 def get_api_data(self):
3144 def get_api_data(self):
3145 from rhodecode.model.pull_request import PullRequestModel
3145 from rhodecode.model.pull_request import PullRequestModel
3146 pull_request = self
3146 pull_request = self
3147 merge_status = PullRequestModel().merge_status(pull_request)
3147 merge_status = PullRequestModel().merge_status(pull_request)
3148
3148
3149 pull_request_url = url(
3149 pull_request_url = url(
3150 'pullrequest_show', repo_name=self.target_repo.repo_name,
3150 'pullrequest_show', repo_name=self.target_repo.repo_name,
3151 pull_request_id=self.pull_request_id, qualified=True)
3151 pull_request_id=self.pull_request_id, qualified=True)
3152
3152
3153 merge_data = {
3153 merge_data = {
3154 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3154 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3155 'reference': (
3155 'reference': (
3156 pull_request.shadow_merge_ref._asdict()
3156 pull_request.shadow_merge_ref._asdict()
3157 if pull_request.shadow_merge_ref else None),
3157 if pull_request.shadow_merge_ref else None),
3158 }
3158 }
3159
3159
3160 data = {
3160 data = {
3161 'pull_request_id': pull_request.pull_request_id,
3161 'pull_request_id': pull_request.pull_request_id,
3162 'url': pull_request_url,
3162 'url': pull_request_url,
3163 'title': pull_request.title,
3163 'title': pull_request.title,
3164 'description': pull_request.description,
3164 'description': pull_request.description,
3165 'status': pull_request.status,
3165 'status': pull_request.status,
3166 'created_on': pull_request.created_on,
3166 'created_on': pull_request.created_on,
3167 'updated_on': pull_request.updated_on,
3167 'updated_on': pull_request.updated_on,
3168 'commit_ids': pull_request.revisions,
3168 'commit_ids': pull_request.revisions,
3169 'review_status': pull_request.calculated_review_status(),
3169 'review_status': pull_request.calculated_review_status(),
3170 'mergeable': {
3170 'mergeable': {
3171 'status': merge_status[0],
3171 'status': merge_status[0],
3172 'message': unicode(merge_status[1]),
3172 'message': unicode(merge_status[1]),
3173 },
3173 },
3174 'source': {
3174 'source': {
3175 'clone_url': pull_request.source_repo.clone_url(),
3175 'clone_url': pull_request.source_repo.clone_url(),
3176 'repository': pull_request.source_repo.repo_name,
3176 'repository': pull_request.source_repo.repo_name,
3177 'reference': {
3177 'reference': {
3178 'name': pull_request.source_ref_parts.name,
3178 'name': pull_request.source_ref_parts.name,
3179 'type': pull_request.source_ref_parts.type,
3179 'type': pull_request.source_ref_parts.type,
3180 'commit_id': pull_request.source_ref_parts.commit_id,
3180 'commit_id': pull_request.source_ref_parts.commit_id,
3181 },
3181 },
3182 },
3182 },
3183 'target': {
3183 'target': {
3184 'clone_url': pull_request.target_repo.clone_url(),
3184 'clone_url': pull_request.target_repo.clone_url(),
3185 'repository': pull_request.target_repo.repo_name,
3185 'repository': pull_request.target_repo.repo_name,
3186 'reference': {
3186 'reference': {
3187 'name': pull_request.target_ref_parts.name,
3187 'name': pull_request.target_ref_parts.name,
3188 'type': pull_request.target_ref_parts.type,
3188 'type': pull_request.target_ref_parts.type,
3189 'commit_id': pull_request.target_ref_parts.commit_id,
3189 'commit_id': pull_request.target_ref_parts.commit_id,
3190 },
3190 },
3191 },
3191 },
3192 'merge': merge_data,
3192 'merge': merge_data,
3193 'author': pull_request.author.get_api_data(include_secrets=False,
3193 'author': pull_request.author.get_api_data(include_secrets=False,
3194 details='basic'),
3194 details='basic'),
3195 'reviewers': [
3195 'reviewers': [
3196 {
3196 {
3197 'user': reviewer.get_api_data(include_secrets=False,
3197 'user': reviewer.get_api_data(include_secrets=False,
3198 details='basic'),
3198 details='basic'),
3199 'reasons': reasons,
3199 'reasons': reasons,
3200 'review_status': st[0][1].status if st else 'not_reviewed',
3200 'review_status': st[0][1].status if st else 'not_reviewed',
3201 }
3201 }
3202 for reviewer, reasons, st in pull_request.reviewers_statuses()
3202 for reviewer, reasons, st in pull_request.reviewers_statuses()
3203 ]
3203 ]
3204 }
3204 }
3205
3205
3206 return data
3206 return data
3207
3207
3208
3208
3209 class PullRequest(Base, _PullRequestBase):
3209 class PullRequest(Base, _PullRequestBase):
3210 __tablename__ = 'pull_requests'
3210 __tablename__ = 'pull_requests'
3211 __table_args__ = (
3211 __table_args__ = (
3212 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3212 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3213 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3213 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3214 )
3214 )
3215
3215
3216 pull_request_id = Column(
3216 pull_request_id = Column(
3217 'pull_request_id', Integer(), nullable=False, primary_key=True)
3217 'pull_request_id', Integer(), nullable=False, primary_key=True)
3218
3218
3219 def __repr__(self):
3219 def __repr__(self):
3220 if self.pull_request_id:
3220 if self.pull_request_id:
3221 return '<DB:PullRequest #%s>' % self.pull_request_id
3221 return '<DB:PullRequest #%s>' % self.pull_request_id
3222 else:
3222 else:
3223 return '<DB:PullRequest at %#x>' % id(self)
3223 return '<DB:PullRequest at %#x>' % id(self)
3224
3224
3225 reviewers = relationship('PullRequestReviewers',
3225 reviewers = relationship('PullRequestReviewers',
3226 cascade="all, delete, delete-orphan")
3226 cascade="all, delete, delete-orphan")
3227 statuses = relationship('ChangesetStatus')
3227 statuses = relationship('ChangesetStatus')
3228 comments = relationship('ChangesetComment',
3228 comments = relationship('ChangesetComment',
3229 cascade="all, delete, delete-orphan")
3229 cascade="all, delete, delete-orphan")
3230 versions = relationship('PullRequestVersion',
3230 versions = relationship('PullRequestVersion',
3231 cascade="all, delete, delete-orphan",
3231 cascade="all, delete, delete-orphan",
3232 lazy='dynamic')
3232 lazy='dynamic')
3233
3233
3234
3234
3235 @classmethod
3235 @classmethod
3236 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3236 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3237 internal_methods=None):
3237 internal_methods=None):
3238
3238
3239 class PullRequestDisplay(object):
3239 class PullRequestDisplay(object):
3240 """
3240 """
3241 Special object wrapper for showing PullRequest data via Versions
3241 Special object wrapper for showing PullRequest data via Versions
3242 It mimics PR object as close as possible. This is read only object
3242 It mimics PR object as close as possible. This is read only object
3243 just for display
3243 just for display
3244 """
3244 """
3245
3245
3246 def __init__(self, attrs, internal=None):
3246 def __init__(self, attrs, internal=None):
3247 self.attrs = attrs
3247 self.attrs = attrs
3248 # internal have priority over the given ones via attrs
3248 # internal have priority over the given ones via attrs
3249 self.internal = internal or ['versions']
3249 self.internal = internal or ['versions']
3250
3250
3251 def __getattr__(self, item):
3251 def __getattr__(self, item):
3252 if item in self.internal:
3252 if item in self.internal:
3253 return getattr(self, item)
3253 return getattr(self, item)
3254 try:
3254 try:
3255 return self.attrs[item]
3255 return self.attrs[item]
3256 except KeyError:
3256 except KeyError:
3257 raise AttributeError(
3257 raise AttributeError(
3258 '%s object has no attribute %s' % (self, item))
3258 '%s object has no attribute %s' % (self, item))
3259
3259
3260 def __repr__(self):
3260 def __repr__(self):
3261 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3261 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3262
3262
3263 def versions(self):
3263 def versions(self):
3264 return pull_request_obj.versions.order_by(
3264 return pull_request_obj.versions.order_by(
3265 PullRequestVersion.pull_request_version_id).all()
3265 PullRequestVersion.pull_request_version_id).all()
3266
3266
3267 def is_closed(self):
3267 def is_closed(self):
3268 return pull_request_obj.is_closed()
3268 return pull_request_obj.is_closed()
3269
3269
3270 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3270 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3271
3271
3272 attrs.author = StrictAttributeDict(
3272 attrs.author = StrictAttributeDict(
3273 pull_request_obj.author.get_api_data())
3273 pull_request_obj.author.get_api_data())
3274 if pull_request_obj.target_repo:
3274 if pull_request_obj.target_repo:
3275 attrs.target_repo = StrictAttributeDict(
3275 attrs.target_repo = StrictAttributeDict(
3276 pull_request_obj.target_repo.get_api_data())
3276 pull_request_obj.target_repo.get_api_data())
3277 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3277 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3278
3278
3279 if pull_request_obj.source_repo:
3279 if pull_request_obj.source_repo:
3280 attrs.source_repo = StrictAttributeDict(
3280 attrs.source_repo = StrictAttributeDict(
3281 pull_request_obj.source_repo.get_api_data())
3281 pull_request_obj.source_repo.get_api_data())
3282 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3282 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3283
3283
3284 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3284 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3285 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3285 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3286 attrs.revisions = pull_request_obj.revisions
3286 attrs.revisions = pull_request_obj.revisions
3287
3287
3288 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3288 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3289
3289
3290 return PullRequestDisplay(attrs, internal=internal_methods)
3290 return PullRequestDisplay(attrs, internal=internal_methods)
3291
3291
3292 def is_closed(self):
3292 def is_closed(self):
3293 return self.status == self.STATUS_CLOSED
3293 return self.status == self.STATUS_CLOSED
3294
3294
3295 def __json__(self):
3295 def __json__(self):
3296 return {
3296 return {
3297 'revisions': self.revisions,
3297 'revisions': self.revisions,
3298 }
3298 }
3299
3299
3300 def calculated_review_status(self):
3300 def calculated_review_status(self):
3301 from rhodecode.model.changeset_status import ChangesetStatusModel
3301 from rhodecode.model.changeset_status import ChangesetStatusModel
3302 return ChangesetStatusModel().calculated_review_status(self)
3302 return ChangesetStatusModel().calculated_review_status(self)
3303
3303
3304 def reviewers_statuses(self):
3304 def reviewers_statuses(self):
3305 from rhodecode.model.changeset_status import ChangesetStatusModel
3305 from rhodecode.model.changeset_status import ChangesetStatusModel
3306 return ChangesetStatusModel().reviewers_statuses(self)
3306 return ChangesetStatusModel().reviewers_statuses(self)
3307
3307
3308
3308
3309 class PullRequestVersion(Base, _PullRequestBase):
3309 class PullRequestVersion(Base, _PullRequestBase):
3310 __tablename__ = 'pull_request_versions'
3310 __tablename__ = 'pull_request_versions'
3311 __table_args__ = (
3311 __table_args__ = (
3312 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3312 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3313 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3313 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3314 )
3314 )
3315
3315
3316 pull_request_version_id = Column(
3316 pull_request_version_id = Column(
3317 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3317 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3318 pull_request_id = Column(
3318 pull_request_id = Column(
3319 'pull_request_id', Integer(),
3319 'pull_request_id', Integer(),
3320 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3320 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3321 pull_request = relationship('PullRequest')
3321 pull_request = relationship('PullRequest')
3322
3322
3323 def __repr__(self):
3323 def __repr__(self):
3324 if self.pull_request_version_id:
3324 if self.pull_request_version_id:
3325 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3325 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3326 else:
3326 else:
3327 return '<DB:PullRequestVersion at %#x>' % id(self)
3327 return '<DB:PullRequestVersion at %#x>' % id(self)
3328
3328
3329 @property
3329 @property
3330 def reviewers(self):
3330 def reviewers(self):
3331 return self.pull_request.reviewers
3331 return self.pull_request.reviewers
3332
3332
3333 @property
3333 @property
3334 def versions(self):
3334 def versions(self):
3335 return self.pull_request.versions
3335 return self.pull_request.versions
3336
3336
3337 def is_closed(self):
3337 def is_closed(self):
3338 # calculate from original
3338 # calculate from original
3339 return self.pull_request.status == self.STATUS_CLOSED
3339 return self.pull_request.status == self.STATUS_CLOSED
3340
3340
3341 def calculated_review_status(self):
3341 def calculated_review_status(self):
3342 return self.pull_request.calculated_review_status()
3342 return self.pull_request.calculated_review_status()
3343
3343
3344 def reviewers_statuses(self):
3344 def reviewers_statuses(self):
3345 return self.pull_request.reviewers_statuses()
3345 return self.pull_request.reviewers_statuses()
3346
3346
3347
3347
3348 class PullRequestReviewers(Base, BaseModel):
3348 class PullRequestReviewers(Base, BaseModel):
3349 __tablename__ = 'pull_request_reviewers'
3349 __tablename__ = 'pull_request_reviewers'
3350 __table_args__ = (
3350 __table_args__ = (
3351 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3351 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3352 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3352 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3353 )
3353 )
3354
3354
3355 def __init__(self, user=None, pull_request=None, reasons=None):
3355 def __init__(self, user=None, pull_request=None, reasons=None):
3356 self.user = user
3356 self.user = user
3357 self.pull_request = pull_request
3357 self.pull_request = pull_request
3358 self.reasons = reasons or []
3358 self.reasons = reasons or []
3359
3359
3360 @hybrid_property
3360 @hybrid_property
3361 def reasons(self):
3361 def reasons(self):
3362 if not self._reasons:
3362 if not self._reasons:
3363 return []
3363 return []
3364 return self._reasons
3364 return self._reasons
3365
3365
3366 @reasons.setter
3366 @reasons.setter
3367 def reasons(self, val):
3367 def reasons(self, val):
3368 val = val or []
3368 val = val or []
3369 if any(not isinstance(x, basestring) for x in val):
3369 if any(not isinstance(x, basestring) for x in val):
3370 raise Exception('invalid reasons type, must be list of strings')
3370 raise Exception('invalid reasons type, must be list of strings')
3371 self._reasons = val
3371 self._reasons = val
3372
3372
3373 pull_requests_reviewers_id = Column(
3373 pull_requests_reviewers_id = Column(
3374 'pull_requests_reviewers_id', Integer(), nullable=False,
3374 'pull_requests_reviewers_id', Integer(), nullable=False,
3375 primary_key=True)
3375 primary_key=True)
3376 pull_request_id = Column(
3376 pull_request_id = Column(
3377 "pull_request_id", Integer(),
3377 "pull_request_id", Integer(),
3378 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3378 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3379 user_id = Column(
3379 user_id = Column(
3380 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3380 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3381 _reasons = Column(
3381 _reasons = Column(
3382 'reason', MutationList.as_mutable(
3382 'reason', MutationList.as_mutable(
3383 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3383 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3384
3384
3385 user = relationship('User')
3385 user = relationship('User')
3386 pull_request = relationship('PullRequest')
3386 pull_request = relationship('PullRequest')
3387
3387
3388
3388
3389 class Notification(Base, BaseModel):
3389 class Notification(Base, BaseModel):
3390 __tablename__ = 'notifications'
3390 __tablename__ = 'notifications'
3391 __table_args__ = (
3391 __table_args__ = (
3392 Index('notification_type_idx', 'type'),
3392 Index('notification_type_idx', 'type'),
3393 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3393 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3394 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3394 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3395 )
3395 )
3396
3396
3397 TYPE_CHANGESET_COMMENT = u'cs_comment'
3397 TYPE_CHANGESET_COMMENT = u'cs_comment'
3398 TYPE_MESSAGE = u'message'
3398 TYPE_MESSAGE = u'message'
3399 TYPE_MENTION = u'mention'
3399 TYPE_MENTION = u'mention'
3400 TYPE_REGISTRATION = u'registration'
3400 TYPE_REGISTRATION = u'registration'
3401 TYPE_PULL_REQUEST = u'pull_request'
3401 TYPE_PULL_REQUEST = u'pull_request'
3402 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3402 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3403
3403
3404 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3404 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3405 subject = Column('subject', Unicode(512), nullable=True)
3405 subject = Column('subject', Unicode(512), nullable=True)
3406 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3406 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3407 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3407 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3408 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3408 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3409 type_ = Column('type', Unicode(255))
3409 type_ = Column('type', Unicode(255))
3410
3410
3411 created_by_user = relationship('User')
3411 created_by_user = relationship('User')
3412 notifications_to_users = relationship('UserNotification', lazy='joined',
3412 notifications_to_users = relationship('UserNotification', lazy='joined',
3413 cascade="all, delete, delete-orphan")
3413 cascade="all, delete, delete-orphan")
3414
3414
3415 @property
3415 @property
3416 def recipients(self):
3416 def recipients(self):
3417 return [x.user for x in UserNotification.query()\
3417 return [x.user for x in UserNotification.query()\
3418 .filter(UserNotification.notification == self)\
3418 .filter(UserNotification.notification == self)\
3419 .order_by(UserNotification.user_id.asc()).all()]
3419 .order_by(UserNotification.user_id.asc()).all()]
3420
3420
3421 @classmethod
3421 @classmethod
3422 def create(cls, created_by, subject, body, recipients, type_=None):
3422 def create(cls, created_by, subject, body, recipients, type_=None):
3423 if type_ is None:
3423 if type_ is None:
3424 type_ = Notification.TYPE_MESSAGE
3424 type_ = Notification.TYPE_MESSAGE
3425
3425
3426 notification = cls()
3426 notification = cls()
3427 notification.created_by_user = created_by
3427 notification.created_by_user = created_by
3428 notification.subject = subject
3428 notification.subject = subject
3429 notification.body = body
3429 notification.body = body
3430 notification.type_ = type_
3430 notification.type_ = type_
3431 notification.created_on = datetime.datetime.now()
3431 notification.created_on = datetime.datetime.now()
3432
3432
3433 for u in recipients:
3433 for u in recipients:
3434 assoc = UserNotification()
3434 assoc = UserNotification()
3435 assoc.notification = notification
3435 assoc.notification = notification
3436
3436
3437 # if created_by is inside recipients mark his notification
3437 # if created_by is inside recipients mark his notification
3438 # as read
3438 # as read
3439 if u.user_id == created_by.user_id:
3439 if u.user_id == created_by.user_id:
3440 assoc.read = True
3440 assoc.read = True
3441
3441
3442 u.notifications.append(assoc)
3442 u.notifications.append(assoc)
3443 Session().add(notification)
3443 Session().add(notification)
3444
3444
3445 return notification
3445 return notification
3446
3446
3447 @property
3447 @property
3448 def description(self):
3448 def description(self):
3449 from rhodecode.model.notification import NotificationModel
3449 from rhodecode.model.notification import NotificationModel
3450 return NotificationModel().make_description(self)
3450 return NotificationModel().make_description(self)
3451
3451
3452
3452
3453 class UserNotification(Base, BaseModel):
3453 class UserNotification(Base, BaseModel):
3454 __tablename__ = 'user_to_notification'
3454 __tablename__ = 'user_to_notification'
3455 __table_args__ = (
3455 __table_args__ = (
3456 UniqueConstraint('user_id', 'notification_id'),
3456 UniqueConstraint('user_id', 'notification_id'),
3457 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3457 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3458 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3458 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3459 )
3459 )
3460 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3460 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3461 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3461 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3462 read = Column('read', Boolean, default=False)
3462 read = Column('read', Boolean, default=False)
3463 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3463 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3464
3464
3465 user = relationship('User', lazy="joined")
3465 user = relationship('User', lazy="joined")
3466 notification = relationship('Notification', lazy="joined",
3466 notification = relationship('Notification', lazy="joined",
3467 order_by=lambda: Notification.created_on.desc(),)
3467 order_by=lambda: Notification.created_on.desc(),)
3468
3468
3469 def mark_as_read(self):
3469 def mark_as_read(self):
3470 self.read = True
3470 self.read = True
3471 Session().add(self)
3471 Session().add(self)
3472
3472
3473
3473
3474 class Gist(Base, BaseModel):
3474 class Gist(Base, BaseModel):
3475 __tablename__ = 'gists'
3475 __tablename__ = 'gists'
3476 __table_args__ = (
3476 __table_args__ = (
3477 Index('g_gist_access_id_idx', 'gist_access_id'),
3477 Index('g_gist_access_id_idx', 'gist_access_id'),
3478 Index('g_created_on_idx', 'created_on'),
3478 Index('g_created_on_idx', 'created_on'),
3479 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3479 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3480 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3480 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3481 )
3481 )
3482 GIST_PUBLIC = u'public'
3482 GIST_PUBLIC = u'public'
3483 GIST_PRIVATE = u'private'
3483 GIST_PRIVATE = u'private'
3484 DEFAULT_FILENAME = u'gistfile1.txt'
3484 DEFAULT_FILENAME = u'gistfile1.txt'
3485
3485
3486 ACL_LEVEL_PUBLIC = u'acl_public'
3486 ACL_LEVEL_PUBLIC = u'acl_public'
3487 ACL_LEVEL_PRIVATE = u'acl_private'
3487 ACL_LEVEL_PRIVATE = u'acl_private'
3488
3488
3489 gist_id = Column('gist_id', Integer(), primary_key=True)
3489 gist_id = Column('gist_id', Integer(), primary_key=True)
3490 gist_access_id = Column('gist_access_id', Unicode(250))
3490 gist_access_id = Column('gist_access_id', Unicode(250))
3491 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3491 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3492 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3492 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3493 gist_expires = Column('gist_expires', Float(53), nullable=False)
3493 gist_expires = Column('gist_expires', Float(53), nullable=False)
3494 gist_type = Column('gist_type', Unicode(128), nullable=False)
3494 gist_type = Column('gist_type', Unicode(128), nullable=False)
3495 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3495 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3496 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3496 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3497 acl_level = Column('acl_level', Unicode(128), nullable=True)
3497 acl_level = Column('acl_level', Unicode(128), nullable=True)
3498
3498
3499 owner = relationship('User')
3499 owner = relationship('User')
3500
3500
3501 def __repr__(self):
3501 def __repr__(self):
3502 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3502 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3503
3503
3504 @classmethod
3504 @classmethod
3505 def get_or_404(cls, id_):
3505 def get_or_404(cls, id_):
3506 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3506 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3507 if not res:
3507 if not res:
3508 raise HTTPNotFound
3508 raise HTTPNotFound
3509 return res
3509 return res
3510
3510
3511 @classmethod
3511 @classmethod
3512 def get_by_access_id(cls, gist_access_id):
3512 def get_by_access_id(cls, gist_access_id):
3513 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3513 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3514
3514
3515 def gist_url(self):
3515 def gist_url(self):
3516 import rhodecode
3516 import rhodecode
3517 alias_url = rhodecode.CONFIG.get('gist_alias_url')
3517 alias_url = rhodecode.CONFIG.get('gist_alias_url')
3518 if alias_url:
3518 if alias_url:
3519 return alias_url.replace('{gistid}', self.gist_access_id)
3519 return alias_url.replace('{gistid}', self.gist_access_id)
3520
3520
3521 return url('gist', gist_id=self.gist_access_id, qualified=True)
3521 return url('gist', gist_id=self.gist_access_id, qualified=True)
3522
3522
3523 @classmethod
3523 @classmethod
3524 def base_path(cls):
3524 def base_path(cls):
3525 """
3525 """
3526 Returns base path when all gists are stored
3526 Returns base path when all gists are stored
3527
3527
3528 :param cls:
3528 :param cls:
3529 """
3529 """
3530 from rhodecode.model.gist import GIST_STORE_LOC
3530 from rhodecode.model.gist import GIST_STORE_LOC
3531 q = Session().query(RhodeCodeUi)\
3531 q = Session().query(RhodeCodeUi)\
3532 .filter(RhodeCodeUi.ui_key == URL_SEP)
3532 .filter(RhodeCodeUi.ui_key == URL_SEP)
3533 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3533 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3534 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3534 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3535
3535
3536 def get_api_data(self):
3536 def get_api_data(self):
3537 """
3537 """
3538 Common function for generating gist related data for API
3538 Common function for generating gist related data for API
3539 """
3539 """
3540 gist = self
3540 gist = self
3541 data = {
3541 data = {
3542 'gist_id': gist.gist_id,
3542 'gist_id': gist.gist_id,
3543 'type': gist.gist_type,
3543 'type': gist.gist_type,
3544 'access_id': gist.gist_access_id,
3544 'access_id': gist.gist_access_id,
3545 'description': gist.gist_description,
3545 'description': gist.gist_description,
3546 'url': gist.gist_url(),
3546 'url': gist.gist_url(),
3547 'expires': gist.gist_expires,
3547 'expires': gist.gist_expires,
3548 'created_on': gist.created_on,
3548 'created_on': gist.created_on,
3549 'modified_at': gist.modified_at,
3549 'modified_at': gist.modified_at,
3550 'content': None,
3550 'content': None,
3551 'acl_level': gist.acl_level,
3551 'acl_level': gist.acl_level,
3552 }
3552 }
3553 return data
3553 return data
3554
3554
3555 def __json__(self):
3555 def __json__(self):
3556 data = dict(
3556 data = dict(
3557 )
3557 )
3558 data.update(self.get_api_data())
3558 data.update(self.get_api_data())
3559 return data
3559 return data
3560 # SCM functions
3560 # SCM functions
3561
3561
3562 def scm_instance(self, **kwargs):
3562 def scm_instance(self, **kwargs):
3563 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3563 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3564 return get_vcs_instance(
3564 return get_vcs_instance(
3565 repo_path=safe_str(full_repo_path), create=False)
3565 repo_path=safe_str(full_repo_path), create=False)
3566
3566
3567
3567
3568 class ExternalIdentity(Base, BaseModel):
3568 class ExternalIdentity(Base, BaseModel):
3569 __tablename__ = 'external_identities'
3569 __tablename__ = 'external_identities'
3570 __table_args__ = (
3570 __table_args__ = (
3571 Index('local_user_id_idx', 'local_user_id'),
3571 Index('local_user_id_idx', 'local_user_id'),
3572 Index('external_id_idx', 'external_id'),
3572 Index('external_id_idx', 'external_id'),
3573 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3573 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3574 'mysql_charset': 'utf8'})
3574 'mysql_charset': 'utf8'})
3575
3575
3576 external_id = Column('external_id', Unicode(255), default=u'',
3576 external_id = Column('external_id', Unicode(255), default=u'',
3577 primary_key=True)
3577 primary_key=True)
3578 external_username = Column('external_username', Unicode(1024), default=u'')
3578 external_username = Column('external_username', Unicode(1024), default=u'')
3579 local_user_id = Column('local_user_id', Integer(),
3579 local_user_id = Column('local_user_id', Integer(),
3580 ForeignKey('users.user_id'), primary_key=True)
3580 ForeignKey('users.user_id'), primary_key=True)
3581 provider_name = Column('provider_name', Unicode(255), default=u'',
3581 provider_name = Column('provider_name', Unicode(255), default=u'',
3582 primary_key=True)
3582 primary_key=True)
3583 access_token = Column('access_token', String(1024), default=u'')
3583 access_token = Column('access_token', String(1024), default=u'')
3584 alt_token = Column('alt_token', String(1024), default=u'')
3584 alt_token = Column('alt_token', String(1024), default=u'')
3585 token_secret = Column('token_secret', String(1024), default=u'')
3585 token_secret = Column('token_secret', String(1024), default=u'')
3586
3586
3587 @classmethod
3587 @classmethod
3588 def by_external_id_and_provider(cls, external_id, provider_name,
3588 def by_external_id_and_provider(cls, external_id, provider_name,
3589 local_user_id=None):
3589 local_user_id=None):
3590 """
3590 """
3591 Returns ExternalIdentity instance based on search params
3591 Returns ExternalIdentity instance based on search params
3592
3592
3593 :param external_id:
3593 :param external_id:
3594 :param provider_name:
3594 :param provider_name:
3595 :return: ExternalIdentity
3595 :return: ExternalIdentity
3596 """
3596 """
3597 query = cls.query()
3597 query = cls.query()
3598 query = query.filter(cls.external_id == external_id)
3598 query = query.filter(cls.external_id == external_id)
3599 query = query.filter(cls.provider_name == provider_name)
3599 query = query.filter(cls.provider_name == provider_name)
3600 if local_user_id:
3600 if local_user_id:
3601 query = query.filter(cls.local_user_id == local_user_id)
3601 query = query.filter(cls.local_user_id == local_user_id)
3602 return query.first()
3602 return query.first()
3603
3603
3604 @classmethod
3604 @classmethod
3605 def user_by_external_id_and_provider(cls, external_id, provider_name):
3605 def user_by_external_id_and_provider(cls, external_id, provider_name):
3606 """
3606 """
3607 Returns User instance based on search params
3607 Returns User instance based on search params
3608
3608
3609 :param external_id:
3609 :param external_id:
3610 :param provider_name:
3610 :param provider_name:
3611 :return: User
3611 :return: User
3612 """
3612 """
3613 query = User.query()
3613 query = User.query()
3614 query = query.filter(cls.external_id == external_id)
3614 query = query.filter(cls.external_id == external_id)
3615 query = query.filter(cls.provider_name == provider_name)
3615 query = query.filter(cls.provider_name == provider_name)
3616 query = query.filter(User.user_id == cls.local_user_id)
3616 query = query.filter(User.user_id == cls.local_user_id)
3617 return query.first()
3617 return query.first()
3618
3618
3619 @classmethod
3619 @classmethod
3620 def by_local_user_id(cls, local_user_id):
3620 def by_local_user_id(cls, local_user_id):
3621 """
3621 """
3622 Returns all tokens for user
3622 Returns all tokens for user
3623
3623
3624 :param local_user_id:
3624 :param local_user_id:
3625 :return: ExternalIdentity
3625 :return: ExternalIdentity
3626 """
3626 """
3627 query = cls.query()
3627 query = cls.query()
3628 query = query.filter(cls.local_user_id == local_user_id)
3628 query = query.filter(cls.local_user_id == local_user_id)
3629 return query
3629 return query
3630
3630
3631
3631
3632 class Integration(Base, BaseModel):
3632 class Integration(Base, BaseModel):
3633 __tablename__ = 'integrations'
3633 __tablename__ = 'integrations'
3634 __table_args__ = (
3634 __table_args__ = (
3635 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3635 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3636 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3636 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3637 )
3637 )
3638
3638
3639 integration_id = Column('integration_id', Integer(), primary_key=True)
3639 integration_id = Column('integration_id', Integer(), primary_key=True)
3640 integration_type = Column('integration_type', String(255))
3640 integration_type = Column('integration_type', String(255))
3641 enabled = Column('enabled', Boolean(), nullable=False)
3641 enabled = Column('enabled', Boolean(), nullable=False)
3642 name = Column('name', String(255), nullable=False)
3642 name = Column('name', String(255), nullable=False)
3643 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
3643 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
3644 default=False)
3644 default=False)
3645
3645
3646 settings = Column(
3646 settings = Column(
3647 'settings_json', MutationObj.as_mutable(
3647 'settings_json', MutationObj.as_mutable(
3648 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3648 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3649 repo_id = Column(
3649 repo_id = Column(
3650 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
3650 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
3651 nullable=True, unique=None, default=None)
3651 nullable=True, unique=None, default=None)
3652 repo = relationship('Repository', lazy='joined')
3652 repo = relationship('Repository', lazy='joined')
3653
3653
3654 repo_group_id = Column(
3654 repo_group_id = Column(
3655 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
3655 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
3656 nullable=True, unique=None, default=None)
3656 nullable=True, unique=None, default=None)
3657 repo_group = relationship('RepoGroup', lazy='joined')
3657 repo_group = relationship('RepoGroup', lazy='joined')
3658
3658
3659 @property
3659 @property
3660 def scope(self):
3660 def scope(self):
3661 if self.repo:
3661 if self.repo:
3662 return repr(self.repo)
3662 return repr(self.repo)
3663 if self.repo_group:
3663 if self.repo_group:
3664 if self.child_repos_only:
3664 if self.child_repos_only:
3665 return repr(self.repo_group) + ' (child repos only)'
3665 return repr(self.repo_group) + ' (child repos only)'
3666 else:
3666 else:
3667 return repr(self.repo_group) + ' (recursive)'
3667 return repr(self.repo_group) + ' (recursive)'
3668 if self.child_repos_only:
3668 if self.child_repos_only:
3669 return 'root_repos'
3669 return 'root_repos'
3670 return 'global'
3670 return 'global'
3671
3671
3672 def __repr__(self):
3672 def __repr__(self):
3673 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
3673 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
3674
3674
3675
3675
3676 class RepoReviewRuleUser(Base, BaseModel):
3676 class RepoReviewRuleUser(Base, BaseModel):
3677 __tablename__ = 'repo_review_rules_users'
3677 __tablename__ = 'repo_review_rules_users'
3678 __table_args__ = (
3678 __table_args__ = (
3679 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3679 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3680 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3680 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3681 )
3681 )
3682 repo_review_rule_user_id = Column(
3682 repo_review_rule_user_id = Column(
3683 'repo_review_rule_user_id', Integer(), primary_key=True)
3683 'repo_review_rule_user_id', Integer(), primary_key=True)
3684 repo_review_rule_id = Column("repo_review_rule_id",
3684 repo_review_rule_id = Column("repo_review_rule_id",
3685 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3685 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3686 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'),
3686 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'),
3687 nullable=False)
3687 nullable=False)
3688 user = relationship('User')
3688 user = relationship('User')
3689
3689
3690
3690
3691 class RepoReviewRuleUserGroup(Base, BaseModel):
3691 class RepoReviewRuleUserGroup(Base, BaseModel):
3692 __tablename__ = 'repo_review_rules_users_groups'
3692 __tablename__ = 'repo_review_rules_users_groups'
3693 __table_args__ = (
3693 __table_args__ = (
3694 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3694 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3695 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3695 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3696 )
3696 )
3697 repo_review_rule_users_group_id = Column(
3697 repo_review_rule_users_group_id = Column(
3698 'repo_review_rule_users_group_id', Integer(), primary_key=True)
3698 'repo_review_rule_users_group_id', Integer(), primary_key=True)
3699 repo_review_rule_id = Column("repo_review_rule_id",
3699 repo_review_rule_id = Column("repo_review_rule_id",
3700 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3700 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3701 users_group_id = Column("users_group_id", Integer(),
3701 users_group_id = Column("users_group_id", Integer(),
3702 ForeignKey('users_groups.users_group_id'), nullable=False)
3702 ForeignKey('users_groups.users_group_id'), nullable=False)
3703 users_group = relationship('UserGroup')
3703 users_group = relationship('UserGroup')
3704
3704
3705
3705
3706 class RepoReviewRule(Base, BaseModel):
3706 class RepoReviewRule(Base, BaseModel):
3707 __tablename__ = 'repo_review_rules'
3707 __tablename__ = 'repo_review_rules'
3708 __table_args__ = (
3708 __table_args__ = (
3709 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3709 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3710 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3710 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3711 )
3711 )
3712
3712
3713 repo_review_rule_id = Column(
3713 repo_review_rule_id = Column(
3714 'repo_review_rule_id', Integer(), primary_key=True)
3714 'repo_review_rule_id', Integer(), primary_key=True)
3715 repo_id = Column(
3715 repo_id = Column(
3716 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
3716 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
3717 repo = relationship('Repository', backref='review_rules')
3717 repo = relationship('Repository', backref='review_rules')
3718
3718
3719 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3719 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3720 default=u'*') # glob
3720 default=u'*') # glob
3721 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3721 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3722 default=u'*') # glob
3722 default=u'*') # glob
3723
3723
3724 use_authors_for_review = Column("use_authors_for_review", Boolean(),
3724 use_authors_for_review = Column("use_authors_for_review", Boolean(),
3725 nullable=False, default=False)
3725 nullable=False, default=False)
3726 rule_users = relationship('RepoReviewRuleUser')
3726 rule_users = relationship('RepoReviewRuleUser')
3727 rule_user_groups = relationship('RepoReviewRuleUserGroup')
3727 rule_user_groups = relationship('RepoReviewRuleUserGroup')
3728
3728
3729 @hybrid_property
3729 @hybrid_property
3730 def branch_pattern(self):
3730 def branch_pattern(self):
3731 return self._branch_pattern or '*'
3731 return self._branch_pattern or '*'
3732
3732
3733 def _validate_glob(self, value):
3733 def _validate_glob(self, value):
3734 re.compile('^' + glob2re(value) + '$')
3734 re.compile('^' + glob2re(value) + '$')
3735
3735
3736 @branch_pattern.setter
3736 @branch_pattern.setter
3737 def branch_pattern(self, value):
3737 def branch_pattern(self, value):
3738 self._validate_glob(value)
3738 self._validate_glob(value)
3739 self._branch_pattern = value or '*'
3739 self._branch_pattern = value or '*'
3740
3740
3741 @hybrid_property
3741 @hybrid_property
3742 def file_pattern(self):
3742 def file_pattern(self):
3743 return self._file_pattern or '*'
3743 return self._file_pattern or '*'
3744
3744
3745 @file_pattern.setter
3745 @file_pattern.setter
3746 def file_pattern(self, value):
3746 def file_pattern(self, value):
3747 self._validate_glob(value)
3747 self._validate_glob(value)
3748 self._file_pattern = value or '*'
3748 self._file_pattern = value or '*'
3749
3749
3750 def matches(self, branch, files_changed):
3750 def matches(self, branch, files_changed):
3751 """
3751 """
3752 Check if this review rule matches a branch/files in a pull request
3752 Check if this review rule matches a branch/files in a pull request
3753
3753
3754 :param branch: branch name for the commit
3754 :param branch: branch name for the commit
3755 :param files_changed: list of file paths changed in the pull request
3755 :param files_changed: list of file paths changed in the pull request
3756 """
3756 """
3757
3757
3758 branch = branch or ''
3758 branch = branch or ''
3759 files_changed = files_changed or []
3759 files_changed = files_changed or []
3760
3760
3761 branch_matches = True
3761 branch_matches = True
3762 if branch:
3762 if branch:
3763 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
3763 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
3764 branch_matches = bool(branch_regex.search(branch))
3764 branch_matches = bool(branch_regex.search(branch))
3765
3765
3766 files_matches = True
3766 files_matches = True
3767 if self.file_pattern != '*':
3767 if self.file_pattern != '*':
3768 files_matches = False
3768 files_matches = False
3769 file_regex = re.compile(glob2re(self.file_pattern))
3769 file_regex = re.compile(glob2re(self.file_pattern))
3770 for filename in files_changed:
3770 for filename in files_changed:
3771 if file_regex.search(filename):
3771 if file_regex.search(filename):
3772 files_matches = True
3772 files_matches = True
3773 break
3773 break
3774
3774
3775 return branch_matches and files_matches
3775 return branch_matches and files_matches
3776
3776
3777 @property
3777 @property
3778 def review_users(self):
3778 def review_users(self):
3779 """ Returns the users which this rule applies to """
3779 """ Returns the users which this rule applies to """
3780
3780
3781 users = set()
3781 users = set()
3782 users |= set([
3782 users |= set([
3783 rule_user.user for rule_user in self.rule_users
3783 rule_user.user for rule_user in self.rule_users
3784 if rule_user.user.active])
3784 if rule_user.user.active])
3785 users |= set(
3785 users |= set(
3786 member.user
3786 member.user
3787 for rule_user_group in self.rule_user_groups
3787 for rule_user_group in self.rule_user_groups
3788 for member in rule_user_group.users_group.members
3788 for member in rule_user_group.users_group.members
3789 if member.user.active
3789 if member.user.active
3790 )
3790 )
3791 return users
3791 return users
3792
3792
3793 def __repr__(self):
3793 def __repr__(self):
3794 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
3794 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
3795 self.repo_review_rule_id, self.repo)
3795 self.repo_review_rule_id, self.repo)
3796
3796
3797
3797
3798 class DbMigrateVersion(Base, BaseModel):
3798 class DbMigrateVersion(Base, BaseModel):
3799 __tablename__ = 'db_migrate_version'
3799 __tablename__ = 'db_migrate_version'
3800 __table_args__ = (
3800 __table_args__ = (
3801 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3801 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3802 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3802 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3803 )
3803 )
3804 repository_id = Column('repository_id', String(250), primary_key=True)
3804 repository_id = Column('repository_id', String(250), primary_key=True)
3805 repository_path = Column('repository_path', Text)
3805 repository_path = Column('repository_path', Text)
3806 version = Column('version', Integer)
3806 version = Column('version', Integer)
3807
3807
3808
3808
3809 class DbSession(Base, BaseModel):
3809 class DbSession(Base, BaseModel):
3810 __tablename__ = 'db_session'
3810 __tablename__ = 'db_session'
3811 __table_args__ = (
3811 __table_args__ = (
3812 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3812 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3813 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3813 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3814 )
3814 )
3815
3816 def __repr__(self):
3817 return '<DB:DbSession({})>'.format(self.id)
3818
3819 id = Column('id', Integer())
3815 namespace = Column('namespace', String(255), primary_key=True)
3820 namespace = Column('namespace', String(255), primary_key=True)
3816 accessed = Column('accessed', DateTime, nullable=False)
3821 accessed = Column('accessed', DateTime, nullable=False)
3817 created = Column('created', DateTime, nullable=False)
3822 created = Column('created', DateTime, nullable=False)
3818 data = Column('data', PickleType, nullable=False) No newline at end of file
3823 data = Column('data', PickleType, nullable=False)
@@ -1,56 +1,56 b''
1 <div class="panel panel-default">
1 <div class="panel panel-default">
2 <div class="panel-heading">
2 <div class="panel-heading">
3 <h3 class="panel-title">${_('Email Configuration')}</h3>
3 <h3 class="panel-title">${_('Email Configuration')}</h3>
4 </div>
4 </div>
5 <div class="panel-body">
5 <div class="panel-body">
6 <%
6 <%
7 elems = [
7 elems = [
8 (_('Email prefix'), c.rhodecode_ini.get('email_prefix'), ''),
8 (_('Email prefix'), c.rhodecode_ini.get('email_prefix'), ''),
9 (_('RhodeCode email from'), c.rhodecode_ini.get('app_email_from'), ''),
9 (_('RhodeCode email from'), c.rhodecode_ini.get('app_email_from'), ''),
10 (_('Error email from'), c.rhodecode_ini.get('error_email_from'), ''),
10 (_('Error email from'), c.rhodecode_ini.get('error_email_from'), ''),
11 (_('Error email recipients'), c.rhodecode_ini.get('email_to'), ''),
11 (_('Error email recipients'), c.rhodecode_ini.get('email_to'), ''),
12
12
13 (_('SMTP server'), c.rhodecode_ini.get('smtp_server'), ''),
13 (_('SMTP server'), c.rhodecode_ini.get('smtp_server'), ''),
14 (_('SMTP username'), c.rhodecode_ini.get('smtp_username'), ''),
14 (_('SMTP username'), c.rhodecode_ini.get('smtp_username'), ''),
15 (_('SMTP password'), '%s chars' % len(c.rhodecode_ini.get('smtp_password', '')), ''),
15 (_('SMTP password'), '%s chars' % len(c.rhodecode_ini.get('smtp_password', '')), ''),
16 (_('SMTP port'), c.rhodecode_ini.get('smtp_port'), ''),
16 (_('SMTP port'), c.rhodecode_ini.get('smtp_port'), ''),
17
17
18 (_('SMTP use TLS'), c.rhodecode_ini.get('smtp_use_tls'), ''),
18 (_('SMTP use TLS'), c.rhodecode_ini.get('smtp_use_tls'), ''),
19 (_('SMTP use SSL'), c.rhodecode_ini.get('smtp_use_ssl'), ''),
19 (_('SMTP use SSL'), c.rhodecode_ini.get('smtp_use_ssl'), ''),
20 (_('SMTP auth'), c.rhodecode_ini.get('smtp_auth'), ''),
20 (_('SMTP auth'), c.rhodecode_ini.get('smtp_auth'), ''),
21 ]
21 ]
22 %>
22 %>
23 <dl class="dl-horizontal">
23 <dl class="dl-horizontal settings">
24 %for dt, dd, tt in elems:
24 %for dt, dd, tt in elems:
25 <dt >${dt}:</dt>
25 <dt >${dt}:</dt>
26 <dd title="${tt}">${dd}</dd>
26 <dd title="${tt}">${dd}</dd>
27 %endfor
27 %endfor
28 </dl>
28 </dl>
29 </div>
29 </div>
30 </div>
30 </div>
31
31
32 <div class="panel panel-default">
32 <div class="panel panel-default">
33 <div class="panel-heading">
33 <div class="panel-heading">
34 <h3 class="panel-title">${_('Test Email')}</h3>
34 <h3 class="panel-title">${_('Test Email')}</h3>
35 </div>
35 </div>
36 <div class="panel-body">
36 <div class="panel-body">
37 ${h.secure_form(url('admin_settings_email'), method='post')}
37 ${h.secure_form(url('admin_settings_email'), method='post')}
38
38
39 <div class="field input">
39 <div class="field input">
40 ${h.text('test_email', size=60, placeholder=_('enter valid email'))}
40 ${h.text('test_email', size=60, placeholder=_('enter valid email'))}
41 </div>
41 </div>
42 <div class="field">
42 <div class="field">
43 <span class="help-block">
43 <span class="help-block">
44 ${_('Send an auto-generated email from this server to above email...')}
44 ${_('Send an auto-generated email from this server to above email...')}
45 </span>
45 </span>
46 </div>
46 </div>
47 <div class="buttons">
47 <div class="buttons">
48 ${h.submit('send',_('Send'),class_="btn")}
48 ${h.submit('send',_('Send'),class_="btn")}
49 </div>
49 </div>
50 ${h.end_form()}
50 ${h.end_form()}
51 </div>
51 </div>
52 </div>
52 </div>
53
53
54
54
55
55
56
56
General Comments 0
You need to be logged in to leave comments. Login now