##// END OF EJS Templates
repo-settings: converted repo settings to pyramid...
marcink -
r1716:24a2983a default
parent child Browse files
Show More
@@ -0,0 +1,178 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2011-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 logging
22
23 import deform
24 from pyramid.httpexceptions import HTTPFound
25 from pyramid.view import view_config
26
27 from rhodecode.apps._base import RepoAppView
28 from rhodecode.forms import RcForm
29 from rhodecode.lib import helpers as h
30 from rhodecode.lib import audit_logger
31 from rhodecode.lib.auth import (
32 LoginRequired, HasRepoPermissionAnyDecorator,
33 HasRepoPermissionAllDecorator, CSRFRequired)
34 from rhodecode.model.db import RepositoryField, RepoGroup
35 from rhodecode.model.meta import Session
36 from rhodecode.model.repo import RepoModel
37 from rhodecode.model.scm import RepoGroupList, ScmModel
38 from rhodecode.model.validation_schema.schemas import repo_schema
39
40 log = logging.getLogger(__name__)
41
42
43 class RepoSettingsView(RepoAppView):
44
45 def load_default_context(self):
46 c = self._get_local_tmpl_context()
47
48 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
49 c.repo_info = self.db_repo
50
51 acl_groups = RepoGroupList(
52 RepoGroup.query().all(),
53 perm_set=['group.write', 'group.admin'])
54 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
55 c.repo_groups_choices = map(lambda k: k[0], c.repo_groups)
56
57 # in case someone no longer have a group.write access to a repository
58 # pre fill the list with this entry, we don't care if this is the same
59 # but it will allow saving repo data properly.
60 repo_group = self.db_repo.group
61 if repo_group and repo_group.group_id not in c.repo_groups_choices:
62 c.repo_groups_choices.append(repo_group.group_id)
63 c.repo_groups.append(RepoGroup._generate_choice(repo_group))
64
65 if c.repository_requirements_missing or self.rhodecode_vcs_repo is None:
66 # we might be in missing requirement state, so we load things
67 # without touching scm_instance()
68 c.landing_revs_choices, c.landing_revs = \
69 ScmModel().get_repo_landing_revs()
70 else:
71 c.landing_revs_choices, c.landing_revs = \
72 ScmModel().get_repo_landing_revs(self.db_repo)
73
74 c.personal_repo_group = c.auth_user.personal_repo_group
75 c.repo_fields = RepositoryField.query()\
76 .filter(RepositoryField.repository == self.db_repo).all()
77
78 self._register_global_c(c)
79 return c
80
81 def _get_schema(self, c, old_values=None):
82 return repo_schema.RepoSettingsSchema().bind(
83 repo_type_options=[self.db_repo.repo_type],
84 repo_ref_options=c.landing_revs_choices,
85 repo_ref_items=c.landing_revs,
86 repo_repo_group_options=c.repo_groups_choices,
87 repo_repo_group_items=c.repo_groups,
88 # user caller
89 user=self._rhodecode_user,
90 old_values=old_values
91 )
92
93 @LoginRequired()
94 @HasRepoPermissionAnyDecorator('repository.admin')
95 @view_config(
96 route_name='edit_repo', request_method='GET',
97 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
98 def edit_settings(self):
99 c = self.load_default_context()
100 c.active = 'settings'
101
102 defaults = RepoModel()._get_defaults(self.db_repo_name)
103 defaults['repo_owner'] = defaults['user']
104 defaults['repo_landing_commit_ref'] = defaults['repo_landing_rev']
105
106 schema = self._get_schema(c)
107 c.form = RcForm(schema, appstruct=defaults)
108 return self._get_template_context(c)
109
110 @LoginRequired()
111 @HasRepoPermissionAllDecorator('repository.admin')
112 @CSRFRequired()
113 @view_config(
114 route_name='edit_repo', request_method='POST',
115 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
116 def edit_settings_update(self):
117 _ = self.request.translate
118 c = self.load_default_context()
119 c.active = 'settings'
120 old_repo_name = self.db_repo_name
121
122 old_values = self.db_repo.get_api_data()
123 schema = self._get_schema(c, old_values=old_values)
124
125 c.form = RcForm(schema)
126 pstruct = self.request.POST.items()
127 pstruct.append(('repo_type', self.db_repo.repo_type))
128 try:
129 schema_data = c.form.validate(pstruct)
130 except deform.ValidationFailure as err_form:
131 return self._get_template_context(c)
132
133 # data is now VALID, proceed with updates
134 # save validated data back into the updates dict
135 validated_updates = dict(
136 repo_name=schema_data['repo_group']['repo_name_without_group'],
137 repo_group=schema_data['repo_group']['repo_group_id'],
138
139 user=schema_data['repo_owner'],
140 repo_description=schema_data['repo_description'],
141 repo_private=schema_data['repo_private'],
142 clone_uri=schema_data['repo_clone_uri'],
143 repo_landing_rev=schema_data['repo_landing_commit_ref'],
144 repo_enable_statistics=schema_data['repo_enable_statistics'],
145 repo_enable_locking=schema_data['repo_enable_locking'],
146 repo_enable_downloads=schema_data['repo_enable_downloads'],
147 )
148 # detect if CLONE URI changed, if we get OLD means we keep old values
149 if schema_data['repo_clone_uri_change'] == 'OLD':
150 validated_updates['clone_uri'] = self.db_repo.clone_uri
151
152 # use the new full name for redirect
153 new_repo_name = schema_data['repo_group']['repo_name_with_group']
154
155 # save extra fields into our validated data
156 for key, value in pstruct:
157 if key.startswith(RepositoryField.PREFIX):
158 validated_updates[key] = value
159
160 try:
161 RepoModel().update(self.db_repo, **validated_updates)
162 ScmModel().mark_for_invalidation(new_repo_name)
163
164 audit_logger.store(
165 'repo.edit', action_data={'old_data': old_values},
166 user=self._rhodecode_user, repo=self.db_repo)
167
168 Session().commit()
169
170 h.flash(_('Repository {} updated successfully').format(
171 old_repo_name), category='success')
172 except Exception:
173 log.exception("Exception during update of repository")
174 h.flash(_('Error occurred during update of repository {}').format(
175 old_repo_name), category='error')
176
177 raise HTTPFound(
178 self.request.route_path('edit_repo', repo_name=new_repo_name))
@@ -1,46 +1,50 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 def includeme(config):
22 def includeme(config):
23
23
24 # Settings
25 config.add_route(
26 name='edit_repo',
27 pattern='/{repo_name:.*?[^/]}/settings', repo_route=True)
28
24 config.add_route(
29 config.add_route(
25 name='repo_maintenance',
30 name='repo_maintenance',
26 pattern='/{repo_name:.*?[^/]}/maintenance', repo_route=True)
31 pattern='/{repo_name:.*?[^/]}/maintenance', repo_route=True)
27
32
28 config.add_route(
33 config.add_route(
29 name='repo_maintenance_execute',
34 name='repo_maintenance_execute',
30 pattern='/{repo_name:.*?[^/]}/maintenance/execute', repo_route=True)
35 pattern='/{repo_name:.*?[^/]}/maintenance/execute', repo_route=True)
31
36
32
33 # Strip
37 # Strip
34 config.add_route(
38 config.add_route(
35 name='strip',
39 name='strip',
36 pattern='/{repo_name:.*?[^/]}/strip', repo_route=True)
40 pattern='/{repo_name:.*?[^/]}/strip', repo_route=True)
37
41
38 config.add_route(
42 config.add_route(
39 name='strip_check',
43 name='strip_check',
40 pattern='/{repo_name:.*?[^/]}/strip_check', repo_route=True)
44 pattern='/{repo_name:.*?[^/]}/strip_check', repo_route=True)
41
45
42 config.add_route(
46 config.add_route(
43 name='strip_execute',
47 name='strip_execute',
44 pattern='/{repo_name:.*?[^/]}/strip_execute', repo_route=True)
48 pattern='/{repo_name:.*?[^/]}/strip_execute', repo_route=True)
45 # Scan module for configuration decorators.
49 # Scan module for configuration decorators.
46 config.scan()
50 config.scan()
@@ -1,1116 +1,1107 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 # prefix for non repository related links needs to be prefixed with `/`
35 # prefix for non repository related links needs to be prefixed with `/`
36 ADMIN_PREFIX = '/_admin'
36 ADMIN_PREFIX = '/_admin'
37 STATIC_FILE_PREFIX = '/_static'
37 STATIC_FILE_PREFIX = '/_static'
38
38
39 # Default requirements for URL parts
39 # Default requirements for URL parts
40 URL_NAME_REQUIREMENTS = {
40 URL_NAME_REQUIREMENTS = {
41 # group name can have a slash in them, but they must not end with a slash
41 # group name can have a slash in them, but they must not end with a slash
42 'group_name': r'.*?[^/]',
42 'group_name': r'.*?[^/]',
43 'repo_group_name': r'.*?[^/]',
43 'repo_group_name': r'.*?[^/]',
44 # repo names can have a slash in them, but they must not end with a slash
44 # repo names can have a slash in them, but they must not end with a slash
45 'repo_name': r'.*?[^/]',
45 'repo_name': r'.*?[^/]',
46 # file path eats up everything at the end
46 # file path eats up everything at the end
47 'f_path': r'.*',
47 'f_path': r'.*',
48 # reference types
48 # reference types
49 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
49 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
50 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
50 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
51 }
51 }
52
52
53
53
54 def add_route_requirements(route_path, requirements):
54 def add_route_requirements(route_path, requirements):
55 """
55 """
56 Adds regex requirements to pyramid routes using a mapping dict
56 Adds regex requirements to pyramid routes using a mapping dict
57
57
58 >>> add_route_requirements('/{action}/{id}', {'id': r'\d+'})
58 >>> add_route_requirements('/{action}/{id}', {'id': r'\d+'})
59 '/{action}/{id:\d+}'
59 '/{action}/{id:\d+}'
60
60
61 """
61 """
62 for key, regex in requirements.items():
62 for key, regex in requirements.items():
63 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
63 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
64 return route_path
64 return route_path
65
65
66
66
67 class JSRoutesMapper(Mapper):
67 class JSRoutesMapper(Mapper):
68 """
68 """
69 Wrapper for routes.Mapper to make pyroutes compatible url definitions
69 Wrapper for routes.Mapper to make pyroutes compatible url definitions
70 """
70 """
71 _named_route_regex = re.compile(r'^[a-z-_0-9A-Z]+$')
71 _named_route_regex = re.compile(r'^[a-z-_0-9A-Z]+$')
72 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
72 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
73 def __init__(self, *args, **kw):
73 def __init__(self, *args, **kw):
74 super(JSRoutesMapper, self).__init__(*args, **kw)
74 super(JSRoutesMapper, self).__init__(*args, **kw)
75 self._jsroutes = []
75 self._jsroutes = []
76
76
77 def connect(self, *args, **kw):
77 def connect(self, *args, **kw):
78 """
78 """
79 Wrapper for connect to take an extra argument jsroute=True
79 Wrapper for connect to take an extra argument jsroute=True
80
80
81 :param jsroute: boolean, if True will add the route to the pyroutes list
81 :param jsroute: boolean, if True will add the route to the pyroutes list
82 """
82 """
83 if kw.pop('jsroute', False):
83 if kw.pop('jsroute', False):
84 if not self._named_route_regex.match(args[0]):
84 if not self._named_route_regex.match(args[0]):
85 raise Exception('only named routes can be added to pyroutes')
85 raise Exception('only named routes can be added to pyroutes')
86 self._jsroutes.append(args[0])
86 self._jsroutes.append(args[0])
87
87
88 super(JSRoutesMapper, self).connect(*args, **kw)
88 super(JSRoutesMapper, self).connect(*args, **kw)
89
89
90 def _extract_route_information(self, route):
90 def _extract_route_information(self, route):
91 """
91 """
92 Convert a route into tuple(name, path, args), eg:
92 Convert a route into tuple(name, path, args), eg:
93 ('show_user', '/profile/%(username)s', ['username'])
93 ('show_user', '/profile/%(username)s', ['username'])
94 """
94 """
95 routepath = route.routepath
95 routepath = route.routepath
96 def replace(matchobj):
96 def replace(matchobj):
97 if matchobj.group(1):
97 if matchobj.group(1):
98 return "%%(%s)s" % matchobj.group(1).split(':')[0]
98 return "%%(%s)s" % matchobj.group(1).split(':')[0]
99 else:
99 else:
100 return "%%(%s)s" % matchobj.group(2)
100 return "%%(%s)s" % matchobj.group(2)
101
101
102 routepath = self._argument_prog.sub(replace, routepath)
102 routepath = self._argument_prog.sub(replace, routepath)
103 return (
103 return (
104 route.name,
104 route.name,
105 routepath,
105 routepath,
106 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
106 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
107 for arg in self._argument_prog.findall(route.routepath)]
107 for arg in self._argument_prog.findall(route.routepath)]
108 )
108 )
109
109
110 def jsroutes(self):
110 def jsroutes(self):
111 """
111 """
112 Return a list of pyroutes.js compatible routes
112 Return a list of pyroutes.js compatible routes
113 """
113 """
114 for route_name in self._jsroutes:
114 for route_name in self._jsroutes:
115 yield self._extract_route_information(self._routenames[route_name])
115 yield self._extract_route_information(self._routenames[route_name])
116
116
117
117
118 def make_map(config):
118 def make_map(config):
119 """Create, configure and return the routes Mapper"""
119 """Create, configure and return the routes Mapper"""
120 rmap = JSRoutesMapper(directory=config['pylons.paths']['controllers'],
120 rmap = JSRoutesMapper(directory=config['pylons.paths']['controllers'],
121 always_scan=config['debug'])
121 always_scan=config['debug'])
122 rmap.minimization = False
122 rmap.minimization = False
123 rmap.explicit = False
123 rmap.explicit = False
124
124
125 from rhodecode.lib.utils2 import str2bool
125 from rhodecode.lib.utils2 import str2bool
126 from rhodecode.model import repo, repo_group
126 from rhodecode.model import repo, repo_group
127
127
128 def check_repo(environ, match_dict):
128 def check_repo(environ, match_dict):
129 """
129 """
130 check for valid repository for proper 404 handling
130 check for valid repository for proper 404 handling
131
131
132 :param environ:
132 :param environ:
133 :param match_dict:
133 :param match_dict:
134 """
134 """
135 repo_name = match_dict.get('repo_name')
135 repo_name = match_dict.get('repo_name')
136
136
137 if match_dict.get('f_path'):
137 if match_dict.get('f_path'):
138 # fix for multiple initial slashes that causes errors
138 # fix for multiple initial slashes that causes errors
139 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
139 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
140 repo_model = repo.RepoModel()
140 repo_model = repo.RepoModel()
141 by_name_match = repo_model.get_by_repo_name(repo_name)
141 by_name_match = repo_model.get_by_repo_name(repo_name)
142 # if we match quickly from database, short circuit the operation,
142 # if we match quickly from database, short circuit the operation,
143 # and validate repo based on the type.
143 # and validate repo based on the type.
144 if by_name_match:
144 if by_name_match:
145 return True
145 return True
146
146
147 by_id_match = repo_model.get_repo_by_id(repo_name)
147 by_id_match = repo_model.get_repo_by_id(repo_name)
148 if by_id_match:
148 if by_id_match:
149 repo_name = by_id_match.repo_name
149 repo_name = by_id_match.repo_name
150 match_dict['repo_name'] = repo_name
150 match_dict['repo_name'] = repo_name
151 return True
151 return True
152
152
153 return False
153 return False
154
154
155 def check_group(environ, match_dict):
155 def check_group(environ, match_dict):
156 """
156 """
157 check for valid repository group path for proper 404 handling
157 check for valid repository group path for proper 404 handling
158
158
159 :param environ:
159 :param environ:
160 :param match_dict:
160 :param match_dict:
161 """
161 """
162 repo_group_name = match_dict.get('group_name')
162 repo_group_name = match_dict.get('group_name')
163 repo_group_model = repo_group.RepoGroupModel()
163 repo_group_model = repo_group.RepoGroupModel()
164 by_name_match = repo_group_model.get_by_group_name(repo_group_name)
164 by_name_match = repo_group_model.get_by_group_name(repo_group_name)
165 if by_name_match:
165 if by_name_match:
166 return True
166 return True
167
167
168 return False
168 return False
169
169
170 def check_user_group(environ, match_dict):
170 def check_user_group(environ, match_dict):
171 """
171 """
172 check for valid user group for proper 404 handling
172 check for valid user group for proper 404 handling
173
173
174 :param environ:
174 :param environ:
175 :param match_dict:
175 :param match_dict:
176 """
176 """
177 return True
177 return True
178
178
179 def check_int(environ, match_dict):
179 def check_int(environ, match_dict):
180 return match_dict.get('id').isdigit()
180 return match_dict.get('id').isdigit()
181
181
182
182
183 #==========================================================================
183 #==========================================================================
184 # CUSTOM ROUTES HERE
184 # CUSTOM ROUTES HERE
185 #==========================================================================
185 #==========================================================================
186
186
187 # MAIN PAGE
187 # MAIN PAGE
188 rmap.connect('home', '/', controller='home', action='index', jsroute=True)
188 rmap.connect('home', '/', controller='home', action='index', jsroute=True)
189
189
190 # ping and pylons error test
190 # ping and pylons error test
191 rmap.connect('ping', '%s/ping' % (ADMIN_PREFIX,), controller='home', action='ping')
191 rmap.connect('ping', '%s/ping' % (ADMIN_PREFIX,), controller='home', action='ping')
192 rmap.connect('error_test', '%s/error_test' % (ADMIN_PREFIX,), controller='home', action='error_test')
192 rmap.connect('error_test', '%s/error_test' % (ADMIN_PREFIX,), controller='home', action='error_test')
193
193
194 # ADMIN REPOSITORY ROUTES
194 # ADMIN REPOSITORY ROUTES
195 with rmap.submapper(path_prefix=ADMIN_PREFIX,
195 with rmap.submapper(path_prefix=ADMIN_PREFIX,
196 controller='admin/repos') as m:
196 controller='admin/repos') as m:
197 m.connect('repos', '/repos',
197 m.connect('repos', '/repos',
198 action='create', conditions={'method': ['POST']})
198 action='create', conditions={'method': ['POST']})
199 m.connect('repos', '/repos',
199 m.connect('repos', '/repos',
200 action='index', conditions={'method': ['GET']})
200 action='index', conditions={'method': ['GET']})
201 m.connect('new_repo', '/create_repository', jsroute=True,
201 m.connect('new_repo', '/create_repository', jsroute=True,
202 action='create_repository', conditions={'method': ['GET']})
202 action='create_repository', conditions={'method': ['GET']})
203 m.connect('/repos/{repo_name}',
204 action='update', conditions={'method': ['PUT'],
205 'function': check_repo},
206 requirements=URL_NAME_REQUIREMENTS)
207 m.connect('delete_repo', '/repos/{repo_name}',
203 m.connect('delete_repo', '/repos/{repo_name}',
208 action='delete', conditions={'method': ['DELETE']},
204 action='delete', conditions={'method': ['DELETE']},
209 requirements=URL_NAME_REQUIREMENTS)
205 requirements=URL_NAME_REQUIREMENTS)
210 m.connect('repo', '/repos/{repo_name}',
206 m.connect('repo', '/repos/{repo_name}',
211 action='show', conditions={'method': ['GET'],
207 action='show', conditions={'method': ['GET'],
212 'function': check_repo},
208 'function': check_repo},
213 requirements=URL_NAME_REQUIREMENTS)
209 requirements=URL_NAME_REQUIREMENTS)
214
210
215 # ADMIN REPOSITORY GROUPS ROUTES
211 # ADMIN REPOSITORY GROUPS ROUTES
216 with rmap.submapper(path_prefix=ADMIN_PREFIX,
212 with rmap.submapper(path_prefix=ADMIN_PREFIX,
217 controller='admin/repo_groups') as m:
213 controller='admin/repo_groups') as m:
218 m.connect('repo_groups', '/repo_groups',
214 m.connect('repo_groups', '/repo_groups',
219 action='create', conditions={'method': ['POST']})
215 action='create', conditions={'method': ['POST']})
220 m.connect('repo_groups', '/repo_groups',
216 m.connect('repo_groups', '/repo_groups',
221 action='index', conditions={'method': ['GET']})
217 action='index', conditions={'method': ['GET']})
222 m.connect('new_repo_group', '/repo_groups/new',
218 m.connect('new_repo_group', '/repo_groups/new',
223 action='new', conditions={'method': ['GET']})
219 action='new', conditions={'method': ['GET']})
224 m.connect('update_repo_group', '/repo_groups/{group_name}',
220 m.connect('update_repo_group', '/repo_groups/{group_name}',
225 action='update', conditions={'method': ['PUT'],
221 action='update', conditions={'method': ['PUT'],
226 'function': check_group},
222 'function': check_group},
227 requirements=URL_NAME_REQUIREMENTS)
223 requirements=URL_NAME_REQUIREMENTS)
228
224
229 # EXTRAS REPO GROUP ROUTES
225 # EXTRAS REPO GROUP ROUTES
230 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
226 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
231 action='edit',
227 action='edit',
232 conditions={'method': ['GET'], 'function': check_group},
228 conditions={'method': ['GET'], 'function': check_group},
233 requirements=URL_NAME_REQUIREMENTS)
229 requirements=URL_NAME_REQUIREMENTS)
234 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
230 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
235 action='edit',
231 action='edit',
236 conditions={'method': ['PUT'], 'function': check_group},
232 conditions={'method': ['PUT'], 'function': check_group},
237 requirements=URL_NAME_REQUIREMENTS)
233 requirements=URL_NAME_REQUIREMENTS)
238
234
239 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
235 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
240 action='edit_repo_group_advanced',
236 action='edit_repo_group_advanced',
241 conditions={'method': ['GET'], 'function': check_group},
237 conditions={'method': ['GET'], 'function': check_group},
242 requirements=URL_NAME_REQUIREMENTS)
238 requirements=URL_NAME_REQUIREMENTS)
243 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
239 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
244 action='edit_repo_group_advanced',
240 action='edit_repo_group_advanced',
245 conditions={'method': ['PUT'], 'function': check_group},
241 conditions={'method': ['PUT'], 'function': check_group},
246 requirements=URL_NAME_REQUIREMENTS)
242 requirements=URL_NAME_REQUIREMENTS)
247
243
248 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
244 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
249 action='edit_repo_group_perms',
245 action='edit_repo_group_perms',
250 conditions={'method': ['GET'], 'function': check_group},
246 conditions={'method': ['GET'], 'function': check_group},
251 requirements=URL_NAME_REQUIREMENTS)
247 requirements=URL_NAME_REQUIREMENTS)
252 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
248 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
253 action='update_perms',
249 action='update_perms',
254 conditions={'method': ['PUT'], 'function': check_group},
250 conditions={'method': ['PUT'], 'function': check_group},
255 requirements=URL_NAME_REQUIREMENTS)
251 requirements=URL_NAME_REQUIREMENTS)
256
252
257 m.connect('delete_repo_group', '/repo_groups/{group_name}',
253 m.connect('delete_repo_group', '/repo_groups/{group_name}',
258 action='delete', conditions={'method': ['DELETE'],
254 action='delete', conditions={'method': ['DELETE'],
259 'function': check_group},
255 'function': check_group},
260 requirements=URL_NAME_REQUIREMENTS)
256 requirements=URL_NAME_REQUIREMENTS)
261
257
262 # ADMIN USER ROUTES
258 # ADMIN USER ROUTES
263 with rmap.submapper(path_prefix=ADMIN_PREFIX,
259 with rmap.submapper(path_prefix=ADMIN_PREFIX,
264 controller='admin/users') as m:
260 controller='admin/users') as m:
265 m.connect('users', '/users',
261 m.connect('users', '/users',
266 action='create', conditions={'method': ['POST']})
262 action='create', conditions={'method': ['POST']})
267 m.connect('new_user', '/users/new',
263 m.connect('new_user', '/users/new',
268 action='new', conditions={'method': ['GET']})
264 action='new', conditions={'method': ['GET']})
269 m.connect('update_user', '/users/{user_id}',
265 m.connect('update_user', '/users/{user_id}',
270 action='update', conditions={'method': ['PUT']})
266 action='update', conditions={'method': ['PUT']})
271 m.connect('delete_user', '/users/{user_id}',
267 m.connect('delete_user', '/users/{user_id}',
272 action='delete', conditions={'method': ['DELETE']})
268 action='delete', conditions={'method': ['DELETE']})
273 m.connect('edit_user', '/users/{user_id}/edit',
269 m.connect('edit_user', '/users/{user_id}/edit',
274 action='edit', conditions={'method': ['GET']}, jsroute=True)
270 action='edit', conditions={'method': ['GET']}, jsroute=True)
275 m.connect('user', '/users/{user_id}',
271 m.connect('user', '/users/{user_id}',
276 action='show', conditions={'method': ['GET']})
272 action='show', conditions={'method': ['GET']})
277 m.connect('force_password_reset_user', '/users/{user_id}/password_reset',
273 m.connect('force_password_reset_user', '/users/{user_id}/password_reset',
278 action='reset_password', conditions={'method': ['POST']})
274 action='reset_password', conditions={'method': ['POST']})
279 m.connect('create_personal_repo_group', '/users/{user_id}/create_repo_group',
275 m.connect('create_personal_repo_group', '/users/{user_id}/create_repo_group',
280 action='create_personal_repo_group', conditions={'method': ['POST']})
276 action='create_personal_repo_group', conditions={'method': ['POST']})
281
277
282 # EXTRAS USER ROUTES
278 # EXTRAS USER ROUTES
283 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
279 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
284 action='edit_advanced', conditions={'method': ['GET']})
280 action='edit_advanced', conditions={'method': ['GET']})
285 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
281 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
286 action='update_advanced', conditions={'method': ['PUT']})
282 action='update_advanced', conditions={'method': ['PUT']})
287
283
288 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
284 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
289 action='edit_global_perms', conditions={'method': ['GET']})
285 action='edit_global_perms', conditions={'method': ['GET']})
290 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
286 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
291 action='update_global_perms', conditions={'method': ['PUT']})
287 action='update_global_perms', conditions={'method': ['PUT']})
292
288
293 m.connect('edit_user_perms_summary', '/users/{user_id}/edit/permissions_summary',
289 m.connect('edit_user_perms_summary', '/users/{user_id}/edit/permissions_summary',
294 action='edit_perms_summary', conditions={'method': ['GET']})
290 action='edit_perms_summary', conditions={'method': ['GET']})
295
291
296 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
292 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
297 action='edit_emails', conditions={'method': ['GET']})
293 action='edit_emails', conditions={'method': ['GET']})
298 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
294 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
299 action='add_email', conditions={'method': ['PUT']})
295 action='add_email', conditions={'method': ['PUT']})
300 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
296 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
301 action='delete_email', conditions={'method': ['DELETE']})
297 action='delete_email', conditions={'method': ['DELETE']})
302
298
303 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
299 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
304 action='edit_ips', conditions={'method': ['GET']})
300 action='edit_ips', conditions={'method': ['GET']})
305 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
301 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
306 action='add_ip', conditions={'method': ['PUT']})
302 action='add_ip', conditions={'method': ['PUT']})
307 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
303 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
308 action='delete_ip', conditions={'method': ['DELETE']})
304 action='delete_ip', conditions={'method': ['DELETE']})
309
305
310 # ADMIN USER GROUPS REST ROUTES
306 # ADMIN USER GROUPS REST ROUTES
311 with rmap.submapper(path_prefix=ADMIN_PREFIX,
307 with rmap.submapper(path_prefix=ADMIN_PREFIX,
312 controller='admin/user_groups') as m:
308 controller='admin/user_groups') as m:
313 m.connect('users_groups', '/user_groups',
309 m.connect('users_groups', '/user_groups',
314 action='create', conditions={'method': ['POST']})
310 action='create', conditions={'method': ['POST']})
315 m.connect('users_groups', '/user_groups',
311 m.connect('users_groups', '/user_groups',
316 action='index', conditions={'method': ['GET']})
312 action='index', conditions={'method': ['GET']})
317 m.connect('new_users_group', '/user_groups/new',
313 m.connect('new_users_group', '/user_groups/new',
318 action='new', conditions={'method': ['GET']})
314 action='new', conditions={'method': ['GET']})
319 m.connect('update_users_group', '/user_groups/{user_group_id}',
315 m.connect('update_users_group', '/user_groups/{user_group_id}',
320 action='update', conditions={'method': ['PUT']})
316 action='update', conditions={'method': ['PUT']})
321 m.connect('delete_users_group', '/user_groups/{user_group_id}',
317 m.connect('delete_users_group', '/user_groups/{user_group_id}',
322 action='delete', conditions={'method': ['DELETE']})
318 action='delete', conditions={'method': ['DELETE']})
323 m.connect('edit_users_group', '/user_groups/{user_group_id}/edit',
319 m.connect('edit_users_group', '/user_groups/{user_group_id}/edit',
324 action='edit', conditions={'method': ['GET']},
320 action='edit', conditions={'method': ['GET']},
325 function=check_user_group)
321 function=check_user_group)
326
322
327 # EXTRAS USER GROUP ROUTES
323 # EXTRAS USER GROUP ROUTES
328 m.connect('edit_user_group_global_perms',
324 m.connect('edit_user_group_global_perms',
329 '/user_groups/{user_group_id}/edit/global_permissions',
325 '/user_groups/{user_group_id}/edit/global_permissions',
330 action='edit_global_perms', conditions={'method': ['GET']})
326 action='edit_global_perms', conditions={'method': ['GET']})
331 m.connect('edit_user_group_global_perms',
327 m.connect('edit_user_group_global_perms',
332 '/user_groups/{user_group_id}/edit/global_permissions',
328 '/user_groups/{user_group_id}/edit/global_permissions',
333 action='update_global_perms', conditions={'method': ['PUT']})
329 action='update_global_perms', conditions={'method': ['PUT']})
334 m.connect('edit_user_group_perms_summary',
330 m.connect('edit_user_group_perms_summary',
335 '/user_groups/{user_group_id}/edit/permissions_summary',
331 '/user_groups/{user_group_id}/edit/permissions_summary',
336 action='edit_perms_summary', conditions={'method': ['GET']})
332 action='edit_perms_summary', conditions={'method': ['GET']})
337
333
338 m.connect('edit_user_group_perms',
334 m.connect('edit_user_group_perms',
339 '/user_groups/{user_group_id}/edit/permissions',
335 '/user_groups/{user_group_id}/edit/permissions',
340 action='edit_perms', conditions={'method': ['GET']})
336 action='edit_perms', conditions={'method': ['GET']})
341 m.connect('edit_user_group_perms',
337 m.connect('edit_user_group_perms',
342 '/user_groups/{user_group_id}/edit/permissions',
338 '/user_groups/{user_group_id}/edit/permissions',
343 action='update_perms', conditions={'method': ['PUT']})
339 action='update_perms', conditions={'method': ['PUT']})
344
340
345 m.connect('edit_user_group_advanced',
341 m.connect('edit_user_group_advanced',
346 '/user_groups/{user_group_id}/edit/advanced',
342 '/user_groups/{user_group_id}/edit/advanced',
347 action='edit_advanced', conditions={'method': ['GET']})
343 action='edit_advanced', conditions={'method': ['GET']})
348
344
349 m.connect('edit_user_group_advanced_sync',
345 m.connect('edit_user_group_advanced_sync',
350 '/user_groups/{user_group_id}/edit/advanced/sync',
346 '/user_groups/{user_group_id}/edit/advanced/sync',
351 action='edit_advanced_set_synchronization', conditions={'method': ['POST']})
347 action='edit_advanced_set_synchronization', conditions={'method': ['POST']})
352
348
353 m.connect('edit_user_group_members',
349 m.connect('edit_user_group_members',
354 '/user_groups/{user_group_id}/edit/members', jsroute=True,
350 '/user_groups/{user_group_id}/edit/members', jsroute=True,
355 action='user_group_members', conditions={'method': ['GET']})
351 action='user_group_members', conditions={'method': ['GET']})
356
352
357 # ADMIN PERMISSIONS ROUTES
353 # ADMIN PERMISSIONS ROUTES
358 with rmap.submapper(path_prefix=ADMIN_PREFIX,
354 with rmap.submapper(path_prefix=ADMIN_PREFIX,
359 controller='admin/permissions') as m:
355 controller='admin/permissions') as m:
360 m.connect('admin_permissions_application', '/permissions/application',
356 m.connect('admin_permissions_application', '/permissions/application',
361 action='permission_application_update', conditions={'method': ['POST']})
357 action='permission_application_update', conditions={'method': ['POST']})
362 m.connect('admin_permissions_application', '/permissions/application',
358 m.connect('admin_permissions_application', '/permissions/application',
363 action='permission_application', conditions={'method': ['GET']})
359 action='permission_application', conditions={'method': ['GET']})
364
360
365 m.connect('admin_permissions_global', '/permissions/global',
361 m.connect('admin_permissions_global', '/permissions/global',
366 action='permission_global_update', conditions={'method': ['POST']})
362 action='permission_global_update', conditions={'method': ['POST']})
367 m.connect('admin_permissions_global', '/permissions/global',
363 m.connect('admin_permissions_global', '/permissions/global',
368 action='permission_global', conditions={'method': ['GET']})
364 action='permission_global', conditions={'method': ['GET']})
369
365
370 m.connect('admin_permissions_object', '/permissions/object',
366 m.connect('admin_permissions_object', '/permissions/object',
371 action='permission_objects_update', conditions={'method': ['POST']})
367 action='permission_objects_update', conditions={'method': ['POST']})
372 m.connect('admin_permissions_object', '/permissions/object',
368 m.connect('admin_permissions_object', '/permissions/object',
373 action='permission_objects', conditions={'method': ['GET']})
369 action='permission_objects', conditions={'method': ['GET']})
374
370
375 m.connect('admin_permissions_ips', '/permissions/ips',
371 m.connect('admin_permissions_ips', '/permissions/ips',
376 action='permission_ips', conditions={'method': ['POST']})
372 action='permission_ips', conditions={'method': ['POST']})
377 m.connect('admin_permissions_ips', '/permissions/ips',
373 m.connect('admin_permissions_ips', '/permissions/ips',
378 action='permission_ips', conditions={'method': ['GET']})
374 action='permission_ips', conditions={'method': ['GET']})
379
375
380 m.connect('admin_permissions_overview', '/permissions/overview',
376 m.connect('admin_permissions_overview', '/permissions/overview',
381 action='permission_perms', conditions={'method': ['GET']})
377 action='permission_perms', conditions={'method': ['GET']})
382
378
383 # ADMIN DEFAULTS REST ROUTES
379 # ADMIN DEFAULTS REST ROUTES
384 with rmap.submapper(path_prefix=ADMIN_PREFIX,
380 with rmap.submapper(path_prefix=ADMIN_PREFIX,
385 controller='admin/defaults') as m:
381 controller='admin/defaults') as m:
386 m.connect('admin_defaults_repositories', '/defaults/repositories',
382 m.connect('admin_defaults_repositories', '/defaults/repositories',
387 action='update_repository_defaults', conditions={'method': ['POST']})
383 action='update_repository_defaults', conditions={'method': ['POST']})
388 m.connect('admin_defaults_repositories', '/defaults/repositories',
384 m.connect('admin_defaults_repositories', '/defaults/repositories',
389 action='index', conditions={'method': ['GET']})
385 action='index', conditions={'method': ['GET']})
390
386
391 # ADMIN DEBUG STYLE ROUTES
387 # ADMIN DEBUG STYLE ROUTES
392 if str2bool(config.get('debug_style')):
388 if str2bool(config.get('debug_style')):
393 with rmap.submapper(path_prefix=ADMIN_PREFIX + '/debug_style',
389 with rmap.submapper(path_prefix=ADMIN_PREFIX + '/debug_style',
394 controller='debug_style') as m:
390 controller='debug_style') as m:
395 m.connect('debug_style_home', '',
391 m.connect('debug_style_home', '',
396 action='index', conditions={'method': ['GET']})
392 action='index', conditions={'method': ['GET']})
397 m.connect('debug_style_template', '/t/{t_path}',
393 m.connect('debug_style_template', '/t/{t_path}',
398 action='template', conditions={'method': ['GET']})
394 action='template', conditions={'method': ['GET']})
399
395
400 # ADMIN SETTINGS ROUTES
396 # ADMIN SETTINGS ROUTES
401 with rmap.submapper(path_prefix=ADMIN_PREFIX,
397 with rmap.submapper(path_prefix=ADMIN_PREFIX,
402 controller='admin/settings') as m:
398 controller='admin/settings') as m:
403
399
404 # default
400 # default
405 m.connect('admin_settings', '/settings',
401 m.connect('admin_settings', '/settings',
406 action='settings_global_update',
402 action='settings_global_update',
407 conditions={'method': ['POST']})
403 conditions={'method': ['POST']})
408 m.connect('admin_settings', '/settings',
404 m.connect('admin_settings', '/settings',
409 action='settings_global', conditions={'method': ['GET']})
405 action='settings_global', conditions={'method': ['GET']})
410
406
411 m.connect('admin_settings_vcs', '/settings/vcs',
407 m.connect('admin_settings_vcs', '/settings/vcs',
412 action='settings_vcs_update',
408 action='settings_vcs_update',
413 conditions={'method': ['POST']})
409 conditions={'method': ['POST']})
414 m.connect('admin_settings_vcs', '/settings/vcs',
410 m.connect('admin_settings_vcs', '/settings/vcs',
415 action='settings_vcs',
411 action='settings_vcs',
416 conditions={'method': ['GET']})
412 conditions={'method': ['GET']})
417 m.connect('admin_settings_vcs', '/settings/vcs',
413 m.connect('admin_settings_vcs', '/settings/vcs',
418 action='delete_svn_pattern',
414 action='delete_svn_pattern',
419 conditions={'method': ['DELETE']})
415 conditions={'method': ['DELETE']})
420
416
421 m.connect('admin_settings_mapping', '/settings/mapping',
417 m.connect('admin_settings_mapping', '/settings/mapping',
422 action='settings_mapping_update',
418 action='settings_mapping_update',
423 conditions={'method': ['POST']})
419 conditions={'method': ['POST']})
424 m.connect('admin_settings_mapping', '/settings/mapping',
420 m.connect('admin_settings_mapping', '/settings/mapping',
425 action='settings_mapping', conditions={'method': ['GET']})
421 action='settings_mapping', conditions={'method': ['GET']})
426
422
427 m.connect('admin_settings_global', '/settings/global',
423 m.connect('admin_settings_global', '/settings/global',
428 action='settings_global_update',
424 action='settings_global_update',
429 conditions={'method': ['POST']})
425 conditions={'method': ['POST']})
430 m.connect('admin_settings_global', '/settings/global',
426 m.connect('admin_settings_global', '/settings/global',
431 action='settings_global', conditions={'method': ['GET']})
427 action='settings_global', conditions={'method': ['GET']})
432
428
433 m.connect('admin_settings_visual', '/settings/visual',
429 m.connect('admin_settings_visual', '/settings/visual',
434 action='settings_visual_update',
430 action='settings_visual_update',
435 conditions={'method': ['POST']})
431 conditions={'method': ['POST']})
436 m.connect('admin_settings_visual', '/settings/visual',
432 m.connect('admin_settings_visual', '/settings/visual',
437 action='settings_visual', conditions={'method': ['GET']})
433 action='settings_visual', conditions={'method': ['GET']})
438
434
439 m.connect('admin_settings_issuetracker',
435 m.connect('admin_settings_issuetracker',
440 '/settings/issue-tracker', action='settings_issuetracker',
436 '/settings/issue-tracker', action='settings_issuetracker',
441 conditions={'method': ['GET']})
437 conditions={'method': ['GET']})
442 m.connect('admin_settings_issuetracker_save',
438 m.connect('admin_settings_issuetracker_save',
443 '/settings/issue-tracker/save',
439 '/settings/issue-tracker/save',
444 action='settings_issuetracker_save',
440 action='settings_issuetracker_save',
445 conditions={'method': ['POST']})
441 conditions={'method': ['POST']})
446 m.connect('admin_issuetracker_test', '/settings/issue-tracker/test',
442 m.connect('admin_issuetracker_test', '/settings/issue-tracker/test',
447 action='settings_issuetracker_test',
443 action='settings_issuetracker_test',
448 conditions={'method': ['POST']})
444 conditions={'method': ['POST']})
449 m.connect('admin_issuetracker_delete',
445 m.connect('admin_issuetracker_delete',
450 '/settings/issue-tracker/delete',
446 '/settings/issue-tracker/delete',
451 action='settings_issuetracker_delete',
447 action='settings_issuetracker_delete',
452 conditions={'method': ['DELETE']})
448 conditions={'method': ['DELETE']})
453
449
454 m.connect('admin_settings_email', '/settings/email',
450 m.connect('admin_settings_email', '/settings/email',
455 action='settings_email_update',
451 action='settings_email_update',
456 conditions={'method': ['POST']})
452 conditions={'method': ['POST']})
457 m.connect('admin_settings_email', '/settings/email',
453 m.connect('admin_settings_email', '/settings/email',
458 action='settings_email', conditions={'method': ['GET']})
454 action='settings_email', conditions={'method': ['GET']})
459
455
460 m.connect('admin_settings_hooks', '/settings/hooks',
456 m.connect('admin_settings_hooks', '/settings/hooks',
461 action='settings_hooks_update',
457 action='settings_hooks_update',
462 conditions={'method': ['POST', 'DELETE']})
458 conditions={'method': ['POST', 'DELETE']})
463 m.connect('admin_settings_hooks', '/settings/hooks',
459 m.connect('admin_settings_hooks', '/settings/hooks',
464 action='settings_hooks', conditions={'method': ['GET']})
460 action='settings_hooks', conditions={'method': ['GET']})
465
461
466 m.connect('admin_settings_search', '/settings/search',
462 m.connect('admin_settings_search', '/settings/search',
467 action='settings_search', conditions={'method': ['GET']})
463 action='settings_search', conditions={'method': ['GET']})
468
464
469 m.connect('admin_settings_supervisor', '/settings/supervisor',
465 m.connect('admin_settings_supervisor', '/settings/supervisor',
470 action='settings_supervisor', conditions={'method': ['GET']})
466 action='settings_supervisor', conditions={'method': ['GET']})
471 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
467 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
472 action='settings_supervisor_log', conditions={'method': ['GET']})
468 action='settings_supervisor_log', conditions={'method': ['GET']})
473
469
474 m.connect('admin_settings_labs', '/settings/labs',
470 m.connect('admin_settings_labs', '/settings/labs',
475 action='settings_labs_update',
471 action='settings_labs_update',
476 conditions={'method': ['POST']})
472 conditions={'method': ['POST']})
477 m.connect('admin_settings_labs', '/settings/labs',
473 m.connect('admin_settings_labs', '/settings/labs',
478 action='settings_labs', conditions={'method': ['GET']})
474 action='settings_labs', conditions={'method': ['GET']})
479
475
480 # ADMIN MY ACCOUNT
476 # ADMIN MY ACCOUNT
481 with rmap.submapper(path_prefix=ADMIN_PREFIX,
477 with rmap.submapper(path_prefix=ADMIN_PREFIX,
482 controller='admin/my_account') as m:
478 controller='admin/my_account') as m:
483
479
484 m.connect('my_account_edit', '/my_account/edit',
480 m.connect('my_account_edit', '/my_account/edit',
485 action='my_account_edit', conditions={'method': ['GET']})
481 action='my_account_edit', conditions={'method': ['GET']})
486 m.connect('my_account', '/my_account/update',
482 m.connect('my_account', '/my_account/update',
487 action='my_account_update', conditions={'method': ['POST']})
483 action='my_account_update', conditions={'method': ['POST']})
488
484
489 # NOTE(marcink): this needs to be kept for password force flag to be
485 # NOTE(marcink): this needs to be kept for password force flag to be
490 # handler, remove after migration to pyramid
486 # handler, remove after migration to pyramid
491 m.connect('my_account_password', '/my_account/password',
487 m.connect('my_account_password', '/my_account/password',
492 action='my_account_password', conditions={'method': ['GET']})
488 action='my_account_password', conditions={'method': ['GET']})
493
489
494 m.connect('my_account_repos', '/my_account/repos',
490 m.connect('my_account_repos', '/my_account/repos',
495 action='my_account_repos', conditions={'method': ['GET']})
491 action='my_account_repos', conditions={'method': ['GET']})
496
492
497 m.connect('my_account_watched', '/my_account/watched',
493 m.connect('my_account_watched', '/my_account/watched',
498 action='my_account_watched', conditions={'method': ['GET']})
494 action='my_account_watched', conditions={'method': ['GET']})
499
495
500 m.connect('my_account_pullrequests', '/my_account/pull_requests',
496 m.connect('my_account_pullrequests', '/my_account/pull_requests',
501 action='my_account_pullrequests', conditions={'method': ['GET']})
497 action='my_account_pullrequests', conditions={'method': ['GET']})
502
498
503 m.connect('my_account_perms', '/my_account/perms',
499 m.connect('my_account_perms', '/my_account/perms',
504 action='my_account_perms', conditions={'method': ['GET']})
500 action='my_account_perms', conditions={'method': ['GET']})
505
501
506 m.connect('my_account_emails', '/my_account/emails',
502 m.connect('my_account_emails', '/my_account/emails',
507 action='my_account_emails', conditions={'method': ['GET']})
503 action='my_account_emails', conditions={'method': ['GET']})
508 m.connect('my_account_emails', '/my_account/emails',
504 m.connect('my_account_emails', '/my_account/emails',
509 action='my_account_emails_add', conditions={'method': ['POST']})
505 action='my_account_emails_add', conditions={'method': ['POST']})
510 m.connect('my_account_emails', '/my_account/emails',
506 m.connect('my_account_emails', '/my_account/emails',
511 action='my_account_emails_delete', conditions={'method': ['DELETE']})
507 action='my_account_emails_delete', conditions={'method': ['DELETE']})
512
508
513 m.connect('my_account_notifications', '/my_account/notifications',
509 m.connect('my_account_notifications', '/my_account/notifications',
514 action='my_notifications',
510 action='my_notifications',
515 conditions={'method': ['GET']})
511 conditions={'method': ['GET']})
516 m.connect('my_account_notifications_toggle_visibility',
512 m.connect('my_account_notifications_toggle_visibility',
517 '/my_account/toggle_visibility',
513 '/my_account/toggle_visibility',
518 action='my_notifications_toggle_visibility',
514 action='my_notifications_toggle_visibility',
519 conditions={'method': ['POST']})
515 conditions={'method': ['POST']})
520 m.connect('my_account_notifications_test_channelstream',
516 m.connect('my_account_notifications_test_channelstream',
521 '/my_account/test_channelstream',
517 '/my_account/test_channelstream',
522 action='my_account_notifications_test_channelstream',
518 action='my_account_notifications_test_channelstream',
523 conditions={'method': ['POST']})
519 conditions={'method': ['POST']})
524
520
525 # NOTIFICATION REST ROUTES
521 # NOTIFICATION REST ROUTES
526 with rmap.submapper(path_prefix=ADMIN_PREFIX,
522 with rmap.submapper(path_prefix=ADMIN_PREFIX,
527 controller='admin/notifications') as m:
523 controller='admin/notifications') as m:
528 m.connect('notifications', '/notifications',
524 m.connect('notifications', '/notifications',
529 action='index', conditions={'method': ['GET']})
525 action='index', conditions={'method': ['GET']})
530 m.connect('notifications_mark_all_read', '/notifications/mark_all_read',
526 m.connect('notifications_mark_all_read', '/notifications/mark_all_read',
531 action='mark_all_read', conditions={'method': ['POST']})
527 action='mark_all_read', conditions={'method': ['POST']})
532 m.connect('/notifications/{notification_id}',
528 m.connect('/notifications/{notification_id}',
533 action='update', conditions={'method': ['PUT']})
529 action='update', conditions={'method': ['PUT']})
534 m.connect('/notifications/{notification_id}',
530 m.connect('/notifications/{notification_id}',
535 action='delete', conditions={'method': ['DELETE']})
531 action='delete', conditions={'method': ['DELETE']})
536 m.connect('notification', '/notifications/{notification_id}',
532 m.connect('notification', '/notifications/{notification_id}',
537 action='show', conditions={'method': ['GET']})
533 action='show', conditions={'method': ['GET']})
538
534
539 # ADMIN GIST
535 # ADMIN GIST
540 with rmap.submapper(path_prefix=ADMIN_PREFIX,
536 with rmap.submapper(path_prefix=ADMIN_PREFIX,
541 controller='admin/gists') as m:
537 controller='admin/gists') as m:
542 m.connect('gists', '/gists',
538 m.connect('gists', '/gists',
543 action='create', conditions={'method': ['POST']})
539 action='create', conditions={'method': ['POST']})
544 m.connect('gists', '/gists', jsroute=True,
540 m.connect('gists', '/gists', jsroute=True,
545 action='index', conditions={'method': ['GET']})
541 action='index', conditions={'method': ['GET']})
546 m.connect('new_gist', '/gists/new', jsroute=True,
542 m.connect('new_gist', '/gists/new', jsroute=True,
547 action='new', conditions={'method': ['GET']})
543 action='new', conditions={'method': ['GET']})
548
544
549 m.connect('/gists/{gist_id}',
545 m.connect('/gists/{gist_id}',
550 action='delete', conditions={'method': ['DELETE']})
546 action='delete', conditions={'method': ['DELETE']})
551 m.connect('edit_gist', '/gists/{gist_id}/edit',
547 m.connect('edit_gist', '/gists/{gist_id}/edit',
552 action='edit_form', conditions={'method': ['GET']})
548 action='edit_form', conditions={'method': ['GET']})
553 m.connect('edit_gist', '/gists/{gist_id}/edit',
549 m.connect('edit_gist', '/gists/{gist_id}/edit',
554 action='edit', conditions={'method': ['POST']})
550 action='edit', conditions={'method': ['POST']})
555 m.connect(
551 m.connect(
556 'edit_gist_check_revision', '/gists/{gist_id}/edit/check_revision',
552 'edit_gist_check_revision', '/gists/{gist_id}/edit/check_revision',
557 action='check_revision', conditions={'method': ['GET']})
553 action='check_revision', conditions={'method': ['GET']})
558
554
559 m.connect('gist', '/gists/{gist_id}',
555 m.connect('gist', '/gists/{gist_id}',
560 action='show', conditions={'method': ['GET']})
556 action='show', conditions={'method': ['GET']})
561 m.connect('gist_rev', '/gists/{gist_id}/{revision}',
557 m.connect('gist_rev', '/gists/{gist_id}/{revision}',
562 revision='tip',
558 revision='tip',
563 action='show', conditions={'method': ['GET']})
559 action='show', conditions={'method': ['GET']})
564 m.connect('formatted_gist', '/gists/{gist_id}/{revision}/{format}',
560 m.connect('formatted_gist', '/gists/{gist_id}/{revision}/{format}',
565 revision='tip',
561 revision='tip',
566 action='show', conditions={'method': ['GET']})
562 action='show', conditions={'method': ['GET']})
567 m.connect('formatted_gist_file', '/gists/{gist_id}/{revision}/{format}/{f_path}',
563 m.connect('formatted_gist_file', '/gists/{gist_id}/{revision}/{format}/{f_path}',
568 revision='tip',
564 revision='tip',
569 action='show', conditions={'method': ['GET']},
565 action='show', conditions={'method': ['GET']},
570 requirements=URL_NAME_REQUIREMENTS)
566 requirements=URL_NAME_REQUIREMENTS)
571
567
572 # ADMIN MAIN PAGES
568 # ADMIN MAIN PAGES
573 with rmap.submapper(path_prefix=ADMIN_PREFIX,
569 with rmap.submapper(path_prefix=ADMIN_PREFIX,
574 controller='admin/admin') as m:
570 controller='admin/admin') as m:
575 m.connect('admin_home', '', action='index')
571 m.connect('admin_home', '', action='index')
576 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
572 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
577 action='add_repo')
573 action='add_repo')
578 m.connect(
574 m.connect(
579 'pull_requests_global_0', '/pull_requests/{pull_request_id:[0-9]+}',
575 'pull_requests_global_0', '/pull_requests/{pull_request_id:[0-9]+}',
580 action='pull_requests')
576 action='pull_requests')
581 m.connect(
577 m.connect(
582 'pull_requests_global_1', '/pull-requests/{pull_request_id:[0-9]+}',
578 'pull_requests_global_1', '/pull-requests/{pull_request_id:[0-9]+}',
583 action='pull_requests')
579 action='pull_requests')
584 m.connect(
580 m.connect(
585 'pull_requests_global', '/pull-request/{pull_request_id:[0-9]+}',
581 'pull_requests_global', '/pull-request/{pull_request_id:[0-9]+}',
586 action='pull_requests')
582 action='pull_requests')
587
583
588 # USER JOURNAL
584 # USER JOURNAL
589 rmap.connect('journal', '%s/journal' % (ADMIN_PREFIX,),
585 rmap.connect('journal', '%s/journal' % (ADMIN_PREFIX,),
590 controller='journal', action='index')
586 controller='journal', action='index')
591 rmap.connect('journal_rss', '%s/journal/rss' % (ADMIN_PREFIX,),
587 rmap.connect('journal_rss', '%s/journal/rss' % (ADMIN_PREFIX,),
592 controller='journal', action='journal_rss')
588 controller='journal', action='journal_rss')
593 rmap.connect('journal_atom', '%s/journal/atom' % (ADMIN_PREFIX,),
589 rmap.connect('journal_atom', '%s/journal/atom' % (ADMIN_PREFIX,),
594 controller='journal', action='journal_atom')
590 controller='journal', action='journal_atom')
595
591
596 rmap.connect('public_journal', '%s/public_journal' % (ADMIN_PREFIX,),
592 rmap.connect('public_journal', '%s/public_journal' % (ADMIN_PREFIX,),
597 controller='journal', action='public_journal')
593 controller='journal', action='public_journal')
598
594
599 rmap.connect('public_journal_rss', '%s/public_journal/rss' % (ADMIN_PREFIX,),
595 rmap.connect('public_journal_rss', '%s/public_journal/rss' % (ADMIN_PREFIX,),
600 controller='journal', action='public_journal_rss')
596 controller='journal', action='public_journal_rss')
601
597
602 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % (ADMIN_PREFIX,),
598 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % (ADMIN_PREFIX,),
603 controller='journal', action='public_journal_rss')
599 controller='journal', action='public_journal_rss')
604
600
605 rmap.connect('public_journal_atom',
601 rmap.connect('public_journal_atom',
606 '%s/public_journal/atom' % (ADMIN_PREFIX,), controller='journal',
602 '%s/public_journal/atom' % (ADMIN_PREFIX,), controller='journal',
607 action='public_journal_atom')
603 action='public_journal_atom')
608
604
609 rmap.connect('public_journal_atom_old',
605 rmap.connect('public_journal_atom_old',
610 '%s/public_journal_atom' % (ADMIN_PREFIX,), controller='journal',
606 '%s/public_journal_atom' % (ADMIN_PREFIX,), controller='journal',
611 action='public_journal_atom')
607 action='public_journal_atom')
612
608
613 rmap.connect('toggle_following', '%s/toggle_following' % (ADMIN_PREFIX,),
609 rmap.connect('toggle_following', '%s/toggle_following' % (ADMIN_PREFIX,),
614 controller='journal', action='toggle_following', jsroute=True,
610 controller='journal', action='toggle_following', jsroute=True,
615 conditions={'method': ['POST']})
611 conditions={'method': ['POST']})
616
612
617 # FEEDS
613 # FEEDS
618 rmap.connect('rss_feed_home', '/{repo_name}/feed/rss',
614 rmap.connect('rss_feed_home', '/{repo_name}/feed/rss',
619 controller='feed', action='rss',
615 controller='feed', action='rss',
620 conditions={'function': check_repo},
616 conditions={'function': check_repo},
621 requirements=URL_NAME_REQUIREMENTS)
617 requirements=URL_NAME_REQUIREMENTS)
622
618
623 rmap.connect('atom_feed_home', '/{repo_name}/feed/atom',
619 rmap.connect('atom_feed_home', '/{repo_name}/feed/atom',
624 controller='feed', action='atom',
620 controller='feed', action='atom',
625 conditions={'function': check_repo},
621 conditions={'function': check_repo},
626 requirements=URL_NAME_REQUIREMENTS)
622 requirements=URL_NAME_REQUIREMENTS)
627
623
628 #==========================================================================
624 #==========================================================================
629 # REPOSITORY ROUTES
625 # REPOSITORY ROUTES
630 #==========================================================================
626 #==========================================================================
631
627
632 rmap.connect('repo_creating_home', '/{repo_name}/repo_creating',
628 rmap.connect('repo_creating_home', '/{repo_name}/repo_creating',
633 controller='admin/repos', action='repo_creating',
629 controller='admin/repos', action='repo_creating',
634 requirements=URL_NAME_REQUIREMENTS)
630 requirements=URL_NAME_REQUIREMENTS)
635 rmap.connect('repo_check_home', '/{repo_name}/crepo_check',
631 rmap.connect('repo_check_home', '/{repo_name}/crepo_check',
636 controller='admin/repos', action='repo_check',
632 controller='admin/repos', action='repo_check',
637 requirements=URL_NAME_REQUIREMENTS)
633 requirements=URL_NAME_REQUIREMENTS)
638
634
639 rmap.connect('repo_stats', '/{repo_name}/repo_stats/{commit_id}',
635 rmap.connect('repo_stats', '/{repo_name}/repo_stats/{commit_id}',
640 controller='summary', action='repo_stats',
636 controller='summary', action='repo_stats',
641 conditions={'function': check_repo},
637 conditions={'function': check_repo},
642 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
638 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
643
639
644 rmap.connect('repo_refs_data', '/{repo_name}/refs-data',
640 rmap.connect('repo_refs_data', '/{repo_name}/refs-data',
645 controller='summary', action='repo_refs_data',
641 controller='summary', action='repo_refs_data',
646 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
642 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
647 rmap.connect('repo_refs_changelog_data', '/{repo_name}/refs-data-changelog',
643 rmap.connect('repo_refs_changelog_data', '/{repo_name}/refs-data-changelog',
648 controller='summary', action='repo_refs_changelog_data',
644 controller='summary', action='repo_refs_changelog_data',
649 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
645 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
650 rmap.connect('repo_default_reviewers_data', '/{repo_name}/default-reviewers',
646 rmap.connect('repo_default_reviewers_data', '/{repo_name}/default-reviewers',
651 controller='summary', action='repo_default_reviewers_data',
647 controller='summary', action='repo_default_reviewers_data',
652 jsroute=True, requirements=URL_NAME_REQUIREMENTS)
648 jsroute=True, requirements=URL_NAME_REQUIREMENTS)
653
649
654 rmap.connect('changeset_home', '/{repo_name}/changeset/{revision}',
650 rmap.connect('changeset_home', '/{repo_name}/changeset/{revision}',
655 controller='changeset', revision='tip',
651 controller='changeset', revision='tip',
656 conditions={'function': check_repo},
652 conditions={'function': check_repo},
657 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
653 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
658 rmap.connect('changeset_children', '/{repo_name}/changeset_children/{revision}',
654 rmap.connect('changeset_children', '/{repo_name}/changeset_children/{revision}',
659 controller='changeset', revision='tip', action='changeset_children',
655 controller='changeset', revision='tip', action='changeset_children',
660 conditions={'function': check_repo},
656 conditions={'function': check_repo},
661 requirements=URL_NAME_REQUIREMENTS)
657 requirements=URL_NAME_REQUIREMENTS)
662 rmap.connect('changeset_parents', '/{repo_name}/changeset_parents/{revision}',
658 rmap.connect('changeset_parents', '/{repo_name}/changeset_parents/{revision}',
663 controller='changeset', revision='tip', action='changeset_parents',
659 controller='changeset', revision='tip', action='changeset_parents',
664 conditions={'function': check_repo},
660 conditions={'function': check_repo},
665 requirements=URL_NAME_REQUIREMENTS)
661 requirements=URL_NAME_REQUIREMENTS)
666
662
667 # repo edit options
663 # repo edit options
668 rmap.connect('edit_repo', '/{repo_name}/settings', jsroute=True,
669 controller='admin/repos', action='edit',
670 conditions={'method': ['GET'], 'function': check_repo},
671 requirements=URL_NAME_REQUIREMENTS)
672
673 rmap.connect('edit_repo_perms', '/{repo_name}/settings/permissions',
664 rmap.connect('edit_repo_perms', '/{repo_name}/settings/permissions',
674 jsroute=True,
665 jsroute=True,
675 controller='admin/repos', action='edit_permissions',
666 controller='admin/repos', action='edit_permissions',
676 conditions={'method': ['GET'], 'function': check_repo},
667 conditions={'method': ['GET'], 'function': check_repo},
677 requirements=URL_NAME_REQUIREMENTS)
668 requirements=URL_NAME_REQUIREMENTS)
678 rmap.connect('edit_repo_perms_update', '/{repo_name}/settings/permissions',
669 rmap.connect('edit_repo_perms_update', '/{repo_name}/settings/permissions',
679 controller='admin/repos', action='edit_permissions_update',
670 controller='admin/repos', action='edit_permissions_update',
680 conditions={'method': ['PUT'], 'function': check_repo},
671 conditions={'method': ['PUT'], 'function': check_repo},
681 requirements=URL_NAME_REQUIREMENTS)
672 requirements=URL_NAME_REQUIREMENTS)
682
673
683 rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields',
674 rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields',
684 controller='admin/repos', action='edit_fields',
675 controller='admin/repos', action='edit_fields',
685 conditions={'method': ['GET'], 'function': check_repo},
676 conditions={'method': ['GET'], 'function': check_repo},
686 requirements=URL_NAME_REQUIREMENTS)
677 requirements=URL_NAME_REQUIREMENTS)
687 rmap.connect('create_repo_fields', '/{repo_name}/settings/fields/new',
678 rmap.connect('create_repo_fields', '/{repo_name}/settings/fields/new',
688 controller='admin/repos', action='create_repo_field',
679 controller='admin/repos', action='create_repo_field',
689 conditions={'method': ['PUT'], 'function': check_repo},
680 conditions={'method': ['PUT'], 'function': check_repo},
690 requirements=URL_NAME_REQUIREMENTS)
681 requirements=URL_NAME_REQUIREMENTS)
691 rmap.connect('delete_repo_fields', '/{repo_name}/settings/fields/{field_id}',
682 rmap.connect('delete_repo_fields', '/{repo_name}/settings/fields/{field_id}',
692 controller='admin/repos', action='delete_repo_field',
683 controller='admin/repos', action='delete_repo_field',
693 conditions={'method': ['DELETE'], 'function': check_repo},
684 conditions={'method': ['DELETE'], 'function': check_repo},
694 requirements=URL_NAME_REQUIREMENTS)
685 requirements=URL_NAME_REQUIREMENTS)
695
686
696 rmap.connect('edit_repo_advanced', '/{repo_name}/settings/advanced',
687 rmap.connect('edit_repo_advanced', '/{repo_name}/settings/advanced',
697 controller='admin/repos', action='edit_advanced',
688 controller='admin/repos', action='edit_advanced',
698 conditions={'method': ['GET'], 'function': check_repo},
689 conditions={'method': ['GET'], 'function': check_repo},
699 requirements=URL_NAME_REQUIREMENTS)
690 requirements=URL_NAME_REQUIREMENTS)
700
691
701 rmap.connect('edit_repo_advanced_locking', '/{repo_name}/settings/advanced/locking',
692 rmap.connect('edit_repo_advanced_locking', '/{repo_name}/settings/advanced/locking',
702 controller='admin/repos', action='edit_advanced_locking',
693 controller='admin/repos', action='edit_advanced_locking',
703 conditions={'method': ['PUT'], 'function': check_repo},
694 conditions={'method': ['PUT'], 'function': check_repo},
704 requirements=URL_NAME_REQUIREMENTS)
695 requirements=URL_NAME_REQUIREMENTS)
705 rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle',
696 rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle',
706 controller='admin/repos', action='toggle_locking',
697 controller='admin/repos', action='toggle_locking',
707 conditions={'method': ['GET'], 'function': check_repo},
698 conditions={'method': ['GET'], 'function': check_repo},
708 requirements=URL_NAME_REQUIREMENTS)
699 requirements=URL_NAME_REQUIREMENTS)
709
700
710 rmap.connect('edit_repo_advanced_journal', '/{repo_name}/settings/advanced/journal',
701 rmap.connect('edit_repo_advanced_journal', '/{repo_name}/settings/advanced/journal',
711 controller='admin/repos', action='edit_advanced_journal',
702 controller='admin/repos', action='edit_advanced_journal',
712 conditions={'method': ['PUT'], 'function': check_repo},
703 conditions={'method': ['PUT'], 'function': check_repo},
713 requirements=URL_NAME_REQUIREMENTS)
704 requirements=URL_NAME_REQUIREMENTS)
714
705
715 rmap.connect('edit_repo_advanced_fork', '/{repo_name}/settings/advanced/fork',
706 rmap.connect('edit_repo_advanced_fork', '/{repo_name}/settings/advanced/fork',
716 controller='admin/repos', action='edit_advanced_fork',
707 controller='admin/repos', action='edit_advanced_fork',
717 conditions={'method': ['PUT'], 'function': check_repo},
708 conditions={'method': ['PUT'], 'function': check_repo},
718 requirements=URL_NAME_REQUIREMENTS)
709 requirements=URL_NAME_REQUIREMENTS)
719
710
720 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
711 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
721 controller='admin/repos', action='edit_caches_form',
712 controller='admin/repos', action='edit_caches_form',
722 conditions={'method': ['GET'], 'function': check_repo},
713 conditions={'method': ['GET'], 'function': check_repo},
723 requirements=URL_NAME_REQUIREMENTS)
714 requirements=URL_NAME_REQUIREMENTS)
724 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
715 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
725 controller='admin/repos', action='edit_caches',
716 controller='admin/repos', action='edit_caches',
726 conditions={'method': ['PUT'], 'function': check_repo},
717 conditions={'method': ['PUT'], 'function': check_repo},
727 requirements=URL_NAME_REQUIREMENTS)
718 requirements=URL_NAME_REQUIREMENTS)
728
719
729 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
720 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
730 controller='admin/repos', action='edit_remote_form',
721 controller='admin/repos', action='edit_remote_form',
731 conditions={'method': ['GET'], 'function': check_repo},
722 conditions={'method': ['GET'], 'function': check_repo},
732 requirements=URL_NAME_REQUIREMENTS)
723 requirements=URL_NAME_REQUIREMENTS)
733 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
724 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
734 controller='admin/repos', action='edit_remote',
725 controller='admin/repos', action='edit_remote',
735 conditions={'method': ['PUT'], 'function': check_repo},
726 conditions={'method': ['PUT'], 'function': check_repo},
736 requirements=URL_NAME_REQUIREMENTS)
727 requirements=URL_NAME_REQUIREMENTS)
737
728
738 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
729 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
739 controller='admin/repos', action='edit_statistics_form',
730 controller='admin/repos', action='edit_statistics_form',
740 conditions={'method': ['GET'], 'function': check_repo},
731 conditions={'method': ['GET'], 'function': check_repo},
741 requirements=URL_NAME_REQUIREMENTS)
732 requirements=URL_NAME_REQUIREMENTS)
742 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
733 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
743 controller='admin/repos', action='edit_statistics',
734 controller='admin/repos', action='edit_statistics',
744 conditions={'method': ['PUT'], 'function': check_repo},
735 conditions={'method': ['PUT'], 'function': check_repo},
745 requirements=URL_NAME_REQUIREMENTS)
736 requirements=URL_NAME_REQUIREMENTS)
746 rmap.connect('repo_settings_issuetracker',
737 rmap.connect('repo_settings_issuetracker',
747 '/{repo_name}/settings/issue-tracker',
738 '/{repo_name}/settings/issue-tracker',
748 controller='admin/repos', action='repo_issuetracker',
739 controller='admin/repos', action='repo_issuetracker',
749 conditions={'method': ['GET'], 'function': check_repo},
740 conditions={'method': ['GET'], 'function': check_repo},
750 requirements=URL_NAME_REQUIREMENTS)
741 requirements=URL_NAME_REQUIREMENTS)
751 rmap.connect('repo_issuetracker_test',
742 rmap.connect('repo_issuetracker_test',
752 '/{repo_name}/settings/issue-tracker/test',
743 '/{repo_name}/settings/issue-tracker/test',
753 controller='admin/repos', action='repo_issuetracker_test',
744 controller='admin/repos', action='repo_issuetracker_test',
754 conditions={'method': ['POST'], 'function': check_repo},
745 conditions={'method': ['POST'], 'function': check_repo},
755 requirements=URL_NAME_REQUIREMENTS)
746 requirements=URL_NAME_REQUIREMENTS)
756 rmap.connect('repo_issuetracker_delete',
747 rmap.connect('repo_issuetracker_delete',
757 '/{repo_name}/settings/issue-tracker/delete',
748 '/{repo_name}/settings/issue-tracker/delete',
758 controller='admin/repos', action='repo_issuetracker_delete',
749 controller='admin/repos', action='repo_issuetracker_delete',
759 conditions={'method': ['DELETE'], 'function': check_repo},
750 conditions={'method': ['DELETE'], 'function': check_repo},
760 requirements=URL_NAME_REQUIREMENTS)
751 requirements=URL_NAME_REQUIREMENTS)
761 rmap.connect('repo_issuetracker_save',
752 rmap.connect('repo_issuetracker_save',
762 '/{repo_name}/settings/issue-tracker/save',
753 '/{repo_name}/settings/issue-tracker/save',
763 controller='admin/repos', action='repo_issuetracker_save',
754 controller='admin/repos', action='repo_issuetracker_save',
764 conditions={'method': ['POST'], 'function': check_repo},
755 conditions={'method': ['POST'], 'function': check_repo},
765 requirements=URL_NAME_REQUIREMENTS)
756 requirements=URL_NAME_REQUIREMENTS)
766 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
757 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
767 controller='admin/repos', action='repo_settings_vcs_update',
758 controller='admin/repos', action='repo_settings_vcs_update',
768 conditions={'method': ['POST'], 'function': check_repo},
759 conditions={'method': ['POST'], 'function': check_repo},
769 requirements=URL_NAME_REQUIREMENTS)
760 requirements=URL_NAME_REQUIREMENTS)
770 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
761 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
771 controller='admin/repos', action='repo_settings_vcs',
762 controller='admin/repos', action='repo_settings_vcs',
772 conditions={'method': ['GET'], 'function': check_repo},
763 conditions={'method': ['GET'], 'function': check_repo},
773 requirements=URL_NAME_REQUIREMENTS)
764 requirements=URL_NAME_REQUIREMENTS)
774 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
765 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
775 controller='admin/repos', action='repo_delete_svn_pattern',
766 controller='admin/repos', action='repo_delete_svn_pattern',
776 conditions={'method': ['DELETE'], 'function': check_repo},
767 conditions={'method': ['DELETE'], 'function': check_repo},
777 requirements=URL_NAME_REQUIREMENTS)
768 requirements=URL_NAME_REQUIREMENTS)
778 rmap.connect('repo_pullrequest_settings', '/{repo_name}/settings/pullrequest',
769 rmap.connect('repo_pullrequest_settings', '/{repo_name}/settings/pullrequest',
779 controller='admin/repos', action='repo_settings_pullrequest',
770 controller='admin/repos', action='repo_settings_pullrequest',
780 conditions={'method': ['GET', 'POST'], 'function': check_repo},
771 conditions={'method': ['GET', 'POST'], 'function': check_repo},
781 requirements=URL_NAME_REQUIREMENTS)
772 requirements=URL_NAME_REQUIREMENTS)
782
773
783 # still working url for backward compat.
774 # still working url for backward compat.
784 rmap.connect('raw_changeset_home_depraced',
775 rmap.connect('raw_changeset_home_depraced',
785 '/{repo_name}/raw-changeset/{revision}',
776 '/{repo_name}/raw-changeset/{revision}',
786 controller='changeset', action='changeset_raw',
777 controller='changeset', action='changeset_raw',
787 revision='tip', conditions={'function': check_repo},
778 revision='tip', conditions={'function': check_repo},
788 requirements=URL_NAME_REQUIREMENTS)
779 requirements=URL_NAME_REQUIREMENTS)
789
780
790 # new URLs
781 # new URLs
791 rmap.connect('changeset_raw_home',
782 rmap.connect('changeset_raw_home',
792 '/{repo_name}/changeset-diff/{revision}',
783 '/{repo_name}/changeset-diff/{revision}',
793 controller='changeset', action='changeset_raw',
784 controller='changeset', action='changeset_raw',
794 revision='tip', conditions={'function': check_repo},
785 revision='tip', conditions={'function': check_repo},
795 requirements=URL_NAME_REQUIREMENTS)
786 requirements=URL_NAME_REQUIREMENTS)
796
787
797 rmap.connect('changeset_patch_home',
788 rmap.connect('changeset_patch_home',
798 '/{repo_name}/changeset-patch/{revision}',
789 '/{repo_name}/changeset-patch/{revision}',
799 controller='changeset', action='changeset_patch',
790 controller='changeset', action='changeset_patch',
800 revision='tip', conditions={'function': check_repo},
791 revision='tip', conditions={'function': check_repo},
801 requirements=URL_NAME_REQUIREMENTS)
792 requirements=URL_NAME_REQUIREMENTS)
802
793
803 rmap.connect('changeset_download_home',
794 rmap.connect('changeset_download_home',
804 '/{repo_name}/changeset-download/{revision}',
795 '/{repo_name}/changeset-download/{revision}',
805 controller='changeset', action='changeset_download',
796 controller='changeset', action='changeset_download',
806 revision='tip', conditions={'function': check_repo},
797 revision='tip', conditions={'function': check_repo},
807 requirements=URL_NAME_REQUIREMENTS)
798 requirements=URL_NAME_REQUIREMENTS)
808
799
809 rmap.connect('changeset_comment',
800 rmap.connect('changeset_comment',
810 '/{repo_name}/changeset/{revision}/comment', jsroute=True,
801 '/{repo_name}/changeset/{revision}/comment', jsroute=True,
811 controller='changeset', revision='tip', action='comment',
802 controller='changeset', revision='tip', action='comment',
812 conditions={'function': check_repo},
803 conditions={'function': check_repo},
813 requirements=URL_NAME_REQUIREMENTS)
804 requirements=URL_NAME_REQUIREMENTS)
814
805
815 rmap.connect('changeset_comment_preview',
806 rmap.connect('changeset_comment_preview',
816 '/{repo_name}/changeset/comment/preview', jsroute=True,
807 '/{repo_name}/changeset/comment/preview', jsroute=True,
817 controller='changeset', action='preview_comment',
808 controller='changeset', action='preview_comment',
818 conditions={'function': check_repo, 'method': ['POST']},
809 conditions={'function': check_repo, 'method': ['POST']},
819 requirements=URL_NAME_REQUIREMENTS)
810 requirements=URL_NAME_REQUIREMENTS)
820
811
821 rmap.connect('changeset_comment_delete',
812 rmap.connect('changeset_comment_delete',
822 '/{repo_name}/changeset/comment/{comment_id}/delete',
813 '/{repo_name}/changeset/comment/{comment_id}/delete',
823 controller='changeset', action='delete_comment',
814 controller='changeset', action='delete_comment',
824 conditions={'function': check_repo, 'method': ['DELETE']},
815 conditions={'function': check_repo, 'method': ['DELETE']},
825 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
816 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
826
817
827 rmap.connect('changeset_info', '/{repo_name}/changeset_info/{revision}',
818 rmap.connect('changeset_info', '/{repo_name}/changeset_info/{revision}',
828 controller='changeset', action='changeset_info',
819 controller='changeset', action='changeset_info',
829 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
820 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
830
821
831 rmap.connect('compare_home',
822 rmap.connect('compare_home',
832 '/{repo_name}/compare',
823 '/{repo_name}/compare',
833 controller='compare', action='index',
824 controller='compare', action='index',
834 conditions={'function': check_repo},
825 conditions={'function': check_repo},
835 requirements=URL_NAME_REQUIREMENTS)
826 requirements=URL_NAME_REQUIREMENTS)
836
827
837 rmap.connect('compare_url',
828 rmap.connect('compare_url',
838 '/{repo_name}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}',
829 '/{repo_name}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}',
839 controller='compare', action='compare',
830 controller='compare', action='compare',
840 conditions={'function': check_repo},
831 conditions={'function': check_repo},
841 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
832 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
842
833
843 rmap.connect('pullrequest_home',
834 rmap.connect('pullrequest_home',
844 '/{repo_name}/pull-request/new', controller='pullrequests',
835 '/{repo_name}/pull-request/new', controller='pullrequests',
845 action='index', conditions={'function': check_repo,
836 action='index', conditions={'function': check_repo,
846 'method': ['GET']},
837 'method': ['GET']},
847 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
838 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
848
839
849 rmap.connect('pullrequest',
840 rmap.connect('pullrequest',
850 '/{repo_name}/pull-request/new', controller='pullrequests',
841 '/{repo_name}/pull-request/new', controller='pullrequests',
851 action='create', conditions={'function': check_repo,
842 action='create', conditions={'function': check_repo,
852 'method': ['POST']},
843 'method': ['POST']},
853 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
844 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
854
845
855 rmap.connect('pullrequest_repo_refs',
846 rmap.connect('pullrequest_repo_refs',
856 '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
847 '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
857 controller='pullrequests',
848 controller='pullrequests',
858 action='get_repo_refs',
849 action='get_repo_refs',
859 conditions={'function': check_repo, 'method': ['GET']},
850 conditions={'function': check_repo, 'method': ['GET']},
860 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
851 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
861
852
862 rmap.connect('pullrequest_repo_destinations',
853 rmap.connect('pullrequest_repo_destinations',
863 '/{repo_name}/pull-request/repo-destinations',
854 '/{repo_name}/pull-request/repo-destinations',
864 controller='pullrequests',
855 controller='pullrequests',
865 action='get_repo_destinations',
856 action='get_repo_destinations',
866 conditions={'function': check_repo, 'method': ['GET']},
857 conditions={'function': check_repo, 'method': ['GET']},
867 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
858 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
868
859
869 rmap.connect('pullrequest_show',
860 rmap.connect('pullrequest_show',
870 '/{repo_name}/pull-request/{pull_request_id}',
861 '/{repo_name}/pull-request/{pull_request_id}',
871 controller='pullrequests',
862 controller='pullrequests',
872 action='show', conditions={'function': check_repo,
863 action='show', conditions={'function': check_repo,
873 'method': ['GET']},
864 'method': ['GET']},
874 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
865 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
875
866
876 rmap.connect('pullrequest_update',
867 rmap.connect('pullrequest_update',
877 '/{repo_name}/pull-request/{pull_request_id}',
868 '/{repo_name}/pull-request/{pull_request_id}',
878 controller='pullrequests',
869 controller='pullrequests',
879 action='update', conditions={'function': check_repo,
870 action='update', conditions={'function': check_repo,
880 'method': ['PUT']},
871 'method': ['PUT']},
881 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
872 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
882
873
883 rmap.connect('pullrequest_merge',
874 rmap.connect('pullrequest_merge',
884 '/{repo_name}/pull-request/{pull_request_id}',
875 '/{repo_name}/pull-request/{pull_request_id}',
885 controller='pullrequests',
876 controller='pullrequests',
886 action='merge', conditions={'function': check_repo,
877 action='merge', conditions={'function': check_repo,
887 'method': ['POST']},
878 'method': ['POST']},
888 requirements=URL_NAME_REQUIREMENTS)
879 requirements=URL_NAME_REQUIREMENTS)
889
880
890 rmap.connect('pullrequest_delete',
881 rmap.connect('pullrequest_delete',
891 '/{repo_name}/pull-request/{pull_request_id}',
882 '/{repo_name}/pull-request/{pull_request_id}',
892 controller='pullrequests',
883 controller='pullrequests',
893 action='delete', conditions={'function': check_repo,
884 action='delete', conditions={'function': check_repo,
894 'method': ['DELETE']},
885 'method': ['DELETE']},
895 requirements=URL_NAME_REQUIREMENTS)
886 requirements=URL_NAME_REQUIREMENTS)
896
887
897 rmap.connect('pullrequest_show_all',
888 rmap.connect('pullrequest_show_all',
898 '/{repo_name}/pull-request',
889 '/{repo_name}/pull-request',
899 controller='pullrequests',
890 controller='pullrequests',
900 action='show_all', conditions={'function': check_repo,
891 action='show_all', conditions={'function': check_repo,
901 'method': ['GET']},
892 'method': ['GET']},
902 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
893 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
903
894
904 rmap.connect('pullrequest_comment',
895 rmap.connect('pullrequest_comment',
905 '/{repo_name}/pull-request-comment/{pull_request_id}',
896 '/{repo_name}/pull-request-comment/{pull_request_id}',
906 controller='pullrequests',
897 controller='pullrequests',
907 action='comment', conditions={'function': check_repo,
898 action='comment', conditions={'function': check_repo,
908 'method': ['POST']},
899 'method': ['POST']},
909 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
900 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
910
901
911 rmap.connect('pullrequest_comment_delete',
902 rmap.connect('pullrequest_comment_delete',
912 '/{repo_name}/pull-request-comment/{comment_id}/delete',
903 '/{repo_name}/pull-request-comment/{comment_id}/delete',
913 controller='pullrequests', action='delete_comment',
904 controller='pullrequests', action='delete_comment',
914 conditions={'function': check_repo, 'method': ['DELETE']},
905 conditions={'function': check_repo, 'method': ['DELETE']},
915 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
906 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
916
907
917 rmap.connect('summary_home_explicit', '/{repo_name}/summary',
908 rmap.connect('summary_home_explicit', '/{repo_name}/summary',
918 controller='summary', conditions={'function': check_repo},
909 controller='summary', conditions={'function': check_repo},
919 requirements=URL_NAME_REQUIREMENTS)
910 requirements=URL_NAME_REQUIREMENTS)
920
911
921 rmap.connect('branches_home', '/{repo_name}/branches',
912 rmap.connect('branches_home', '/{repo_name}/branches',
922 controller='branches', conditions={'function': check_repo},
913 controller='branches', conditions={'function': check_repo},
923 requirements=URL_NAME_REQUIREMENTS)
914 requirements=URL_NAME_REQUIREMENTS)
924
915
925 rmap.connect('tags_home', '/{repo_name}/tags',
916 rmap.connect('tags_home', '/{repo_name}/tags',
926 controller='tags', conditions={'function': check_repo},
917 controller='tags', conditions={'function': check_repo},
927 requirements=URL_NAME_REQUIREMENTS)
918 requirements=URL_NAME_REQUIREMENTS)
928
919
929 rmap.connect('bookmarks_home', '/{repo_name}/bookmarks',
920 rmap.connect('bookmarks_home', '/{repo_name}/bookmarks',
930 controller='bookmarks', conditions={'function': check_repo},
921 controller='bookmarks', conditions={'function': check_repo},
931 requirements=URL_NAME_REQUIREMENTS)
922 requirements=URL_NAME_REQUIREMENTS)
932
923
933 rmap.connect('changelog_home', '/{repo_name}/changelog', jsroute=True,
924 rmap.connect('changelog_home', '/{repo_name}/changelog', jsroute=True,
934 controller='changelog', conditions={'function': check_repo},
925 controller='changelog', conditions={'function': check_repo},
935 requirements=URL_NAME_REQUIREMENTS)
926 requirements=URL_NAME_REQUIREMENTS)
936
927
937 rmap.connect('changelog_summary_home', '/{repo_name}/changelog_summary',
928 rmap.connect('changelog_summary_home', '/{repo_name}/changelog_summary',
938 controller='changelog', action='changelog_summary',
929 controller='changelog', action='changelog_summary',
939 conditions={'function': check_repo},
930 conditions={'function': check_repo},
940 requirements=URL_NAME_REQUIREMENTS)
931 requirements=URL_NAME_REQUIREMENTS)
941
932
942 rmap.connect('changelog_file_home',
933 rmap.connect('changelog_file_home',
943 '/{repo_name}/changelog/{revision}/{f_path}',
934 '/{repo_name}/changelog/{revision}/{f_path}',
944 controller='changelog', f_path=None,
935 controller='changelog', f_path=None,
945 conditions={'function': check_repo},
936 conditions={'function': check_repo},
946 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
937 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
947
938
948 rmap.connect('changelog_elements', '/{repo_name}/changelog_details',
939 rmap.connect('changelog_elements', '/{repo_name}/changelog_details',
949 controller='changelog', action='changelog_elements',
940 controller='changelog', action='changelog_elements',
950 conditions={'function': check_repo},
941 conditions={'function': check_repo},
951 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
942 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
952
943
953 rmap.connect('files_home', '/{repo_name}/files/{revision}/{f_path}',
944 rmap.connect('files_home', '/{repo_name}/files/{revision}/{f_path}',
954 controller='files', revision='tip', f_path='',
945 controller='files', revision='tip', f_path='',
955 conditions={'function': check_repo},
946 conditions={'function': check_repo},
956 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
947 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
957
948
958 rmap.connect('files_home_simple_catchrev',
949 rmap.connect('files_home_simple_catchrev',
959 '/{repo_name}/files/{revision}',
950 '/{repo_name}/files/{revision}',
960 controller='files', revision='tip', f_path='',
951 controller='files', revision='tip', f_path='',
961 conditions={'function': check_repo},
952 conditions={'function': check_repo},
962 requirements=URL_NAME_REQUIREMENTS)
953 requirements=URL_NAME_REQUIREMENTS)
963
954
964 rmap.connect('files_home_simple_catchall',
955 rmap.connect('files_home_simple_catchall',
965 '/{repo_name}/files',
956 '/{repo_name}/files',
966 controller='files', revision='tip', f_path='',
957 controller='files', revision='tip', f_path='',
967 conditions={'function': check_repo},
958 conditions={'function': check_repo},
968 requirements=URL_NAME_REQUIREMENTS)
959 requirements=URL_NAME_REQUIREMENTS)
969
960
970 rmap.connect('files_history_home',
961 rmap.connect('files_history_home',
971 '/{repo_name}/history/{revision}/{f_path}',
962 '/{repo_name}/history/{revision}/{f_path}',
972 controller='files', action='history', revision='tip', f_path='',
963 controller='files', action='history', revision='tip', f_path='',
973 conditions={'function': check_repo},
964 conditions={'function': check_repo},
974 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
965 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
975
966
976 rmap.connect('files_authors_home',
967 rmap.connect('files_authors_home',
977 '/{repo_name}/authors/{revision}/{f_path}',
968 '/{repo_name}/authors/{revision}/{f_path}',
978 controller='files', action='authors', revision='tip', f_path='',
969 controller='files', action='authors', revision='tip', f_path='',
979 conditions={'function': check_repo},
970 conditions={'function': check_repo},
980 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
971 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
981
972
982 rmap.connect('files_diff_home', '/{repo_name}/diff/{f_path}',
973 rmap.connect('files_diff_home', '/{repo_name}/diff/{f_path}',
983 controller='files', action='diff', f_path='',
974 controller='files', action='diff', f_path='',
984 conditions={'function': check_repo},
975 conditions={'function': check_repo},
985 requirements=URL_NAME_REQUIREMENTS)
976 requirements=URL_NAME_REQUIREMENTS)
986
977
987 rmap.connect('files_diff_2way_home',
978 rmap.connect('files_diff_2way_home',
988 '/{repo_name}/diff-2way/{f_path}',
979 '/{repo_name}/diff-2way/{f_path}',
989 controller='files', action='diff_2way', f_path='',
980 controller='files', action='diff_2way', f_path='',
990 conditions={'function': check_repo},
981 conditions={'function': check_repo},
991 requirements=URL_NAME_REQUIREMENTS)
982 requirements=URL_NAME_REQUIREMENTS)
992
983
993 rmap.connect('files_rawfile_home',
984 rmap.connect('files_rawfile_home',
994 '/{repo_name}/rawfile/{revision}/{f_path}',
985 '/{repo_name}/rawfile/{revision}/{f_path}',
995 controller='files', action='rawfile', revision='tip',
986 controller='files', action='rawfile', revision='tip',
996 f_path='', conditions={'function': check_repo},
987 f_path='', conditions={'function': check_repo},
997 requirements=URL_NAME_REQUIREMENTS)
988 requirements=URL_NAME_REQUIREMENTS)
998
989
999 rmap.connect('files_raw_home',
990 rmap.connect('files_raw_home',
1000 '/{repo_name}/raw/{revision}/{f_path}',
991 '/{repo_name}/raw/{revision}/{f_path}',
1001 controller='files', action='raw', revision='tip', f_path='',
992 controller='files', action='raw', revision='tip', f_path='',
1002 conditions={'function': check_repo},
993 conditions={'function': check_repo},
1003 requirements=URL_NAME_REQUIREMENTS)
994 requirements=URL_NAME_REQUIREMENTS)
1004
995
1005 rmap.connect('files_render_home',
996 rmap.connect('files_render_home',
1006 '/{repo_name}/render/{revision}/{f_path}',
997 '/{repo_name}/render/{revision}/{f_path}',
1007 controller='files', action='index', revision='tip', f_path='',
998 controller='files', action='index', revision='tip', f_path='',
1008 rendered=True, conditions={'function': check_repo},
999 rendered=True, conditions={'function': check_repo},
1009 requirements=URL_NAME_REQUIREMENTS)
1000 requirements=URL_NAME_REQUIREMENTS)
1010
1001
1011 rmap.connect('files_annotate_home',
1002 rmap.connect('files_annotate_home',
1012 '/{repo_name}/annotate/{revision}/{f_path}',
1003 '/{repo_name}/annotate/{revision}/{f_path}',
1013 controller='files', action='index', revision='tip',
1004 controller='files', action='index', revision='tip',
1014 f_path='', annotate=True, conditions={'function': check_repo},
1005 f_path='', annotate=True, conditions={'function': check_repo},
1015 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1006 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1016
1007
1017 rmap.connect('files_annotate_previous',
1008 rmap.connect('files_annotate_previous',
1018 '/{repo_name}/annotate-previous/{revision}/{f_path}',
1009 '/{repo_name}/annotate-previous/{revision}/{f_path}',
1019 controller='files', action='annotate_previous', revision='tip',
1010 controller='files', action='annotate_previous', revision='tip',
1020 f_path='', annotate=True, conditions={'function': check_repo},
1011 f_path='', annotate=True, conditions={'function': check_repo},
1021 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1012 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1022
1013
1023 rmap.connect('files_edit',
1014 rmap.connect('files_edit',
1024 '/{repo_name}/edit/{revision}/{f_path}',
1015 '/{repo_name}/edit/{revision}/{f_path}',
1025 controller='files', action='edit', revision='tip',
1016 controller='files', action='edit', revision='tip',
1026 f_path='',
1017 f_path='',
1027 conditions={'function': check_repo, 'method': ['POST']},
1018 conditions={'function': check_repo, 'method': ['POST']},
1028 requirements=URL_NAME_REQUIREMENTS)
1019 requirements=URL_NAME_REQUIREMENTS)
1029
1020
1030 rmap.connect('files_edit_home',
1021 rmap.connect('files_edit_home',
1031 '/{repo_name}/edit/{revision}/{f_path}',
1022 '/{repo_name}/edit/{revision}/{f_path}',
1032 controller='files', action='edit_home', revision='tip',
1023 controller='files', action='edit_home', revision='tip',
1033 f_path='', conditions={'function': check_repo},
1024 f_path='', conditions={'function': check_repo},
1034 requirements=URL_NAME_REQUIREMENTS)
1025 requirements=URL_NAME_REQUIREMENTS)
1035
1026
1036 rmap.connect('files_add',
1027 rmap.connect('files_add',
1037 '/{repo_name}/add/{revision}/{f_path}',
1028 '/{repo_name}/add/{revision}/{f_path}',
1038 controller='files', action='add', revision='tip',
1029 controller='files', action='add', revision='tip',
1039 f_path='',
1030 f_path='',
1040 conditions={'function': check_repo, 'method': ['POST']},
1031 conditions={'function': check_repo, 'method': ['POST']},
1041 requirements=URL_NAME_REQUIREMENTS)
1032 requirements=URL_NAME_REQUIREMENTS)
1042
1033
1043 rmap.connect('files_add_home',
1034 rmap.connect('files_add_home',
1044 '/{repo_name}/add/{revision}/{f_path}',
1035 '/{repo_name}/add/{revision}/{f_path}',
1045 controller='files', action='add_home', revision='tip',
1036 controller='files', action='add_home', revision='tip',
1046 f_path='', conditions={'function': check_repo},
1037 f_path='', conditions={'function': check_repo},
1047 requirements=URL_NAME_REQUIREMENTS)
1038 requirements=URL_NAME_REQUIREMENTS)
1048
1039
1049 rmap.connect('files_delete',
1040 rmap.connect('files_delete',
1050 '/{repo_name}/delete/{revision}/{f_path}',
1041 '/{repo_name}/delete/{revision}/{f_path}',
1051 controller='files', action='delete', revision='tip',
1042 controller='files', action='delete', revision='tip',
1052 f_path='',
1043 f_path='',
1053 conditions={'function': check_repo, 'method': ['POST']},
1044 conditions={'function': check_repo, 'method': ['POST']},
1054 requirements=URL_NAME_REQUIREMENTS)
1045 requirements=URL_NAME_REQUIREMENTS)
1055
1046
1056 rmap.connect('files_delete_home',
1047 rmap.connect('files_delete_home',
1057 '/{repo_name}/delete/{revision}/{f_path}',
1048 '/{repo_name}/delete/{revision}/{f_path}',
1058 controller='files', action='delete_home', revision='tip',
1049 controller='files', action='delete_home', revision='tip',
1059 f_path='', conditions={'function': check_repo},
1050 f_path='', conditions={'function': check_repo},
1060 requirements=URL_NAME_REQUIREMENTS)
1051 requirements=URL_NAME_REQUIREMENTS)
1061
1052
1062 rmap.connect('files_archive_home', '/{repo_name}/archive/{fname}',
1053 rmap.connect('files_archive_home', '/{repo_name}/archive/{fname}',
1063 controller='files', action='archivefile',
1054 controller='files', action='archivefile',
1064 conditions={'function': check_repo},
1055 conditions={'function': check_repo},
1065 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1056 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1066
1057
1067 rmap.connect('files_nodelist_home',
1058 rmap.connect('files_nodelist_home',
1068 '/{repo_name}/nodelist/{revision}/{f_path}',
1059 '/{repo_name}/nodelist/{revision}/{f_path}',
1069 controller='files', action='nodelist',
1060 controller='files', action='nodelist',
1070 conditions={'function': check_repo},
1061 conditions={'function': check_repo},
1071 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1062 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1072
1063
1073 rmap.connect('files_nodetree_full',
1064 rmap.connect('files_nodetree_full',
1074 '/{repo_name}/nodetree_full/{commit_id}/{f_path}',
1065 '/{repo_name}/nodetree_full/{commit_id}/{f_path}',
1075 controller='files', action='nodetree_full',
1066 controller='files', action='nodetree_full',
1076 conditions={'function': check_repo},
1067 conditions={'function': check_repo},
1077 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1068 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1078
1069
1079 rmap.connect('repo_fork_create_home', '/{repo_name}/fork',
1070 rmap.connect('repo_fork_create_home', '/{repo_name}/fork',
1080 controller='forks', action='fork_create',
1071 controller='forks', action='fork_create',
1081 conditions={'function': check_repo, 'method': ['POST']},
1072 conditions={'function': check_repo, 'method': ['POST']},
1082 requirements=URL_NAME_REQUIREMENTS)
1073 requirements=URL_NAME_REQUIREMENTS)
1083
1074
1084 rmap.connect('repo_fork_home', '/{repo_name}/fork',
1075 rmap.connect('repo_fork_home', '/{repo_name}/fork',
1085 controller='forks', action='fork',
1076 controller='forks', action='fork',
1086 conditions={'function': check_repo},
1077 conditions={'function': check_repo},
1087 requirements=URL_NAME_REQUIREMENTS)
1078 requirements=URL_NAME_REQUIREMENTS)
1088
1079
1089 rmap.connect('repo_forks_home', '/{repo_name}/forks',
1080 rmap.connect('repo_forks_home', '/{repo_name}/forks',
1090 controller='forks', action='forks',
1081 controller='forks', action='forks',
1091 conditions={'function': check_repo},
1082 conditions={'function': check_repo},
1092 requirements=URL_NAME_REQUIREMENTS)
1083 requirements=URL_NAME_REQUIREMENTS)
1093
1084
1094 # must be here for proper group/repo catching pattern
1085 # must be here for proper group/repo catching pattern
1095 _connect_with_slash(
1086 _connect_with_slash(
1096 rmap, 'repo_group_home', '/{group_name}',
1087 rmap, 'repo_group_home', '/{group_name}',
1097 controller='home', action='index_repo_group',
1088 controller='home', action='index_repo_group',
1098 conditions={'function': check_group},
1089 conditions={'function': check_group},
1099 requirements=URL_NAME_REQUIREMENTS)
1090 requirements=URL_NAME_REQUIREMENTS)
1100
1091
1101 # catch all, at the end
1092 # catch all, at the end
1102 _connect_with_slash(
1093 _connect_with_slash(
1103 rmap, 'summary_home', '/{repo_name}', jsroute=True,
1094 rmap, 'summary_home', '/{repo_name}', jsroute=True,
1104 controller='summary', action='index',
1095 controller='summary', action='index',
1105 conditions={'function': check_repo},
1096 conditions={'function': check_repo},
1106 requirements=URL_NAME_REQUIREMENTS)
1097 requirements=URL_NAME_REQUIREMENTS)
1107
1098
1108 return rmap
1099 return rmap
1109
1100
1110
1101
1111 def _connect_with_slash(mapper, name, path, *args, **kwargs):
1102 def _connect_with_slash(mapper, name, path, *args, **kwargs):
1112 """
1103 """
1113 Connect a route with an optional trailing slash in `path`.
1104 Connect a route with an optional trailing slash in `path`.
1114 """
1105 """
1115 mapper.connect(name + '_slash', path + '/', *args, **kwargs)
1106 mapper.connect(name + '_slash', path + '/', *args, **kwargs)
1116 mapper.connect(name, path, *args, **kwargs)
1107 mapper.connect(name, path, *args, **kwargs)
@@ -1,888 +1,805 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2013-2017 RhodeCode GmbH
3 # Copyright (C) 2013-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 Repositories controller for RhodeCode
23 Repositories controller for RhodeCode
24 """
24 """
25
25
26 import logging
26 import logging
27 import traceback
27 import traceback
28
28
29 import formencode
29 import formencode
30 from formencode import htmlfill
30 from formencode import htmlfill
31 from pylons import request, tmpl_context as c, url
31 from pylons import request, tmpl_context as c, url
32 from pylons.controllers.util import redirect
32 from pylons.controllers.util import redirect
33 from pylons.i18n.translation import _
33 from pylons.i18n.translation import _
34 from webob.exc import HTTPForbidden, HTTPNotFound, HTTPBadRequest
34 from webob.exc import HTTPForbidden, HTTPNotFound, HTTPBadRequest
35
35
36 import rhodecode
36 import rhodecode
37 from rhodecode.lib import auth, helpers as h
37 from rhodecode.lib import auth, helpers as h
38 from rhodecode.lib.auth import (
38 from rhodecode.lib.auth import (
39 LoginRequired, HasPermissionAllDecorator,
39 LoginRequired, HasPermissionAllDecorator,
40 HasRepoPermissionAllDecorator, NotAnonymous, HasPermissionAny,
40 HasRepoPermissionAllDecorator, NotAnonymous, HasPermissionAny,
41 HasRepoGroupPermissionAny, HasRepoPermissionAnyDecorator)
41 HasRepoGroupPermissionAny, HasRepoPermissionAnyDecorator)
42 from rhodecode.lib.base import BaseRepoController, render
42 from rhodecode.lib.base import BaseRepoController, render
43 from rhodecode.lib.ext_json import json
43 from rhodecode.lib.ext_json import json
44 from rhodecode.lib.exceptions import AttachedForksError
44 from rhodecode.lib.exceptions import AttachedForksError
45 from rhodecode.lib.utils import action_logger, repo_name_slug, jsonify
45 from rhodecode.lib.utils import action_logger, repo_name_slug, jsonify
46 from rhodecode.lib.utils2 import safe_int, str2bool
46 from rhodecode.lib.utils2 import safe_int, str2bool
47 from rhodecode.lib.vcs import RepositoryError
47 from rhodecode.lib.vcs import RepositoryError
48 from rhodecode.model.db import (
48 from rhodecode.model.db import (
49 User, Repository, UserFollowing, RepoGroup, RepositoryField)
49 User, Repository, UserFollowing, RepoGroup, RepositoryField)
50 from rhodecode.model.forms import (
50 from rhodecode.model.forms import (
51 RepoForm, RepoFieldForm, RepoPermsForm, RepoVcsSettingsForm,
51 RepoForm, RepoFieldForm, RepoPermsForm, RepoVcsSettingsForm,
52 IssueTrackerPatternsForm)
52 IssueTrackerPatternsForm)
53 from rhodecode.model.meta import Session
53 from rhodecode.model.meta import Session
54 from rhodecode.model.repo import RepoModel
54 from rhodecode.model.repo import RepoModel
55 from rhodecode.model.scm import ScmModel, RepoGroupList, RepoList
55 from rhodecode.model.scm import ScmModel, RepoGroupList, RepoList
56 from rhodecode.model.settings import (
56 from rhodecode.model.settings import (
57 SettingsModel, IssueTrackerSettingsModel, VcsSettingsModel,
57 SettingsModel, IssueTrackerSettingsModel, VcsSettingsModel,
58 SettingNotFound)
58 SettingNotFound)
59
59
60 log = logging.getLogger(__name__)
60 log = logging.getLogger(__name__)
61
61
62
62
63 class ReposController(BaseRepoController):
63 class ReposController(BaseRepoController):
64 """
64 """
65 REST Controller styled on the Atom Publishing Protocol"""
65 REST Controller styled on the Atom Publishing Protocol"""
66 # To properly map this controller, ensure your config/routing.py
66 # To properly map this controller, ensure your config/routing.py
67 # file has a resource setup:
67 # file has a resource setup:
68 # map.resource('repo', 'repos')
68 # map.resource('repo', 'repos')
69
69
70 @LoginRequired()
70 @LoginRequired()
71 def __before__(self):
71 def __before__(self):
72 super(ReposController, self).__before__()
72 super(ReposController, self).__before__()
73
73
74 def _load_repo(self, repo_name):
74 def _load_repo(self, repo_name):
75 repo_obj = Repository.get_by_repo_name(repo_name)
75 repo_obj = Repository.get_by_repo_name(repo_name)
76
76
77 if repo_obj is None:
77 if repo_obj is None:
78 h.not_mapped_error(repo_name)
78 h.not_mapped_error(repo_name)
79 return redirect(url('repos'))
79 return redirect(url('repos'))
80
80
81 return repo_obj
81 return repo_obj
82
82
83 def __load_defaults(self, repo=None):
83 def __load_defaults(self, repo=None):
84 acl_groups = RepoGroupList(RepoGroup.query().all(),
84 acl_groups = RepoGroupList(RepoGroup.query().all(),
85 perm_set=['group.write', 'group.admin'])
85 perm_set=['group.write', 'group.admin'])
86 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
86 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
87 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
87 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
88
88
89 # in case someone no longer have a group.write access to a repository
89 # in case someone no longer have a group.write access to a repository
90 # pre fill the list with this entry, we don't care if this is the same
90 # pre fill the list with this entry, we don't care if this is the same
91 # but it will allow saving repo data properly.
91 # but it will allow saving repo data properly.
92
92
93 repo_group = None
93 repo_group = None
94 if repo:
94 if repo:
95 repo_group = repo.group
95 repo_group = repo.group
96 if repo_group and unicode(repo_group.group_id) not in c.repo_groups_choices:
96 if repo_group and unicode(repo_group.group_id) not in c.repo_groups_choices:
97 c.repo_groups_choices.append(unicode(repo_group.group_id))
97 c.repo_groups_choices.append(unicode(repo_group.group_id))
98 c.repo_groups.append(RepoGroup._generate_choice(repo_group))
98 c.repo_groups.append(RepoGroup._generate_choice(repo_group))
99
99
100 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
100 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
101 c.landing_revs_choices = choices
101 c.landing_revs_choices = choices
102
102
103 def __load_data(self, repo_name=None):
103 def __load_data(self, repo_name=None):
104 """
104 """
105 Load defaults settings for edit, and update
105 Load defaults settings for edit, and update
106
106
107 :param repo_name:
107 :param repo_name:
108 """
108 """
109 c.repo_info = self._load_repo(repo_name)
109 c.repo_info = self._load_repo(repo_name)
110 self.__load_defaults(c.repo_info)
110 self.__load_defaults(c.repo_info)
111
111
112 # override defaults for exact repo info here git/hg etc
112 # override defaults for exact repo info here git/hg etc
113 if not c.repository_requirements_missing:
113 if not c.repository_requirements_missing:
114 choices, c.landing_revs = ScmModel().get_repo_landing_revs(
114 choices, c.landing_revs = ScmModel().get_repo_landing_revs(
115 c.repo_info)
115 c.repo_info)
116 c.landing_revs_choices = choices
116 c.landing_revs_choices = choices
117 defaults = RepoModel()._get_defaults(repo_name)
117 defaults = RepoModel()._get_defaults(repo_name)
118
118
119 return defaults
119 return defaults
120
120
121 def _log_creation_exception(self, e, repo_name):
121 def _log_creation_exception(self, e, repo_name):
122 reason = None
122 reason = None
123 if len(e.args) == 2:
123 if len(e.args) == 2:
124 reason = e.args[1]
124 reason = e.args[1]
125
125
126 if reason == 'INVALID_CERTIFICATE':
126 if reason == 'INVALID_CERTIFICATE':
127 log.exception(
127 log.exception(
128 'Exception creating a repository: invalid certificate')
128 'Exception creating a repository: invalid certificate')
129 msg = (_('Error creating repository %s: invalid certificate')
129 msg = (_('Error creating repository %s: invalid certificate')
130 % repo_name)
130 % repo_name)
131 else:
131 else:
132 log.exception("Exception creating a repository")
132 log.exception("Exception creating a repository")
133 msg = (_('Error creating repository %s')
133 msg = (_('Error creating repository %s')
134 % repo_name)
134 % repo_name)
135
135
136 return msg
136 return msg
137
137
138 @NotAnonymous()
138 @NotAnonymous()
139 def index(self, format='html'):
139 def index(self, format='html'):
140 """GET /repos: All items in the collection"""
140 """GET /repos: All items in the collection"""
141 # url('repos')
141 # url('repos')
142
142
143 repo_list = Repository.get_all_repos()
143 repo_list = Repository.get_all_repos()
144 c.repo_list = RepoList(repo_list, perm_set=['repository.admin'])
144 c.repo_list = RepoList(repo_list, perm_set=['repository.admin'])
145 repos_data = RepoModel().get_repos_as_dict(
145 repos_data = RepoModel().get_repos_as_dict(
146 repo_list=c.repo_list, admin=True, super_user_actions=True)
146 repo_list=c.repo_list, admin=True, super_user_actions=True)
147 # json used to render the grid
147 # json used to render the grid
148 c.data = json.dumps(repos_data)
148 c.data = json.dumps(repos_data)
149
149
150 return render('admin/repos/repos.mako')
150 return render('admin/repos/repos.mako')
151
151
152 # perms check inside
152 # perms check inside
153 @NotAnonymous()
153 @NotAnonymous()
154 @auth.CSRFRequired()
154 @auth.CSRFRequired()
155 def create(self):
155 def create(self):
156 """
156 """
157 POST /repos: Create a new item"""
157 POST /repos: Create a new item"""
158 # url('repos')
158 # url('repos')
159
159
160 self.__load_defaults()
160 self.__load_defaults()
161 form_result = {}
161 form_result = {}
162 task_id = None
162 task_id = None
163 c.personal_repo_group = c.rhodecode_user.personal_repo_group
163 c.personal_repo_group = c.rhodecode_user.personal_repo_group
164 try:
164 try:
165 # CanWriteToGroup validators checks permissions of this POST
165 # CanWriteToGroup validators checks permissions of this POST
166 form_result = RepoForm(repo_groups=c.repo_groups_choices,
166 form_result = RepoForm(repo_groups=c.repo_groups_choices,
167 landing_revs=c.landing_revs_choices)()\
167 landing_revs=c.landing_revs_choices)()\
168 .to_python(dict(request.POST))
168 .to_python(dict(request.POST))
169
169
170 # create is done sometimes async on celery, db transaction
170 # create is done sometimes async on celery, db transaction
171 # management is handled there.
171 # management is handled there.
172 task = RepoModel().create(form_result, c.rhodecode_user.user_id)
172 task = RepoModel().create(form_result, c.rhodecode_user.user_id)
173 from celery.result import BaseAsyncResult
173 from celery.result import BaseAsyncResult
174 if isinstance(task, BaseAsyncResult):
174 if isinstance(task, BaseAsyncResult):
175 task_id = task.task_id
175 task_id = task.task_id
176 except formencode.Invalid as errors:
176 except formencode.Invalid as errors:
177 return htmlfill.render(
177 return htmlfill.render(
178 render('admin/repos/repo_add.mako'),
178 render('admin/repos/repo_add.mako'),
179 defaults=errors.value,
179 defaults=errors.value,
180 errors=errors.error_dict or {},
180 errors=errors.error_dict or {},
181 prefix_error=False,
181 prefix_error=False,
182 encoding="UTF-8",
182 encoding="UTF-8",
183 force_defaults=False)
183 force_defaults=False)
184
184
185 except Exception as e:
185 except Exception as e:
186 msg = self._log_creation_exception(e, form_result.get('repo_name'))
186 msg = self._log_creation_exception(e, form_result.get('repo_name'))
187 h.flash(msg, category='error')
187 h.flash(msg, category='error')
188 return redirect(url('home'))
188 return redirect(url('home'))
189
189
190 return redirect(h.url('repo_creating_home',
190 return redirect(h.url('repo_creating_home',
191 repo_name=form_result['repo_name_full'],
191 repo_name=form_result['repo_name_full'],
192 task_id=task_id))
192 task_id=task_id))
193
193
194 # perms check inside
194 # perms check inside
195 @NotAnonymous()
195 @NotAnonymous()
196 def create_repository(self):
196 def create_repository(self):
197 """GET /_admin/create_repository: Form to create a new item"""
197 """GET /_admin/create_repository: Form to create a new item"""
198 new_repo = request.GET.get('repo', '')
198 new_repo = request.GET.get('repo', '')
199 parent_group = safe_int(request.GET.get('parent_group'))
199 parent_group = safe_int(request.GET.get('parent_group'))
200 _gr = RepoGroup.get(parent_group)
200 _gr = RepoGroup.get(parent_group)
201
201
202 if not HasPermissionAny('hg.admin', 'hg.create.repository')():
202 if not HasPermissionAny('hg.admin', 'hg.create.repository')():
203 # you're not super admin nor have global create permissions,
203 # you're not super admin nor have global create permissions,
204 # but maybe you have at least write permission to a parent group ?
204 # but maybe you have at least write permission to a parent group ?
205
205
206 gr_name = _gr.group_name if _gr else None
206 gr_name = _gr.group_name if _gr else None
207 # create repositories with write permission on group is set to true
207 # create repositories with write permission on group is set to true
208 create_on_write = HasPermissionAny('hg.create.write_on_repogroup.true')()
208 create_on_write = HasPermissionAny('hg.create.write_on_repogroup.true')()
209 group_admin = HasRepoGroupPermissionAny('group.admin')(group_name=gr_name)
209 group_admin = HasRepoGroupPermissionAny('group.admin')(group_name=gr_name)
210 group_write = HasRepoGroupPermissionAny('group.write')(group_name=gr_name)
210 group_write = HasRepoGroupPermissionAny('group.write')(group_name=gr_name)
211 if not (group_admin or (group_write and create_on_write)):
211 if not (group_admin or (group_write and create_on_write)):
212 raise HTTPForbidden
212 raise HTTPForbidden
213
213
214 acl_groups = RepoGroupList(RepoGroup.query().all(),
214 acl_groups = RepoGroupList(RepoGroup.query().all(),
215 perm_set=['group.write', 'group.admin'])
215 perm_set=['group.write', 'group.admin'])
216 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
216 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
217 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
217 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
218 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
218 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
219 c.personal_repo_group = c.rhodecode_user.personal_repo_group
219 c.personal_repo_group = c.rhodecode_user.personal_repo_group
220 c.new_repo = repo_name_slug(new_repo)
220 c.new_repo = repo_name_slug(new_repo)
221
221
222 # apply the defaults from defaults page
222 # apply the defaults from defaults page
223 defaults = SettingsModel().get_default_repo_settings(strip_prefix=True)
223 defaults = SettingsModel().get_default_repo_settings(strip_prefix=True)
224 # set checkbox to autochecked
224 # set checkbox to autochecked
225 defaults['repo_copy_permissions'] = True
225 defaults['repo_copy_permissions'] = True
226
226
227 parent_group_choice = '-1'
227 parent_group_choice = '-1'
228 if not c.rhodecode_user.is_admin and c.rhodecode_user.personal_repo_group:
228 if not c.rhodecode_user.is_admin and c.rhodecode_user.personal_repo_group:
229 parent_group_choice = c.rhodecode_user.personal_repo_group
229 parent_group_choice = c.rhodecode_user.personal_repo_group
230
230
231 if parent_group and _gr:
231 if parent_group and _gr:
232 if parent_group in [x[0] for x in c.repo_groups]:
232 if parent_group in [x[0] for x in c.repo_groups]:
233 parent_group_choice = unicode(parent_group)
233 parent_group_choice = unicode(parent_group)
234
234
235 defaults.update({'repo_group': parent_group_choice})
235 defaults.update({'repo_group': parent_group_choice})
236
236
237 return htmlfill.render(
237 return htmlfill.render(
238 render('admin/repos/repo_add.mako'),
238 render('admin/repos/repo_add.mako'),
239 defaults=defaults,
239 defaults=defaults,
240 errors={},
240 errors={},
241 prefix_error=False,
241 prefix_error=False,
242 encoding="UTF-8",
242 encoding="UTF-8",
243 force_defaults=False
243 force_defaults=False
244 )
244 )
245
245
246 @NotAnonymous()
246 @NotAnonymous()
247 def repo_creating(self, repo_name):
247 def repo_creating(self, repo_name):
248 c.repo = repo_name
248 c.repo = repo_name
249 c.task_id = request.GET.get('task_id')
249 c.task_id = request.GET.get('task_id')
250 if not c.repo:
250 if not c.repo:
251 raise HTTPNotFound()
251 raise HTTPNotFound()
252 return render('admin/repos/repo_creating.mako')
252 return render('admin/repos/repo_creating.mako')
253
253
254 @NotAnonymous()
254 @NotAnonymous()
255 @jsonify
255 @jsonify
256 def repo_check(self, repo_name):
256 def repo_check(self, repo_name):
257 c.repo = repo_name
257 c.repo = repo_name
258 task_id = request.GET.get('task_id')
258 task_id = request.GET.get('task_id')
259
259
260 if task_id and task_id not in ['None']:
260 if task_id and task_id not in ['None']:
261 import rhodecode
261 import rhodecode
262 from celery.result import AsyncResult
262 from celery.result import AsyncResult
263 if rhodecode.CELERY_ENABLED:
263 if rhodecode.CELERY_ENABLED:
264 task = AsyncResult(task_id)
264 task = AsyncResult(task_id)
265 if task.failed():
265 if task.failed():
266 msg = self._log_creation_exception(task.result, c.repo)
266 msg = self._log_creation_exception(task.result, c.repo)
267 h.flash(msg, category='error')
267 h.flash(msg, category='error')
268 return redirect(url('home'), code=501)
268 return redirect(url('home'), code=501)
269
269
270 repo = Repository.get_by_repo_name(repo_name)
270 repo = Repository.get_by_repo_name(repo_name)
271 if repo and repo.repo_state == Repository.STATE_CREATED:
271 if repo and repo.repo_state == Repository.STATE_CREATED:
272 if repo.clone_uri:
272 if repo.clone_uri:
273 clone_uri = repo.clone_uri_hidden
273 clone_uri = repo.clone_uri_hidden
274 h.flash(_('Created repository %s from %s')
274 h.flash(_('Created repository %s from %s')
275 % (repo.repo_name, clone_uri), category='success')
275 % (repo.repo_name, clone_uri), category='success')
276 else:
276 else:
277 repo_url = h.link_to(repo.repo_name,
277 repo_url = h.link_to(repo.repo_name,
278 h.url('summary_home',
278 h.url('summary_home',
279 repo_name=repo.repo_name))
279 repo_name=repo.repo_name))
280 fork = repo.fork
280 fork = repo.fork
281 if fork:
281 if fork:
282 fork_name = fork.repo_name
282 fork_name = fork.repo_name
283 h.flash(h.literal(_('Forked repository %s as %s')
283 h.flash(h.literal(_('Forked repository %s as %s')
284 % (fork_name, repo_url)), category='success')
284 % (fork_name, repo_url)), category='success')
285 else:
285 else:
286 h.flash(h.literal(_('Created repository %s') % repo_url),
286 h.flash(h.literal(_('Created repository %s') % repo_url),
287 category='success')
287 category='success')
288 return {'result': True}
288 return {'result': True}
289 return {'result': False}
289 return {'result': False}
290
290
291 @HasRepoPermissionAllDecorator('repository.admin')
291 @HasRepoPermissionAllDecorator('repository.admin')
292 @auth.CSRFRequired()
292 @auth.CSRFRequired()
293 def update(self, repo_name):
294 """
295 PUT /repos/repo_name: Update an existing item"""
296 # Forms posted to this method should contain a hidden field:
297 # <input type="hidden" name="_method" value="PUT" />
298 # Or using helpers:
299 # h.form(url('repo', repo_name=ID),
300 # method='put')
301 # url('repo', repo_name=ID)
302
303 self.__load_data(repo_name)
304 c.active = 'settings'
305 c.repo_fields = RepositoryField.query()\
306 .filter(RepositoryField.repository == c.repo_info).all()
307
308 repo_model = RepoModel()
309 changed_name = repo_name
310
311 c.personal_repo_group = c.rhodecode_user.personal_repo_group
312 # override the choices with extracted revisions !
313 repo = Repository.get_by_repo_name(repo_name)
314 old_data = {
315 'repo_name': repo_name,
316 'repo_group': repo.group.get_dict() if repo.group else {},
317 'repo_type': repo.repo_type,
318 }
319 _form = RepoForm(
320 edit=True, old_data=old_data, repo_groups=c.repo_groups_choices,
321 landing_revs=c.landing_revs_choices, allow_disabled=True)()
322
323 try:
324 form_result = _form.to_python(dict(request.POST))
325 repo = repo_model.update(repo_name, **form_result)
326 ScmModel().mark_for_invalidation(repo_name)
327 h.flash(_('Repository %s updated successfully') % repo_name,
328 category='success')
329 changed_name = repo.repo_name
330 action_logger(c.rhodecode_user, 'admin_updated_repo',
331 changed_name, self.ip_addr, self.sa)
332 Session().commit()
333 except formencode.Invalid as errors:
334 defaults = self.__load_data(repo_name)
335 defaults.update(errors.value)
336 return htmlfill.render(
337 render('admin/repos/repo_edit.mako'),
338 defaults=defaults,
339 errors=errors.error_dict or {},
340 prefix_error=False,
341 encoding="UTF-8",
342 force_defaults=False)
343
344 except Exception:
345 log.exception("Exception during update of repository")
346 h.flash(_('Error occurred during update of repository %s') \
347 % repo_name, category='error')
348 return redirect(url('edit_repo', repo_name=changed_name))
349
350 @HasRepoPermissionAllDecorator('repository.admin')
351 @auth.CSRFRequired()
352 def delete(self, repo_name):
293 def delete(self, repo_name):
353 """
294 """
354 DELETE /repos/repo_name: Delete an existing item"""
295 DELETE /repos/repo_name: Delete an existing item"""
355 # Forms posted to this method should contain a hidden field:
296 # Forms posted to this method should contain a hidden field:
356 # <input type="hidden" name="_method" value="DELETE" />
297 # <input type="hidden" name="_method" value="DELETE" />
357 # Or using helpers:
298 # Or using helpers:
358 # h.form(url('repo', repo_name=ID),
299 # h.form(url('repo', repo_name=ID),
359 # method='delete')
300 # method='delete')
360 # url('repo', repo_name=ID)
301 # url('repo', repo_name=ID)
361
302
362 repo_model = RepoModel()
303 repo_model = RepoModel()
363 repo = repo_model.get_by_repo_name(repo_name)
304 repo = repo_model.get_by_repo_name(repo_name)
364 if not repo:
305 if not repo:
365 h.not_mapped_error(repo_name)
306 h.not_mapped_error(repo_name)
366 return redirect(url('repos'))
307 return redirect(url('repos'))
367 try:
308 try:
368 _forks = repo.forks.count()
309 _forks = repo.forks.count()
369 handle_forks = None
310 handle_forks = None
370 if _forks and request.POST.get('forks'):
311 if _forks and request.POST.get('forks'):
371 do = request.POST['forks']
312 do = request.POST['forks']
372 if do == 'detach_forks':
313 if do == 'detach_forks':
373 handle_forks = 'detach'
314 handle_forks = 'detach'
374 h.flash(_('Detached %s forks') % _forks, category='success')
315 h.flash(_('Detached %s forks') % _forks, category='success')
375 elif do == 'delete_forks':
316 elif do == 'delete_forks':
376 handle_forks = 'delete'
317 handle_forks = 'delete'
377 h.flash(_('Deleted %s forks') % _forks, category='success')
318 h.flash(_('Deleted %s forks') % _forks, category='success')
378 repo_model.delete(repo, forks=handle_forks)
319 repo_model.delete(repo, forks=handle_forks)
379 action_logger(c.rhodecode_user, 'admin_deleted_repo',
320 action_logger(c.rhodecode_user, 'admin_deleted_repo',
380 repo_name, self.ip_addr, self.sa)
321 repo_name, self.ip_addr, self.sa)
381 ScmModel().mark_for_invalidation(repo_name)
322 ScmModel().mark_for_invalidation(repo_name)
382 h.flash(_('Deleted repository %s') % repo_name, category='success')
323 h.flash(_('Deleted repository %s') % repo_name, category='success')
383 Session().commit()
324 Session().commit()
384 except AttachedForksError:
325 except AttachedForksError:
385 h.flash(_('Cannot delete %s it still contains attached forks')
326 h.flash(_('Cannot delete %s it still contains attached forks')
386 % repo_name, category='warning')
327 % repo_name, category='warning')
387
328
388 except Exception:
329 except Exception:
389 log.exception("Exception during deletion of repository")
330 log.exception("Exception during deletion of repository")
390 h.flash(_('An error occurred during deletion of %s') % repo_name,
331 h.flash(_('An error occurred during deletion of %s') % repo_name,
391 category='error')
332 category='error')
392
333
393 return redirect(url('repos'))
334 return redirect(url('repos'))
394
335
395 @HasPermissionAllDecorator('hg.admin')
336 @HasPermissionAllDecorator('hg.admin')
396 def show(self, repo_name, format='html'):
337 def show(self, repo_name, format='html'):
397 """GET /repos/repo_name: Show a specific item"""
338 """GET /repos/repo_name: Show a specific item"""
398 # url('repo', repo_name=ID)
339 # url('repo', repo_name=ID)
399
340
400 @HasRepoPermissionAllDecorator('repository.admin')
341 @HasRepoPermissionAllDecorator('repository.admin')
401 def edit(self, repo_name):
402 """GET /repo_name/settings: Form to edit an existing item"""
403 # url('edit_repo', repo_name=ID)
404 defaults = self.__load_data(repo_name)
405 if 'clone_uri' in defaults:
406 del defaults['clone_uri']
407
408 c.repo_fields = RepositoryField.query()\
409 .filter(RepositoryField.repository == c.repo_info).all()
410 c.personal_repo_group = c.rhodecode_user.personal_repo_group
411 c.active = 'settings'
412 return htmlfill.render(
413 render('admin/repos/repo_edit.mako'),
414 defaults=defaults,
415 encoding="UTF-8",
416 force_defaults=False)
417
418 @HasRepoPermissionAllDecorator('repository.admin')
419 def edit_permissions(self, repo_name):
342 def edit_permissions(self, repo_name):
420 """GET /repo_name/settings: Form to edit an existing item"""
343 """GET /repo_name/settings: Form to edit an existing item"""
421 # url('edit_repo', repo_name=ID)
422 c.repo_info = self._load_repo(repo_name)
344 c.repo_info = self._load_repo(repo_name)
423 c.active = 'permissions'
345 c.active = 'permissions'
424 defaults = RepoModel()._get_defaults(repo_name)
346 defaults = RepoModel()._get_defaults(repo_name)
425
347
426 return htmlfill.render(
348 return htmlfill.render(
427 render('admin/repos/repo_edit.mako'),
349 render('admin/repos/repo_edit.mako'),
428 defaults=defaults,
350 defaults=defaults,
429 encoding="UTF-8",
351 encoding="UTF-8",
430 force_defaults=False)
352 force_defaults=False)
431
353
432 @HasRepoPermissionAllDecorator('repository.admin')
354 @HasRepoPermissionAllDecorator('repository.admin')
433 @auth.CSRFRequired()
355 @auth.CSRFRequired()
434 def edit_permissions_update(self, repo_name):
356 def edit_permissions_update(self, repo_name):
435 form = RepoPermsForm()().to_python(request.POST)
357 form = RepoPermsForm()().to_python(request.POST)
436 RepoModel().update_permissions(repo_name,
358 RepoModel().update_permissions(repo_name,
437 form['perm_additions'], form['perm_updates'], form['perm_deletions'])
359 form['perm_additions'], form['perm_updates'], form['perm_deletions'])
438
360
439 #TODO: implement this
361 #TODO: implement this
440 #action_logger(c.rhodecode_user, 'admin_changed_repo_permissions',
362 #action_logger(c.rhodecode_user, 'admin_changed_repo_permissions',
441 # repo_name, self.ip_addr, self.sa)
363 # repo_name, self.ip_addr, self.sa)
442 Session().commit()
364 Session().commit()
443 h.flash(_('Repository permissions updated'), category='success')
365 h.flash(_('Repository permissions updated'), category='success')
444 return redirect(url('edit_repo_perms', repo_name=repo_name))
366 return redirect(url('edit_repo_perms', repo_name=repo_name))
445
367
446 @HasRepoPermissionAllDecorator('repository.admin')
368 @HasRepoPermissionAllDecorator('repository.admin')
447 def edit_fields(self, repo_name):
369 def edit_fields(self, repo_name):
448 """GET /repo_name/settings: Form to edit an existing item"""
370 """GET /repo_name/settings: Form to edit an existing item"""
449 # url('edit_repo', repo_name=ID)
450 c.repo_info = self._load_repo(repo_name)
371 c.repo_info = self._load_repo(repo_name)
451 c.repo_fields = RepositoryField.query()\
372 c.repo_fields = RepositoryField.query()\
452 .filter(RepositoryField.repository == c.repo_info).all()
373 .filter(RepositoryField.repository == c.repo_info).all()
453 c.active = 'fields'
374 c.active = 'fields'
454 if request.POST:
375 if request.POST:
455
376
456 return redirect(url('repo_edit_fields'))
377 return redirect(url('repo_edit_fields'))
457 return render('admin/repos/repo_edit.mako')
378 return render('admin/repos/repo_edit.mako')
458
379
459 @HasRepoPermissionAllDecorator('repository.admin')
380 @HasRepoPermissionAllDecorator('repository.admin')
460 @auth.CSRFRequired()
381 @auth.CSRFRequired()
461 def create_repo_field(self, repo_name):
382 def create_repo_field(self, repo_name):
462 try:
383 try:
463 form_result = RepoFieldForm()().to_python(dict(request.POST))
384 form_result = RepoFieldForm()().to_python(dict(request.POST))
464 RepoModel().add_repo_field(
385 RepoModel().add_repo_field(
465 repo_name, form_result['new_field_key'],
386 repo_name, form_result['new_field_key'],
466 field_type=form_result['new_field_type'],
387 field_type=form_result['new_field_type'],
467 field_value=form_result['new_field_value'],
388 field_value=form_result['new_field_value'],
468 field_label=form_result['new_field_label'],
389 field_label=form_result['new_field_label'],
469 field_desc=form_result['new_field_desc'])
390 field_desc=form_result['new_field_desc'])
470
391
471 Session().commit()
392 Session().commit()
472 except Exception as e:
393 except Exception as e:
473 log.exception("Exception creating field")
394 log.exception("Exception creating field")
474 msg = _('An error occurred during creation of field')
395 msg = _('An error occurred during creation of field')
475 if isinstance(e, formencode.Invalid):
396 if isinstance(e, formencode.Invalid):
476 msg += ". " + e.msg
397 msg += ". " + e.msg
477 h.flash(msg, category='error')
398 h.flash(msg, category='error')
478 return redirect(url('edit_repo_fields', repo_name=repo_name))
399 return redirect(url('edit_repo_fields', repo_name=repo_name))
479
400
480 @HasRepoPermissionAllDecorator('repository.admin')
401 @HasRepoPermissionAllDecorator('repository.admin')
481 @auth.CSRFRequired()
402 @auth.CSRFRequired()
482 def delete_repo_field(self, repo_name, field_id):
403 def delete_repo_field(self, repo_name, field_id):
483 field = RepositoryField.get_or_404(field_id)
404 field = RepositoryField.get_or_404(field_id)
484 try:
405 try:
485 RepoModel().delete_repo_field(repo_name, field.field_key)
406 RepoModel().delete_repo_field(repo_name, field.field_key)
486 Session().commit()
407 Session().commit()
487 except Exception as e:
408 except Exception as e:
488 log.exception("Exception during removal of field")
409 log.exception("Exception during removal of field")
489 msg = _('An error occurred during removal of field')
410 msg = _('An error occurred during removal of field')
490 h.flash(msg, category='error')
411 h.flash(msg, category='error')
491 return redirect(url('edit_repo_fields', repo_name=repo_name))
412 return redirect(url('edit_repo_fields', repo_name=repo_name))
492
413
493 @HasRepoPermissionAllDecorator('repository.admin')
414 @HasRepoPermissionAllDecorator('repository.admin')
494 def edit_advanced(self, repo_name):
415 def edit_advanced(self, repo_name):
495 """GET /repo_name/settings: Form to edit an existing item"""
416 """GET /repo_name/settings: Form to edit an existing item"""
496 # url('edit_repo', repo_name=ID)
497 c.repo_info = self._load_repo(repo_name)
417 c.repo_info = self._load_repo(repo_name)
498 c.default_user_id = User.get_default_user().user_id
418 c.default_user_id = User.get_default_user().user_id
499 c.in_public_journal = UserFollowing.query()\
419 c.in_public_journal = UserFollowing.query()\
500 .filter(UserFollowing.user_id == c.default_user_id)\
420 .filter(UserFollowing.user_id == c.default_user_id)\
501 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
421 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
502
422
503 c.active = 'advanced'
423 c.active = 'advanced'
504 c.has_origin_repo_read_perm = False
424 c.has_origin_repo_read_perm = False
505 if c.repo_info.fork:
425 if c.repo_info.fork:
506 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
426 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
507 'repository.write', 'repository.read', 'repository.admin')(
427 'repository.write', 'repository.read', 'repository.admin')(
508 c.repo_info.fork.repo_name, 'repo set as fork page')
428 c.repo_info.fork.repo_name, 'repo set as fork page')
509
429
510 if request.POST:
430 if request.POST:
511 return redirect(url('repo_edit_advanced'))
431 return redirect(url('repo_edit_advanced'))
512 return render('admin/repos/repo_edit.mako')
432 return render('admin/repos/repo_edit.mako')
513
433
514 @HasRepoPermissionAllDecorator('repository.admin')
434 @HasRepoPermissionAllDecorator('repository.admin')
515 @auth.CSRFRequired()
435 @auth.CSRFRequired()
516 def edit_advanced_journal(self, repo_name):
436 def edit_advanced_journal(self, repo_name):
517 """
437 """
518 Set's this repository to be visible in public journal,
438 Set's this repository to be visible in public journal,
519 in other words assing default user to follow this repo
439 in other words assing default user to follow this repo
520
440
521 :param repo_name:
441 :param repo_name:
522 """
442 """
523
443
524 try:
444 try:
525 repo_id = Repository.get_by_repo_name(repo_name).repo_id
445 repo_id = Repository.get_by_repo_name(repo_name).repo_id
526 user_id = User.get_default_user().user_id
446 user_id = User.get_default_user().user_id
527 self.scm_model.toggle_following_repo(repo_id, user_id)
447 self.scm_model.toggle_following_repo(repo_id, user_id)
528 h.flash(_('Updated repository visibility in public journal'),
448 h.flash(_('Updated repository visibility in public journal'),
529 category='success')
449 category='success')
530 Session().commit()
450 Session().commit()
531 except Exception:
451 except Exception:
532 h.flash(_('An error occurred during setting this'
452 h.flash(_('An error occurred during setting this'
533 ' repository in public journal'),
453 ' repository in public journal'),
534 category='error')
454 category='error')
535
455
536 return redirect(url('edit_repo_advanced', repo_name=repo_name))
456 return redirect(url('edit_repo_advanced', repo_name=repo_name))
537
457
538 @HasRepoPermissionAllDecorator('repository.admin')
458 @HasRepoPermissionAllDecorator('repository.admin')
539 @auth.CSRFRequired()
459 @auth.CSRFRequired()
540 def edit_advanced_fork(self, repo_name):
460 def edit_advanced_fork(self, repo_name):
541 """
461 """
542 Mark given repository as a fork of another
462 Mark given repository as a fork of another
543
463
544 :param repo_name:
464 :param repo_name:
545 """
465 """
546
466
547 new_fork_id = request.POST.get('id_fork_of')
467 new_fork_id = request.POST.get('id_fork_of')
548 try:
468 try:
549
469
550 if new_fork_id and not new_fork_id.isdigit():
470 if new_fork_id and not new_fork_id.isdigit():
551 log.error('Given fork id %s is not an INT', new_fork_id)
471 log.error('Given fork id %s is not an INT', new_fork_id)
552
472
553 fork_id = safe_int(new_fork_id)
473 fork_id = safe_int(new_fork_id)
554 repo = ScmModel().mark_as_fork(repo_name, fork_id,
474 repo = ScmModel().mark_as_fork(repo_name, fork_id,
555 c.rhodecode_user.username)
475 c.rhodecode_user.username)
556 fork = repo.fork.repo_name if repo.fork else _('Nothing')
476 fork = repo.fork.repo_name if repo.fork else _('Nothing')
557 Session().commit()
477 Session().commit()
558 h.flash(_('Marked repo %s as fork of %s') % (repo_name, fork),
478 h.flash(_('Marked repo %s as fork of %s') % (repo_name, fork),
559 category='success')
479 category='success')
560 except RepositoryError as e:
480 except RepositoryError as e:
561 log.exception("Repository Error occurred")
481 log.exception("Repository Error occurred")
562 h.flash(str(e), category='error')
482 h.flash(str(e), category='error')
563 except Exception as e:
483 except Exception as e:
564 log.exception("Exception while editing fork")
484 log.exception("Exception while editing fork")
565 h.flash(_('An error occurred during this operation'),
485 h.flash(_('An error occurred during this operation'),
566 category='error')
486 category='error')
567
487
568 return redirect(url('edit_repo_advanced', repo_name=repo_name))
488 return redirect(url('edit_repo_advanced', repo_name=repo_name))
569
489
570 @HasRepoPermissionAllDecorator('repository.admin')
490 @HasRepoPermissionAllDecorator('repository.admin')
571 @auth.CSRFRequired()
491 @auth.CSRFRequired()
572 def edit_advanced_locking(self, repo_name):
492 def edit_advanced_locking(self, repo_name):
573 """
493 """
574 Unlock repository when it is locked !
494 Unlock repository when it is locked !
575
495
576 :param repo_name:
496 :param repo_name:
577 """
497 """
578 try:
498 try:
579 repo = Repository.get_by_repo_name(repo_name)
499 repo = Repository.get_by_repo_name(repo_name)
580 if request.POST.get('set_lock'):
500 if request.POST.get('set_lock'):
581 Repository.lock(repo, c.rhodecode_user.user_id,
501 Repository.lock(repo, c.rhodecode_user.user_id,
582 lock_reason=Repository.LOCK_WEB)
502 lock_reason=Repository.LOCK_WEB)
583 h.flash(_('Locked repository'), category='success')
503 h.flash(_('Locked repository'), category='success')
584 elif request.POST.get('set_unlock'):
504 elif request.POST.get('set_unlock'):
585 Repository.unlock(repo)
505 Repository.unlock(repo)
586 h.flash(_('Unlocked repository'), category='success')
506 h.flash(_('Unlocked repository'), category='success')
587 except Exception as e:
507 except Exception as e:
588 log.exception("Exception during unlocking")
508 log.exception("Exception during unlocking")
589 h.flash(_('An error occurred during unlocking'),
509 h.flash(_('An error occurred during unlocking'),
590 category='error')
510 category='error')
591 return redirect(url('edit_repo_advanced', repo_name=repo_name))
511 return redirect(url('edit_repo_advanced', repo_name=repo_name))
592
512
593 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
513 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
594 @auth.CSRFRequired()
514 @auth.CSRFRequired()
595 def toggle_locking(self, repo_name):
515 def toggle_locking(self, repo_name):
596 """
516 """
597 Toggle locking of repository by simple GET call to url
517 Toggle locking of repository by simple GET call to url
598
518
599 :param repo_name:
519 :param repo_name:
600 """
520 """
601
521
602 try:
522 try:
603 repo = Repository.get_by_repo_name(repo_name)
523 repo = Repository.get_by_repo_name(repo_name)
604
524
605 if repo.enable_locking:
525 if repo.enable_locking:
606 if repo.locked[0]:
526 if repo.locked[0]:
607 Repository.unlock(repo)
527 Repository.unlock(repo)
608 action = _('Unlocked')
528 action = _('Unlocked')
609 else:
529 else:
610 Repository.lock(repo, c.rhodecode_user.user_id,
530 Repository.lock(repo, c.rhodecode_user.user_id,
611 lock_reason=Repository.LOCK_WEB)
531 lock_reason=Repository.LOCK_WEB)
612 action = _('Locked')
532 action = _('Locked')
613
533
614 h.flash(_('Repository has been %s') % action,
534 h.flash(_('Repository has been %s') % action,
615 category='success')
535 category='success')
616 except Exception:
536 except Exception:
617 log.exception("Exception during unlocking")
537 log.exception("Exception during unlocking")
618 h.flash(_('An error occurred during unlocking'),
538 h.flash(_('An error occurred during unlocking'),
619 category='error')
539 category='error')
620 return redirect(url('summary_home', repo_name=repo_name))
540 return redirect(url('summary_home', repo_name=repo_name))
621
541
622 @HasRepoPermissionAllDecorator('repository.admin')
542 @HasRepoPermissionAllDecorator('repository.admin')
623 @auth.CSRFRequired()
543 @auth.CSRFRequired()
624 def edit_caches(self, repo_name):
544 def edit_caches(self, repo_name):
625 """PUT /{repo_name}/settings/caches: invalidate the repo caches."""
545 """PUT /{repo_name}/settings/caches: invalidate the repo caches."""
626 try:
546 try:
627 ScmModel().mark_for_invalidation(repo_name, delete=True)
547 ScmModel().mark_for_invalidation(repo_name, delete=True)
628 Session().commit()
548 Session().commit()
629 h.flash(_('Cache invalidation successful'),
549 h.flash(_('Cache invalidation successful'),
630 category='success')
550 category='success')
631 except Exception:
551 except Exception:
632 log.exception("Exception during cache invalidation")
552 log.exception("Exception during cache invalidation")
633 h.flash(_('An error occurred during cache invalidation'),
553 h.flash(_('An error occurred during cache invalidation'),
634 category='error')
554 category='error')
635
555
636 return redirect(url('edit_repo_caches', repo_name=c.repo_name))
556 return redirect(url('edit_repo_caches', repo_name=c.repo_name))
637
557
638 @HasRepoPermissionAllDecorator('repository.admin')
558 @HasRepoPermissionAllDecorator('repository.admin')
639 def edit_caches_form(self, repo_name):
559 def edit_caches_form(self, repo_name):
640 """GET /repo_name/settings: Form to edit an existing item"""
560 """GET /repo_name/settings: Form to edit an existing item"""
641 # url('edit_repo', repo_name=ID)
642 c.repo_info = self._load_repo(repo_name)
561 c.repo_info = self._load_repo(repo_name)
643 c.active = 'caches'
562 c.active = 'caches'
644
563
645 return render('admin/repos/repo_edit.mako')
564 return render('admin/repos/repo_edit.mako')
646
565
647 @HasRepoPermissionAllDecorator('repository.admin')
566 @HasRepoPermissionAllDecorator('repository.admin')
648 @auth.CSRFRequired()
567 @auth.CSRFRequired()
649 def edit_remote(self, repo_name):
568 def edit_remote(self, repo_name):
650 """PUT /{repo_name}/settings/remote: edit the repo remote."""
569 """PUT /{repo_name}/settings/remote: edit the repo remote."""
651 try:
570 try:
652 ScmModel().pull_changes(repo_name, c.rhodecode_user.username)
571 ScmModel().pull_changes(repo_name, c.rhodecode_user.username)
653 h.flash(_('Pulled from remote location'), category='success')
572 h.flash(_('Pulled from remote location'), category='success')
654 except Exception:
573 except Exception:
655 log.exception("Exception during pull from remote")
574 log.exception("Exception during pull from remote")
656 h.flash(_('An error occurred during pull from remote location'),
575 h.flash(_('An error occurred during pull from remote location'),
657 category='error')
576 category='error')
658 return redirect(url('edit_repo_remote', repo_name=c.repo_name))
577 return redirect(url('edit_repo_remote', repo_name=c.repo_name))
659
578
660 @HasRepoPermissionAllDecorator('repository.admin')
579 @HasRepoPermissionAllDecorator('repository.admin')
661 def edit_remote_form(self, repo_name):
580 def edit_remote_form(self, repo_name):
662 """GET /repo_name/settings: Form to edit an existing item"""
581 """GET /repo_name/settings: Form to edit an existing item"""
663 # url('edit_repo', repo_name=ID)
664 c.repo_info = self._load_repo(repo_name)
582 c.repo_info = self._load_repo(repo_name)
665 c.active = 'remote'
583 c.active = 'remote'
666
584
667 return render('admin/repos/repo_edit.mako')
585 return render('admin/repos/repo_edit.mako')
668
586
669 @HasRepoPermissionAllDecorator('repository.admin')
587 @HasRepoPermissionAllDecorator('repository.admin')
670 @auth.CSRFRequired()
588 @auth.CSRFRequired()
671 def edit_statistics(self, repo_name):
589 def edit_statistics(self, repo_name):
672 """PUT /{repo_name}/settings/statistics: reset the repo statistics."""
590 """PUT /{repo_name}/settings/statistics: reset the repo statistics."""
673 try:
591 try:
674 RepoModel().delete_stats(repo_name)
592 RepoModel().delete_stats(repo_name)
675 Session().commit()
593 Session().commit()
676 except Exception as e:
594 except Exception as e:
677 log.error(traceback.format_exc())
595 log.error(traceback.format_exc())
678 h.flash(_('An error occurred during deletion of repository stats'),
596 h.flash(_('An error occurred during deletion of repository stats'),
679 category='error')
597 category='error')
680 return redirect(url('edit_repo_statistics', repo_name=c.repo_name))
598 return redirect(url('edit_repo_statistics', repo_name=c.repo_name))
681
599
682 @HasRepoPermissionAllDecorator('repository.admin')
600 @HasRepoPermissionAllDecorator('repository.admin')
683 def edit_statistics_form(self, repo_name):
601 def edit_statistics_form(self, repo_name):
684 """GET /repo_name/settings: Form to edit an existing item"""
602 """GET /repo_name/settings: Form to edit an existing item"""
685 # url('edit_repo', repo_name=ID)
686 c.repo_info = self._load_repo(repo_name)
603 c.repo_info = self._load_repo(repo_name)
687 repo = c.repo_info.scm_instance()
604 repo = c.repo_info.scm_instance()
688
605
689 if c.repo_info.stats:
606 if c.repo_info.stats:
690 # this is on what revision we ended up so we add +1 for count
607 # this is on what revision we ended up so we add +1 for count
691 last_rev = c.repo_info.stats.stat_on_revision + 1
608 last_rev = c.repo_info.stats.stat_on_revision + 1
692 else:
609 else:
693 last_rev = 0
610 last_rev = 0
694 c.stats_revision = last_rev
611 c.stats_revision = last_rev
695
612
696 c.repo_last_rev = repo.count()
613 c.repo_last_rev = repo.count()
697
614
698 if last_rev == 0 or c.repo_last_rev == 0:
615 if last_rev == 0 or c.repo_last_rev == 0:
699 c.stats_percentage = 0
616 c.stats_percentage = 0
700 else:
617 else:
701 c.stats_percentage = '%.2f' % ((float((last_rev)) / c.repo_last_rev) * 100)
618 c.stats_percentage = '%.2f' % ((float((last_rev)) / c.repo_last_rev) * 100)
702
619
703 c.active = 'statistics'
620 c.active = 'statistics'
704
621
705 return render('admin/repos/repo_edit.mako')
622 return render('admin/repos/repo_edit.mako')
706
623
707 @HasRepoPermissionAllDecorator('repository.admin')
624 @HasRepoPermissionAllDecorator('repository.admin')
708 @auth.CSRFRequired()
625 @auth.CSRFRequired()
709 def repo_issuetracker_test(self, repo_name):
626 def repo_issuetracker_test(self, repo_name):
710 if request.is_xhr:
627 if request.is_xhr:
711 return h.urlify_commit_message(
628 return h.urlify_commit_message(
712 request.POST.get('test_text', ''),
629 request.POST.get('test_text', ''),
713 repo_name)
630 repo_name)
714 else:
631 else:
715 raise HTTPBadRequest()
632 raise HTTPBadRequest()
716
633
717 @HasRepoPermissionAllDecorator('repository.admin')
634 @HasRepoPermissionAllDecorator('repository.admin')
718 @auth.CSRFRequired()
635 @auth.CSRFRequired()
719 def repo_issuetracker_delete(self, repo_name):
636 def repo_issuetracker_delete(self, repo_name):
720 uid = request.POST.get('uid')
637 uid = request.POST.get('uid')
721 repo_settings = IssueTrackerSettingsModel(repo=repo_name)
638 repo_settings = IssueTrackerSettingsModel(repo=repo_name)
722 try:
639 try:
723 repo_settings.delete_entries(uid)
640 repo_settings.delete_entries(uid)
724 except Exception:
641 except Exception:
725 h.flash(_('Error occurred during deleting issue tracker entry'),
642 h.flash(_('Error occurred during deleting issue tracker entry'),
726 category='error')
643 category='error')
727 else:
644 else:
728 h.flash(_('Removed issue tracker entry'), category='success')
645 h.flash(_('Removed issue tracker entry'), category='success')
729 return redirect(url('repo_settings_issuetracker',
646 return redirect(url('repo_settings_issuetracker',
730 repo_name=repo_name))
647 repo_name=repo_name))
731
648
732 def _update_patterns(self, form, repo_settings):
649 def _update_patterns(self, form, repo_settings):
733 for uid in form['delete_patterns']:
650 for uid in form['delete_patterns']:
734 repo_settings.delete_entries(uid)
651 repo_settings.delete_entries(uid)
735
652
736 for pattern in form['patterns']:
653 for pattern in form['patterns']:
737 for setting, value, type_ in pattern:
654 for setting, value, type_ in pattern:
738 sett = repo_settings.create_or_update_setting(
655 sett = repo_settings.create_or_update_setting(
739 setting, value, type_)
656 setting, value, type_)
740 Session().add(sett)
657 Session().add(sett)
741
658
742 Session().commit()
659 Session().commit()
743
660
744 @HasRepoPermissionAllDecorator('repository.admin')
661 @HasRepoPermissionAllDecorator('repository.admin')
745 @auth.CSRFRequired()
662 @auth.CSRFRequired()
746 def repo_issuetracker_save(self, repo_name):
663 def repo_issuetracker_save(self, repo_name):
747 # Save inheritance
664 # Save inheritance
748 repo_settings = IssueTrackerSettingsModel(repo=repo_name)
665 repo_settings = IssueTrackerSettingsModel(repo=repo_name)
749 inherited = (request.POST.get('inherit_global_issuetracker')
666 inherited = (request.POST.get('inherit_global_issuetracker')
750 == "inherited")
667 == "inherited")
751 repo_settings.inherit_global_settings = inherited
668 repo_settings.inherit_global_settings = inherited
752 Session().commit()
669 Session().commit()
753
670
754 form = IssueTrackerPatternsForm()().to_python(request.POST)
671 form = IssueTrackerPatternsForm()().to_python(request.POST)
755 if form:
672 if form:
756 self._update_patterns(form, repo_settings)
673 self._update_patterns(form, repo_settings)
757
674
758 h.flash(_('Updated issue tracker entries'), category='success')
675 h.flash(_('Updated issue tracker entries'), category='success')
759 return redirect(url('repo_settings_issuetracker',
676 return redirect(url('repo_settings_issuetracker',
760 repo_name=repo_name))
677 repo_name=repo_name))
761
678
762 @HasRepoPermissionAllDecorator('repository.admin')
679 @HasRepoPermissionAllDecorator('repository.admin')
763 def repo_issuetracker(self, repo_name):
680 def repo_issuetracker(self, repo_name):
764 """GET /admin/settings/issue-tracker: All items in the collection"""
681 """GET /admin/settings/issue-tracker: All items in the collection"""
765 c.active = 'issuetracker'
682 c.active = 'issuetracker'
766 c.data = 'data'
683 c.data = 'data'
767 c.repo_info = self._load_repo(repo_name)
684 c.repo_info = self._load_repo(repo_name)
768
685
769 repo = Repository.get_by_repo_name(repo_name)
686 repo = Repository.get_by_repo_name(repo_name)
770 c.settings_model = IssueTrackerSettingsModel(repo=repo)
687 c.settings_model = IssueTrackerSettingsModel(repo=repo)
771 c.global_patterns = c.settings_model.get_global_settings()
688 c.global_patterns = c.settings_model.get_global_settings()
772 c.repo_patterns = c.settings_model.get_repo_settings()
689 c.repo_patterns = c.settings_model.get_repo_settings()
773
690
774 return render('admin/repos/repo_edit.mako')
691 return render('admin/repos/repo_edit.mako')
775
692
776 @HasRepoPermissionAllDecorator('repository.admin')
693 @HasRepoPermissionAllDecorator('repository.admin')
777 def repo_settings_vcs(self, repo_name):
694 def repo_settings_vcs(self, repo_name):
778 """GET /{repo_name}/settings/vcs/: All items in the collection"""
695 """GET /{repo_name}/settings/vcs/: All items in the collection"""
779
696
780 model = VcsSettingsModel(repo=repo_name)
697 model = VcsSettingsModel(repo=repo_name)
781
698
782 c.active = 'vcs'
699 c.active = 'vcs'
783 c.global_svn_branch_patterns = model.get_global_svn_branch_patterns()
700 c.global_svn_branch_patterns = model.get_global_svn_branch_patterns()
784 c.global_svn_tag_patterns = model.get_global_svn_tag_patterns()
701 c.global_svn_tag_patterns = model.get_global_svn_tag_patterns()
785 c.svn_branch_patterns = model.get_repo_svn_branch_patterns()
702 c.svn_branch_patterns = model.get_repo_svn_branch_patterns()
786 c.svn_tag_patterns = model.get_repo_svn_tag_patterns()
703 c.svn_tag_patterns = model.get_repo_svn_tag_patterns()
787 c.repo_info = self._load_repo(repo_name)
704 c.repo_info = self._load_repo(repo_name)
788 defaults = self._vcs_form_defaults(repo_name)
705 defaults = self._vcs_form_defaults(repo_name)
789 c.inherit_global_settings = defaults['inherit_global_settings']
706 c.inherit_global_settings = defaults['inherit_global_settings']
790 c.labs_active = str2bool(
707 c.labs_active = str2bool(
791 rhodecode.CONFIG.get('labs_settings_active', 'true'))
708 rhodecode.CONFIG.get('labs_settings_active', 'true'))
792
709
793 return htmlfill.render(
710 return htmlfill.render(
794 render('admin/repos/repo_edit.mako'),
711 render('admin/repos/repo_edit.mako'),
795 defaults=defaults,
712 defaults=defaults,
796 encoding="UTF-8",
713 encoding="UTF-8",
797 force_defaults=False)
714 force_defaults=False)
798
715
799 @HasRepoPermissionAllDecorator('repository.admin')
716 @HasRepoPermissionAllDecorator('repository.admin')
800 @auth.CSRFRequired()
717 @auth.CSRFRequired()
801 def repo_settings_vcs_update(self, repo_name):
718 def repo_settings_vcs_update(self, repo_name):
802 """POST /{repo_name}/settings/vcs/: All items in the collection"""
719 """POST /{repo_name}/settings/vcs/: All items in the collection"""
803 c.active = 'vcs'
720 c.active = 'vcs'
804
721
805 model = VcsSettingsModel(repo=repo_name)
722 model = VcsSettingsModel(repo=repo_name)
806 c.global_svn_branch_patterns = model.get_global_svn_branch_patterns()
723 c.global_svn_branch_patterns = model.get_global_svn_branch_patterns()
807 c.global_svn_tag_patterns = model.get_global_svn_tag_patterns()
724 c.global_svn_tag_patterns = model.get_global_svn_tag_patterns()
808 c.svn_branch_patterns = model.get_repo_svn_branch_patterns()
725 c.svn_branch_patterns = model.get_repo_svn_branch_patterns()
809 c.svn_tag_patterns = model.get_repo_svn_tag_patterns()
726 c.svn_tag_patterns = model.get_repo_svn_tag_patterns()
810 c.repo_info = self._load_repo(repo_name)
727 c.repo_info = self._load_repo(repo_name)
811 defaults = self._vcs_form_defaults(repo_name)
728 defaults = self._vcs_form_defaults(repo_name)
812 c.inherit_global_settings = defaults['inherit_global_settings']
729 c.inherit_global_settings = defaults['inherit_global_settings']
813
730
814 application_form = RepoVcsSettingsForm(repo_name)()
731 application_form = RepoVcsSettingsForm(repo_name)()
815 try:
732 try:
816 form_result = application_form.to_python(dict(request.POST))
733 form_result = application_form.to_python(dict(request.POST))
817 except formencode.Invalid as errors:
734 except formencode.Invalid as errors:
818 h.flash(
735 h.flash(
819 _("Some form inputs contain invalid data."),
736 _("Some form inputs contain invalid data."),
820 category='error')
737 category='error')
821 return htmlfill.render(
738 return htmlfill.render(
822 render('admin/repos/repo_edit.mako'),
739 render('admin/repos/repo_edit.mako'),
823 defaults=errors.value,
740 defaults=errors.value,
824 errors=errors.error_dict or {},
741 errors=errors.error_dict or {},
825 prefix_error=False,
742 prefix_error=False,
826 encoding="UTF-8",
743 encoding="UTF-8",
827 force_defaults=False
744 force_defaults=False
828 )
745 )
829
746
830 try:
747 try:
831 inherit_global_settings = form_result['inherit_global_settings']
748 inherit_global_settings = form_result['inherit_global_settings']
832 model.create_or_update_repo_settings(
749 model.create_or_update_repo_settings(
833 form_result, inherit_global_settings=inherit_global_settings)
750 form_result, inherit_global_settings=inherit_global_settings)
834 except Exception:
751 except Exception:
835 log.exception("Exception while updating settings")
752 log.exception("Exception while updating settings")
836 h.flash(
753 h.flash(
837 _('Error occurred during updating repository VCS settings'),
754 _('Error occurred during updating repository VCS settings'),
838 category='error')
755 category='error')
839 else:
756 else:
840 Session().commit()
757 Session().commit()
841 h.flash(_('Updated VCS settings'), category='success')
758 h.flash(_('Updated VCS settings'), category='success')
842 return redirect(url('repo_vcs_settings', repo_name=repo_name))
759 return redirect(url('repo_vcs_settings', repo_name=repo_name))
843
760
844 return htmlfill.render(
761 return htmlfill.render(
845 render('admin/repos/repo_edit.mako'),
762 render('admin/repos/repo_edit.mako'),
846 defaults=self._vcs_form_defaults(repo_name),
763 defaults=self._vcs_form_defaults(repo_name),
847 encoding="UTF-8",
764 encoding="UTF-8",
848 force_defaults=False)
765 force_defaults=False)
849
766
850 @HasRepoPermissionAllDecorator('repository.admin')
767 @HasRepoPermissionAllDecorator('repository.admin')
851 @auth.CSRFRequired()
768 @auth.CSRFRequired()
852 @jsonify
769 @jsonify
853 def repo_delete_svn_pattern(self, repo_name):
770 def repo_delete_svn_pattern(self, repo_name):
854 if not request.is_xhr:
771 if not request.is_xhr:
855 return False
772 return False
856
773
857 delete_pattern_id = request.POST.get('delete_svn_pattern')
774 delete_pattern_id = request.POST.get('delete_svn_pattern')
858 model = VcsSettingsModel(repo=repo_name)
775 model = VcsSettingsModel(repo=repo_name)
859 try:
776 try:
860 model.delete_repo_svn_pattern(delete_pattern_id)
777 model.delete_repo_svn_pattern(delete_pattern_id)
861 except SettingNotFound:
778 except SettingNotFound:
862 raise HTTPBadRequest()
779 raise HTTPBadRequest()
863
780
864 Session().commit()
781 Session().commit()
865 return True
782 return True
866
783
867 def _vcs_form_defaults(self, repo_name):
784 def _vcs_form_defaults(self, repo_name):
868 model = VcsSettingsModel(repo=repo_name)
785 model = VcsSettingsModel(repo=repo_name)
869 global_defaults = model.get_global_settings()
786 global_defaults = model.get_global_settings()
870
787
871 repo_defaults = {}
788 repo_defaults = {}
872 repo_defaults.update(global_defaults)
789 repo_defaults.update(global_defaults)
873 repo_defaults.update(model.get_repo_settings())
790 repo_defaults.update(model.get_repo_settings())
874
791
875 global_defaults = {
792 global_defaults = {
876 '{}_inherited'.format(k): global_defaults[k]
793 '{}_inherited'.format(k): global_defaults[k]
877 for k in global_defaults}
794 for k in global_defaults}
878
795
879 defaults = {
796 defaults = {
880 'inherit_global_settings': model.inherit_global_settings
797 'inherit_global_settings': model.inherit_global_settings
881 }
798 }
882 defaults.update(global_defaults)
799 defaults.update(global_defaults)
883 defaults.update(repo_defaults)
800 defaults.update(repo_defaults)
884 defaults.update({
801 defaults.update({
885 'new_svn_branch': '',
802 'new_svn_branch': '',
886 'new_svn_tag': '',
803 'new_svn_tag': '',
887 })
804 })
888 return defaults
805 return defaults
@@ -1,3986 +1,3986 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 zope.cachedescriptors.property import Lazy as LazyProperty
45 from zope.cachedescriptors.property import Lazy as LazyProperty
46
46
47 from pylons import url
47 from pylons import url
48 from pylons.i18n.translation import lazy_ugettext as _
48 from pylons.i18n.translation import lazy_ugettext as _
49
49
50 from rhodecode.lib.vcs import get_vcs_instance
50 from rhodecode.lib.vcs import get_vcs_instance
51 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
51 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
52 from rhodecode.lib.utils2 import (
52 from rhodecode.lib.utils2 import (
53 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
53 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
54 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
54 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
55 glob2re, StrictAttributeDict, cleaned_uri)
55 glob2re, StrictAttributeDict, cleaned_uri)
56 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType
56 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType
57 from rhodecode.lib.ext_json import json
57 from rhodecode.lib.ext_json import json
58 from rhodecode.lib.caching_query import FromCache
58 from rhodecode.lib.caching_query import FromCache
59 from rhodecode.lib.encrypt import AESCipher
59 from rhodecode.lib.encrypt import AESCipher
60
60
61 from rhodecode.model.meta import Base, Session
61 from rhodecode.model.meta import Base, Session
62
62
63 URL_SEP = '/'
63 URL_SEP = '/'
64 log = logging.getLogger(__name__)
64 log = logging.getLogger(__name__)
65
65
66 # =============================================================================
66 # =============================================================================
67 # BASE CLASSES
67 # BASE CLASSES
68 # =============================================================================
68 # =============================================================================
69
69
70 # this is propagated from .ini file rhodecode.encrypted_values.secret or
70 # this is propagated from .ini file rhodecode.encrypted_values.secret or
71 # beaker.session.secret if first is not set.
71 # beaker.session.secret if first is not set.
72 # and initialized at environment.py
72 # and initialized at environment.py
73 ENCRYPTION_KEY = None
73 ENCRYPTION_KEY = None
74
74
75 # used to sort permissions by types, '#' used here is not allowed to be in
75 # used to sort permissions by types, '#' used here is not allowed to be in
76 # usernames, and it's very early in sorted string.printable table.
76 # usernames, and it's very early in sorted string.printable table.
77 PERMISSION_TYPE_SORT = {
77 PERMISSION_TYPE_SORT = {
78 'admin': '####',
78 'admin': '####',
79 'write': '###',
79 'write': '###',
80 'read': '##',
80 'read': '##',
81 'none': '#',
81 'none': '#',
82 }
82 }
83
83
84
84
85 def display_sort(obj):
85 def display_sort(obj):
86 """
86 """
87 Sort function used to sort permissions in .permissions() function of
87 Sort function used to sort permissions in .permissions() function of
88 Repository, RepoGroup, UserGroup. Also it put the default user in front
88 Repository, RepoGroup, UserGroup. Also it put the default user in front
89 of all other resources
89 of all other resources
90 """
90 """
91
91
92 if obj.username == User.DEFAULT_USER:
92 if obj.username == User.DEFAULT_USER:
93 return '#####'
93 return '#####'
94 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
94 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
95 return prefix + obj.username
95 return prefix + obj.username
96
96
97
97
98 def _hash_key(k):
98 def _hash_key(k):
99 return md5_safe(k)
99 return md5_safe(k)
100
100
101
101
102 class EncryptedTextValue(TypeDecorator):
102 class EncryptedTextValue(TypeDecorator):
103 """
103 """
104 Special column for encrypted long text data, use like::
104 Special column for encrypted long text data, use like::
105
105
106 value = Column("encrypted_value", EncryptedValue(), nullable=False)
106 value = Column("encrypted_value", EncryptedValue(), nullable=False)
107
107
108 This column is intelligent so if value is in unencrypted form it return
108 This column is intelligent so if value is in unencrypted form it return
109 unencrypted form, but on save it always encrypts
109 unencrypted form, but on save it always encrypts
110 """
110 """
111 impl = Text
111 impl = Text
112
112
113 def process_bind_param(self, value, dialect):
113 def process_bind_param(self, value, dialect):
114 if not value:
114 if not value:
115 return value
115 return value
116 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
116 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
117 # protect against double encrypting if someone manually starts
117 # protect against double encrypting if someone manually starts
118 # doing
118 # doing
119 raise ValueError('value needs to be in unencrypted format, ie. '
119 raise ValueError('value needs to be in unencrypted format, ie. '
120 'not starting with enc$aes')
120 'not starting with enc$aes')
121 return 'enc$aes_hmac$%s' % AESCipher(
121 return 'enc$aes_hmac$%s' % AESCipher(
122 ENCRYPTION_KEY, hmac=True).encrypt(value)
122 ENCRYPTION_KEY, hmac=True).encrypt(value)
123
123
124 def process_result_value(self, value, dialect):
124 def process_result_value(self, value, dialect):
125 import rhodecode
125 import rhodecode
126
126
127 if not value:
127 if not value:
128 return value
128 return value
129
129
130 parts = value.split('$', 3)
130 parts = value.split('$', 3)
131 if not len(parts) == 3:
131 if not len(parts) == 3:
132 # probably not encrypted values
132 # probably not encrypted values
133 return value
133 return value
134 else:
134 else:
135 if parts[0] != 'enc':
135 if parts[0] != 'enc':
136 # parts ok but without our header ?
136 # parts ok but without our header ?
137 return value
137 return value
138 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
138 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
139 'rhodecode.encrypted_values.strict') or True)
139 'rhodecode.encrypted_values.strict') or True)
140 # at that stage we know it's our encryption
140 # at that stage we know it's our encryption
141 if parts[1] == 'aes':
141 if parts[1] == 'aes':
142 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
142 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
143 elif parts[1] == 'aes_hmac':
143 elif parts[1] == 'aes_hmac':
144 decrypted_data = AESCipher(
144 decrypted_data = AESCipher(
145 ENCRYPTION_KEY, hmac=True,
145 ENCRYPTION_KEY, hmac=True,
146 strict_verification=enc_strict_mode).decrypt(parts[2])
146 strict_verification=enc_strict_mode).decrypt(parts[2])
147 else:
147 else:
148 raise ValueError(
148 raise ValueError(
149 'Encryption type part is wrong, must be `aes` '
149 'Encryption type part is wrong, must be `aes` '
150 'or `aes_hmac`, got `%s` instead' % (parts[1]))
150 'or `aes_hmac`, got `%s` instead' % (parts[1]))
151 return decrypted_data
151 return decrypted_data
152
152
153
153
154 class BaseModel(object):
154 class BaseModel(object):
155 """
155 """
156 Base Model for all classes
156 Base Model for all classes
157 """
157 """
158
158
159 @classmethod
159 @classmethod
160 def _get_keys(cls):
160 def _get_keys(cls):
161 """return column names for this model """
161 """return column names for this model """
162 return class_mapper(cls).c.keys()
162 return class_mapper(cls).c.keys()
163
163
164 def get_dict(self):
164 def get_dict(self):
165 """
165 """
166 return dict with keys and values corresponding
166 return dict with keys and values corresponding
167 to this model data """
167 to this model data """
168
168
169 d = {}
169 d = {}
170 for k in self._get_keys():
170 for k in self._get_keys():
171 d[k] = getattr(self, k)
171 d[k] = getattr(self, k)
172
172
173 # also use __json__() if present to get additional fields
173 # also use __json__() if present to get additional fields
174 _json_attr = getattr(self, '__json__', None)
174 _json_attr = getattr(self, '__json__', None)
175 if _json_attr:
175 if _json_attr:
176 # update with attributes from __json__
176 # update with attributes from __json__
177 if callable(_json_attr):
177 if callable(_json_attr):
178 _json_attr = _json_attr()
178 _json_attr = _json_attr()
179 for k, val in _json_attr.iteritems():
179 for k, val in _json_attr.iteritems():
180 d[k] = val
180 d[k] = val
181 return d
181 return d
182
182
183 def get_appstruct(self):
183 def get_appstruct(self):
184 """return list with keys and values tuples corresponding
184 """return list with keys and values tuples corresponding
185 to this model data """
185 to this model data """
186
186
187 l = []
187 l = []
188 for k in self._get_keys():
188 for k in self._get_keys():
189 l.append((k, getattr(self, k),))
189 l.append((k, getattr(self, k),))
190 return l
190 return l
191
191
192 def populate_obj(self, populate_dict):
192 def populate_obj(self, populate_dict):
193 """populate model with data from given populate_dict"""
193 """populate model with data from given populate_dict"""
194
194
195 for k in self._get_keys():
195 for k in self._get_keys():
196 if k in populate_dict:
196 if k in populate_dict:
197 setattr(self, k, populate_dict[k])
197 setattr(self, k, populate_dict[k])
198
198
199 @classmethod
199 @classmethod
200 def query(cls):
200 def query(cls):
201 return Session().query(cls)
201 return Session().query(cls)
202
202
203 @classmethod
203 @classmethod
204 def get(cls, id_):
204 def get(cls, id_):
205 if id_:
205 if id_:
206 return cls.query().get(id_)
206 return cls.query().get(id_)
207
207
208 @classmethod
208 @classmethod
209 def get_or_404(cls, id_, pyramid_exc=False):
209 def get_or_404(cls, id_, pyramid_exc=False):
210 if pyramid_exc:
210 if pyramid_exc:
211 # NOTE(marcink): backward compat, once migration to pyramid
211 # NOTE(marcink): backward compat, once migration to pyramid
212 # this should only use pyramid exceptions
212 # this should only use pyramid exceptions
213 from pyramid.httpexceptions import HTTPNotFound
213 from pyramid.httpexceptions import HTTPNotFound
214 else:
214 else:
215 from webob.exc import HTTPNotFound
215 from webob.exc import HTTPNotFound
216
216
217 try:
217 try:
218 id_ = int(id_)
218 id_ = int(id_)
219 except (TypeError, ValueError):
219 except (TypeError, ValueError):
220 raise HTTPNotFound
220 raise HTTPNotFound
221
221
222 res = cls.query().get(id_)
222 res = cls.query().get(id_)
223 if not res:
223 if not res:
224 raise HTTPNotFound
224 raise HTTPNotFound
225 return res
225 return res
226
226
227 @classmethod
227 @classmethod
228 def getAll(cls):
228 def getAll(cls):
229 # deprecated and left for backward compatibility
229 # deprecated and left for backward compatibility
230 return cls.get_all()
230 return cls.get_all()
231
231
232 @classmethod
232 @classmethod
233 def get_all(cls):
233 def get_all(cls):
234 return cls.query().all()
234 return cls.query().all()
235
235
236 @classmethod
236 @classmethod
237 def delete(cls, id_):
237 def delete(cls, id_):
238 obj = cls.query().get(id_)
238 obj = cls.query().get(id_)
239 Session().delete(obj)
239 Session().delete(obj)
240
240
241 @classmethod
241 @classmethod
242 def identity_cache(cls, session, attr_name, value):
242 def identity_cache(cls, session, attr_name, value):
243 exist_in_session = []
243 exist_in_session = []
244 for (item_cls, pkey), instance in session.identity_map.items():
244 for (item_cls, pkey), instance in session.identity_map.items():
245 if cls == item_cls and getattr(instance, attr_name) == value:
245 if cls == item_cls and getattr(instance, attr_name) == value:
246 exist_in_session.append(instance)
246 exist_in_session.append(instance)
247 if exist_in_session:
247 if exist_in_session:
248 if len(exist_in_session) == 1:
248 if len(exist_in_session) == 1:
249 return exist_in_session[0]
249 return exist_in_session[0]
250 log.exception(
250 log.exception(
251 'multiple objects with attr %s and '
251 'multiple objects with attr %s and '
252 'value %s found with same name: %r',
252 'value %s found with same name: %r',
253 attr_name, value, exist_in_session)
253 attr_name, value, exist_in_session)
254
254
255 def __repr__(self):
255 def __repr__(self):
256 if hasattr(self, '__unicode__'):
256 if hasattr(self, '__unicode__'):
257 # python repr needs to return str
257 # python repr needs to return str
258 try:
258 try:
259 return safe_str(self.__unicode__())
259 return safe_str(self.__unicode__())
260 except UnicodeDecodeError:
260 except UnicodeDecodeError:
261 pass
261 pass
262 return '<DB:%s>' % (self.__class__.__name__)
262 return '<DB:%s>' % (self.__class__.__name__)
263
263
264
264
265 class RhodeCodeSetting(Base, BaseModel):
265 class RhodeCodeSetting(Base, BaseModel):
266 __tablename__ = 'rhodecode_settings'
266 __tablename__ = 'rhodecode_settings'
267 __table_args__ = (
267 __table_args__ = (
268 UniqueConstraint('app_settings_name'),
268 UniqueConstraint('app_settings_name'),
269 {'extend_existing': True, 'mysql_engine': 'InnoDB',
269 {'extend_existing': True, 'mysql_engine': 'InnoDB',
270 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
270 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
271 )
271 )
272
272
273 SETTINGS_TYPES = {
273 SETTINGS_TYPES = {
274 'str': safe_str,
274 'str': safe_str,
275 'int': safe_int,
275 'int': safe_int,
276 'unicode': safe_unicode,
276 'unicode': safe_unicode,
277 'bool': str2bool,
277 'bool': str2bool,
278 'list': functools.partial(aslist, sep=',')
278 'list': functools.partial(aslist, sep=',')
279 }
279 }
280 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
280 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
281 GLOBAL_CONF_KEY = 'app_settings'
281 GLOBAL_CONF_KEY = 'app_settings'
282
282
283 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
283 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
284 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
284 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
285 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
285 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
286 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
286 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
287
287
288 def __init__(self, key='', val='', type='unicode'):
288 def __init__(self, key='', val='', type='unicode'):
289 self.app_settings_name = key
289 self.app_settings_name = key
290 self.app_settings_type = type
290 self.app_settings_type = type
291 self.app_settings_value = val
291 self.app_settings_value = val
292
292
293 @validates('_app_settings_value')
293 @validates('_app_settings_value')
294 def validate_settings_value(self, key, val):
294 def validate_settings_value(self, key, val):
295 assert type(val) == unicode
295 assert type(val) == unicode
296 return val
296 return val
297
297
298 @hybrid_property
298 @hybrid_property
299 def app_settings_value(self):
299 def app_settings_value(self):
300 v = self._app_settings_value
300 v = self._app_settings_value
301 _type = self.app_settings_type
301 _type = self.app_settings_type
302 if _type:
302 if _type:
303 _type = self.app_settings_type.split('.')[0]
303 _type = self.app_settings_type.split('.')[0]
304 # decode the encrypted value
304 # decode the encrypted value
305 if 'encrypted' in self.app_settings_type:
305 if 'encrypted' in self.app_settings_type:
306 cipher = EncryptedTextValue()
306 cipher = EncryptedTextValue()
307 v = safe_unicode(cipher.process_result_value(v, None))
307 v = safe_unicode(cipher.process_result_value(v, None))
308
308
309 converter = self.SETTINGS_TYPES.get(_type) or \
309 converter = self.SETTINGS_TYPES.get(_type) or \
310 self.SETTINGS_TYPES['unicode']
310 self.SETTINGS_TYPES['unicode']
311 return converter(v)
311 return converter(v)
312
312
313 @app_settings_value.setter
313 @app_settings_value.setter
314 def app_settings_value(self, val):
314 def app_settings_value(self, val):
315 """
315 """
316 Setter that will always make sure we use unicode in app_settings_value
316 Setter that will always make sure we use unicode in app_settings_value
317
317
318 :param val:
318 :param val:
319 """
319 """
320 val = safe_unicode(val)
320 val = safe_unicode(val)
321 # encode the encrypted value
321 # encode the encrypted value
322 if 'encrypted' in self.app_settings_type:
322 if 'encrypted' in self.app_settings_type:
323 cipher = EncryptedTextValue()
323 cipher = EncryptedTextValue()
324 val = safe_unicode(cipher.process_bind_param(val, None))
324 val = safe_unicode(cipher.process_bind_param(val, None))
325 self._app_settings_value = val
325 self._app_settings_value = val
326
326
327 @hybrid_property
327 @hybrid_property
328 def app_settings_type(self):
328 def app_settings_type(self):
329 return self._app_settings_type
329 return self._app_settings_type
330
330
331 @app_settings_type.setter
331 @app_settings_type.setter
332 def app_settings_type(self, val):
332 def app_settings_type(self, val):
333 if val.split('.')[0] not in self.SETTINGS_TYPES:
333 if val.split('.')[0] not in self.SETTINGS_TYPES:
334 raise Exception('type must be one of %s got %s'
334 raise Exception('type must be one of %s got %s'
335 % (self.SETTINGS_TYPES.keys(), val))
335 % (self.SETTINGS_TYPES.keys(), val))
336 self._app_settings_type = val
336 self._app_settings_type = val
337
337
338 def __unicode__(self):
338 def __unicode__(self):
339 return u"<%s('%s:%s[%s]')>" % (
339 return u"<%s('%s:%s[%s]')>" % (
340 self.__class__.__name__,
340 self.__class__.__name__,
341 self.app_settings_name, self.app_settings_value,
341 self.app_settings_name, self.app_settings_value,
342 self.app_settings_type
342 self.app_settings_type
343 )
343 )
344
344
345
345
346 class RhodeCodeUi(Base, BaseModel):
346 class RhodeCodeUi(Base, BaseModel):
347 __tablename__ = 'rhodecode_ui'
347 __tablename__ = 'rhodecode_ui'
348 __table_args__ = (
348 __table_args__ = (
349 UniqueConstraint('ui_key'),
349 UniqueConstraint('ui_key'),
350 {'extend_existing': True, 'mysql_engine': 'InnoDB',
350 {'extend_existing': True, 'mysql_engine': 'InnoDB',
351 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
351 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
352 )
352 )
353
353
354 HOOK_REPO_SIZE = 'changegroup.repo_size'
354 HOOK_REPO_SIZE = 'changegroup.repo_size'
355 # HG
355 # HG
356 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
356 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
357 HOOK_PULL = 'outgoing.pull_logger'
357 HOOK_PULL = 'outgoing.pull_logger'
358 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
358 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
359 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
359 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
360 HOOK_PUSH = 'changegroup.push_logger'
360 HOOK_PUSH = 'changegroup.push_logger'
361
361
362 # TODO: johbo: Unify way how hooks are configured for git and hg,
362 # TODO: johbo: Unify way how hooks are configured for git and hg,
363 # git part is currently hardcoded.
363 # git part is currently hardcoded.
364
364
365 # SVN PATTERNS
365 # SVN PATTERNS
366 SVN_BRANCH_ID = 'vcs_svn_branch'
366 SVN_BRANCH_ID = 'vcs_svn_branch'
367 SVN_TAG_ID = 'vcs_svn_tag'
367 SVN_TAG_ID = 'vcs_svn_tag'
368
368
369 ui_id = Column(
369 ui_id = Column(
370 "ui_id", Integer(), nullable=False, unique=True, default=None,
370 "ui_id", Integer(), nullable=False, unique=True, default=None,
371 primary_key=True)
371 primary_key=True)
372 ui_section = Column(
372 ui_section = Column(
373 "ui_section", String(255), nullable=True, unique=None, default=None)
373 "ui_section", String(255), nullable=True, unique=None, default=None)
374 ui_key = Column(
374 ui_key = Column(
375 "ui_key", String(255), nullable=True, unique=None, default=None)
375 "ui_key", String(255), nullable=True, unique=None, default=None)
376 ui_value = Column(
376 ui_value = Column(
377 "ui_value", String(255), nullable=True, unique=None, default=None)
377 "ui_value", String(255), nullable=True, unique=None, default=None)
378 ui_active = Column(
378 ui_active = Column(
379 "ui_active", Boolean(), nullable=True, unique=None, default=True)
379 "ui_active", Boolean(), nullable=True, unique=None, default=True)
380
380
381 def __repr__(self):
381 def __repr__(self):
382 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
382 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
383 self.ui_key, self.ui_value)
383 self.ui_key, self.ui_value)
384
384
385
385
386 class RepoRhodeCodeSetting(Base, BaseModel):
386 class RepoRhodeCodeSetting(Base, BaseModel):
387 __tablename__ = 'repo_rhodecode_settings'
387 __tablename__ = 'repo_rhodecode_settings'
388 __table_args__ = (
388 __table_args__ = (
389 UniqueConstraint(
389 UniqueConstraint(
390 'app_settings_name', 'repository_id',
390 'app_settings_name', 'repository_id',
391 name='uq_repo_rhodecode_setting_name_repo_id'),
391 name='uq_repo_rhodecode_setting_name_repo_id'),
392 {'extend_existing': True, 'mysql_engine': 'InnoDB',
392 {'extend_existing': True, 'mysql_engine': 'InnoDB',
393 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
393 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
394 )
394 )
395
395
396 repository_id = Column(
396 repository_id = Column(
397 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
397 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
398 nullable=False)
398 nullable=False)
399 app_settings_id = Column(
399 app_settings_id = Column(
400 "app_settings_id", Integer(), nullable=False, unique=True,
400 "app_settings_id", Integer(), nullable=False, unique=True,
401 default=None, primary_key=True)
401 default=None, primary_key=True)
402 app_settings_name = Column(
402 app_settings_name = Column(
403 "app_settings_name", String(255), nullable=True, unique=None,
403 "app_settings_name", String(255), nullable=True, unique=None,
404 default=None)
404 default=None)
405 _app_settings_value = Column(
405 _app_settings_value = Column(
406 "app_settings_value", String(4096), nullable=True, unique=None,
406 "app_settings_value", String(4096), nullable=True, unique=None,
407 default=None)
407 default=None)
408 _app_settings_type = Column(
408 _app_settings_type = Column(
409 "app_settings_type", String(255), nullable=True, unique=None,
409 "app_settings_type", String(255), nullable=True, unique=None,
410 default=None)
410 default=None)
411
411
412 repository = relationship('Repository')
412 repository = relationship('Repository')
413
413
414 def __init__(self, repository_id, key='', val='', type='unicode'):
414 def __init__(self, repository_id, key='', val='', type='unicode'):
415 self.repository_id = repository_id
415 self.repository_id = repository_id
416 self.app_settings_name = key
416 self.app_settings_name = key
417 self.app_settings_type = type
417 self.app_settings_type = type
418 self.app_settings_value = val
418 self.app_settings_value = val
419
419
420 @validates('_app_settings_value')
420 @validates('_app_settings_value')
421 def validate_settings_value(self, key, val):
421 def validate_settings_value(self, key, val):
422 assert type(val) == unicode
422 assert type(val) == unicode
423 return val
423 return val
424
424
425 @hybrid_property
425 @hybrid_property
426 def app_settings_value(self):
426 def app_settings_value(self):
427 v = self._app_settings_value
427 v = self._app_settings_value
428 type_ = self.app_settings_type
428 type_ = self.app_settings_type
429 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
429 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
430 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
430 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
431 return converter(v)
431 return converter(v)
432
432
433 @app_settings_value.setter
433 @app_settings_value.setter
434 def app_settings_value(self, val):
434 def app_settings_value(self, val):
435 """
435 """
436 Setter that will always make sure we use unicode in app_settings_value
436 Setter that will always make sure we use unicode in app_settings_value
437
437
438 :param val:
438 :param val:
439 """
439 """
440 self._app_settings_value = safe_unicode(val)
440 self._app_settings_value = safe_unicode(val)
441
441
442 @hybrid_property
442 @hybrid_property
443 def app_settings_type(self):
443 def app_settings_type(self):
444 return self._app_settings_type
444 return self._app_settings_type
445
445
446 @app_settings_type.setter
446 @app_settings_type.setter
447 def app_settings_type(self, val):
447 def app_settings_type(self, val):
448 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
448 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
449 if val not in SETTINGS_TYPES:
449 if val not in SETTINGS_TYPES:
450 raise Exception('type must be one of %s got %s'
450 raise Exception('type must be one of %s got %s'
451 % (SETTINGS_TYPES.keys(), val))
451 % (SETTINGS_TYPES.keys(), val))
452 self._app_settings_type = val
452 self._app_settings_type = val
453
453
454 def __unicode__(self):
454 def __unicode__(self):
455 return u"<%s('%s:%s:%s[%s]')>" % (
455 return u"<%s('%s:%s:%s[%s]')>" % (
456 self.__class__.__name__, self.repository.repo_name,
456 self.__class__.__name__, self.repository.repo_name,
457 self.app_settings_name, self.app_settings_value,
457 self.app_settings_name, self.app_settings_value,
458 self.app_settings_type
458 self.app_settings_type
459 )
459 )
460
460
461
461
462 class RepoRhodeCodeUi(Base, BaseModel):
462 class RepoRhodeCodeUi(Base, BaseModel):
463 __tablename__ = 'repo_rhodecode_ui'
463 __tablename__ = 'repo_rhodecode_ui'
464 __table_args__ = (
464 __table_args__ = (
465 UniqueConstraint(
465 UniqueConstraint(
466 'repository_id', 'ui_section', 'ui_key',
466 'repository_id', 'ui_section', 'ui_key',
467 name='uq_repo_rhodecode_ui_repository_id_section_key'),
467 name='uq_repo_rhodecode_ui_repository_id_section_key'),
468 {'extend_existing': True, 'mysql_engine': 'InnoDB',
468 {'extend_existing': True, 'mysql_engine': 'InnoDB',
469 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
469 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
470 )
470 )
471
471
472 repository_id = Column(
472 repository_id = Column(
473 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
473 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
474 nullable=False)
474 nullable=False)
475 ui_id = Column(
475 ui_id = Column(
476 "ui_id", Integer(), nullable=False, unique=True, default=None,
476 "ui_id", Integer(), nullable=False, unique=True, default=None,
477 primary_key=True)
477 primary_key=True)
478 ui_section = Column(
478 ui_section = Column(
479 "ui_section", String(255), nullable=True, unique=None, default=None)
479 "ui_section", String(255), nullable=True, unique=None, default=None)
480 ui_key = Column(
480 ui_key = Column(
481 "ui_key", String(255), nullable=True, unique=None, default=None)
481 "ui_key", String(255), nullable=True, unique=None, default=None)
482 ui_value = Column(
482 ui_value = Column(
483 "ui_value", String(255), nullable=True, unique=None, default=None)
483 "ui_value", String(255), nullable=True, unique=None, default=None)
484 ui_active = Column(
484 ui_active = Column(
485 "ui_active", Boolean(), nullable=True, unique=None, default=True)
485 "ui_active", Boolean(), nullable=True, unique=None, default=True)
486
486
487 repository = relationship('Repository')
487 repository = relationship('Repository')
488
488
489 def __repr__(self):
489 def __repr__(self):
490 return '<%s[%s:%s]%s=>%s]>' % (
490 return '<%s[%s:%s]%s=>%s]>' % (
491 self.__class__.__name__, self.repository.repo_name,
491 self.__class__.__name__, self.repository.repo_name,
492 self.ui_section, self.ui_key, self.ui_value)
492 self.ui_section, self.ui_key, self.ui_value)
493
493
494
494
495 class User(Base, BaseModel):
495 class User(Base, BaseModel):
496 __tablename__ = 'users'
496 __tablename__ = 'users'
497 __table_args__ = (
497 __table_args__ = (
498 UniqueConstraint('username'), UniqueConstraint('email'),
498 UniqueConstraint('username'), UniqueConstraint('email'),
499 Index('u_username_idx', 'username'),
499 Index('u_username_idx', 'username'),
500 Index('u_email_idx', 'email'),
500 Index('u_email_idx', 'email'),
501 {'extend_existing': True, 'mysql_engine': 'InnoDB',
501 {'extend_existing': True, 'mysql_engine': 'InnoDB',
502 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
502 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
503 )
503 )
504 DEFAULT_USER = 'default'
504 DEFAULT_USER = 'default'
505 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
505 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
506 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
506 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
507
507
508 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
508 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
509 username = Column("username", String(255), nullable=True, unique=None, default=None)
509 username = Column("username", String(255), nullable=True, unique=None, default=None)
510 password = Column("password", String(255), nullable=True, unique=None, default=None)
510 password = Column("password", String(255), nullable=True, unique=None, default=None)
511 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
511 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
512 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
512 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
513 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
513 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
514 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
514 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
515 _email = Column("email", String(255), nullable=True, unique=None, default=None)
515 _email = Column("email", String(255), nullable=True, unique=None, default=None)
516 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
516 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
517 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
517 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
518
518
519 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
519 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
520 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
520 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
521 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
521 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
522 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
522 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
523 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
523 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
524 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
524 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
525
525
526 user_log = relationship('UserLog')
526 user_log = relationship('UserLog')
527 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
527 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
528
528
529 repositories = relationship('Repository')
529 repositories = relationship('Repository')
530 repository_groups = relationship('RepoGroup')
530 repository_groups = relationship('RepoGroup')
531 user_groups = relationship('UserGroup')
531 user_groups = relationship('UserGroup')
532
532
533 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
533 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
534 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
534 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
535
535
536 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
536 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
537 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
537 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
538 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
538 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
539
539
540 group_member = relationship('UserGroupMember', cascade='all')
540 group_member = relationship('UserGroupMember', cascade='all')
541
541
542 notifications = relationship('UserNotification', cascade='all')
542 notifications = relationship('UserNotification', cascade='all')
543 # notifications assigned to this user
543 # notifications assigned to this user
544 user_created_notifications = relationship('Notification', cascade='all')
544 user_created_notifications = relationship('Notification', cascade='all')
545 # comments created by this user
545 # comments created by this user
546 user_comments = relationship('ChangesetComment', cascade='all')
546 user_comments = relationship('ChangesetComment', cascade='all')
547 # user profile extra info
547 # user profile extra info
548 user_emails = relationship('UserEmailMap', cascade='all')
548 user_emails = relationship('UserEmailMap', cascade='all')
549 user_ip_map = relationship('UserIpMap', cascade='all')
549 user_ip_map = relationship('UserIpMap', cascade='all')
550 user_auth_tokens = relationship('UserApiKeys', cascade='all')
550 user_auth_tokens = relationship('UserApiKeys', cascade='all')
551 # gists
551 # gists
552 user_gists = relationship('Gist', cascade='all')
552 user_gists = relationship('Gist', cascade='all')
553 # user pull requests
553 # user pull requests
554 user_pull_requests = relationship('PullRequest', cascade='all')
554 user_pull_requests = relationship('PullRequest', cascade='all')
555 # external identities
555 # external identities
556 extenal_identities = relationship(
556 extenal_identities = relationship(
557 'ExternalIdentity',
557 'ExternalIdentity',
558 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
558 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
559 cascade='all')
559 cascade='all')
560
560
561 def __unicode__(self):
561 def __unicode__(self):
562 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
562 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
563 self.user_id, self.username)
563 self.user_id, self.username)
564
564
565 @hybrid_property
565 @hybrid_property
566 def email(self):
566 def email(self):
567 return self._email
567 return self._email
568
568
569 @email.setter
569 @email.setter
570 def email(self, val):
570 def email(self, val):
571 self._email = val.lower() if val else None
571 self._email = val.lower() if val else None
572
572
573 @hybrid_property
573 @hybrid_property
574 def api_key(self):
574 def api_key(self):
575 """
575 """
576 Fetch if exist an auth-token with role ALL connected to this user
576 Fetch if exist an auth-token with role ALL connected to this user
577 """
577 """
578 user_auth_token = UserApiKeys.query()\
578 user_auth_token = UserApiKeys.query()\
579 .filter(UserApiKeys.user_id == self.user_id)\
579 .filter(UserApiKeys.user_id == self.user_id)\
580 .filter(or_(UserApiKeys.expires == -1,
580 .filter(or_(UserApiKeys.expires == -1,
581 UserApiKeys.expires >= time.time()))\
581 UserApiKeys.expires >= time.time()))\
582 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
582 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
583 if user_auth_token:
583 if user_auth_token:
584 user_auth_token = user_auth_token.api_key
584 user_auth_token = user_auth_token.api_key
585
585
586 return user_auth_token
586 return user_auth_token
587
587
588 @api_key.setter
588 @api_key.setter
589 def api_key(self, val):
589 def api_key(self, val):
590 # don't allow to set API key this is deprecated for now
590 # don't allow to set API key this is deprecated for now
591 self._api_key = None
591 self._api_key = None
592
592
593 @property
593 @property
594 def firstname(self):
594 def firstname(self):
595 # alias for future
595 # alias for future
596 return self.name
596 return self.name
597
597
598 @property
598 @property
599 def emails(self):
599 def emails(self):
600 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
600 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
601 return [self.email] + [x.email for x in other]
601 return [self.email] + [x.email for x in other]
602
602
603 @property
603 @property
604 def auth_tokens(self):
604 def auth_tokens(self):
605 return [x.api_key for x in self.extra_auth_tokens]
605 return [x.api_key for x in self.extra_auth_tokens]
606
606
607 @property
607 @property
608 def extra_auth_tokens(self):
608 def extra_auth_tokens(self):
609 return UserApiKeys.query().filter(UserApiKeys.user == self).all()
609 return UserApiKeys.query().filter(UserApiKeys.user == self).all()
610
610
611 @property
611 @property
612 def feed_token(self):
612 def feed_token(self):
613 return self.get_feed_token()
613 return self.get_feed_token()
614
614
615 def get_feed_token(self):
615 def get_feed_token(self):
616 feed_tokens = UserApiKeys.query()\
616 feed_tokens = UserApiKeys.query()\
617 .filter(UserApiKeys.user == self)\
617 .filter(UserApiKeys.user == self)\
618 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
618 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
619 .all()
619 .all()
620 if feed_tokens:
620 if feed_tokens:
621 return feed_tokens[0].api_key
621 return feed_tokens[0].api_key
622 return 'NO_FEED_TOKEN_AVAILABLE'
622 return 'NO_FEED_TOKEN_AVAILABLE'
623
623
624 @classmethod
624 @classmethod
625 def extra_valid_auth_tokens(cls, user, role=None):
625 def extra_valid_auth_tokens(cls, user, role=None):
626 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
626 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
627 .filter(or_(UserApiKeys.expires == -1,
627 .filter(or_(UserApiKeys.expires == -1,
628 UserApiKeys.expires >= time.time()))
628 UserApiKeys.expires >= time.time()))
629 if role:
629 if role:
630 tokens = tokens.filter(or_(UserApiKeys.role == role,
630 tokens = tokens.filter(or_(UserApiKeys.role == role,
631 UserApiKeys.role == UserApiKeys.ROLE_ALL))
631 UserApiKeys.role == UserApiKeys.ROLE_ALL))
632 return tokens.all()
632 return tokens.all()
633
633
634 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
634 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
635 from rhodecode.lib import auth
635 from rhodecode.lib import auth
636
636
637 log.debug('Trying to authenticate user: %s via auth-token, '
637 log.debug('Trying to authenticate user: %s via auth-token, '
638 'and roles: %s', self, roles)
638 'and roles: %s', self, roles)
639
639
640 if not auth_token:
640 if not auth_token:
641 return False
641 return False
642
642
643 crypto_backend = auth.crypto_backend()
643 crypto_backend = auth.crypto_backend()
644
644
645 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
645 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
646 tokens_q = UserApiKeys.query()\
646 tokens_q = UserApiKeys.query()\
647 .filter(UserApiKeys.user_id == self.user_id)\
647 .filter(UserApiKeys.user_id == self.user_id)\
648 .filter(or_(UserApiKeys.expires == -1,
648 .filter(or_(UserApiKeys.expires == -1,
649 UserApiKeys.expires >= time.time()))
649 UserApiKeys.expires >= time.time()))
650
650
651 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
651 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
652
652
653 plain_tokens = []
653 plain_tokens = []
654 hash_tokens = []
654 hash_tokens = []
655
655
656 for token in tokens_q.all():
656 for token in tokens_q.all():
657 # verify scope first
657 # verify scope first
658 if token.repo_id:
658 if token.repo_id:
659 # token has a scope, we need to verify it
659 # token has a scope, we need to verify it
660 if scope_repo_id != token.repo_id:
660 if scope_repo_id != token.repo_id:
661 log.debug(
661 log.debug(
662 'Scope mismatch: token has a set repo scope: %s, '
662 'Scope mismatch: token has a set repo scope: %s, '
663 'and calling scope is:%s, skipping further checks',
663 'and calling scope is:%s, skipping further checks',
664 token.repo, scope_repo_id)
664 token.repo, scope_repo_id)
665 # token has a scope, and it doesn't match, skip token
665 # token has a scope, and it doesn't match, skip token
666 continue
666 continue
667
667
668 if token.api_key.startswith(crypto_backend.ENC_PREF):
668 if token.api_key.startswith(crypto_backend.ENC_PREF):
669 hash_tokens.append(token.api_key)
669 hash_tokens.append(token.api_key)
670 else:
670 else:
671 plain_tokens.append(token.api_key)
671 plain_tokens.append(token.api_key)
672
672
673 is_plain_match = auth_token in plain_tokens
673 is_plain_match = auth_token in plain_tokens
674 if is_plain_match:
674 if is_plain_match:
675 return True
675 return True
676
676
677 for hashed in hash_tokens:
677 for hashed in hash_tokens:
678 # TODO(marcink): this is expensive to calculate, but most secure
678 # TODO(marcink): this is expensive to calculate, but most secure
679 match = crypto_backend.hash_check(auth_token, hashed)
679 match = crypto_backend.hash_check(auth_token, hashed)
680 if match:
680 if match:
681 return True
681 return True
682
682
683 return False
683 return False
684
684
685 @property
685 @property
686 def ip_addresses(self):
686 def ip_addresses(self):
687 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
687 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
688 return [x.ip_addr for x in ret]
688 return [x.ip_addr for x in ret]
689
689
690 @property
690 @property
691 def username_and_name(self):
691 def username_and_name(self):
692 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
692 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
693
693
694 @property
694 @property
695 def username_or_name_or_email(self):
695 def username_or_name_or_email(self):
696 full_name = self.full_name if self.full_name is not ' ' else None
696 full_name = self.full_name if self.full_name is not ' ' else None
697 return self.username or full_name or self.email
697 return self.username or full_name or self.email
698
698
699 @property
699 @property
700 def full_name(self):
700 def full_name(self):
701 return '%s %s' % (self.firstname, self.lastname)
701 return '%s %s' % (self.firstname, self.lastname)
702
702
703 @property
703 @property
704 def full_name_or_username(self):
704 def full_name_or_username(self):
705 return ('%s %s' % (self.firstname, self.lastname)
705 return ('%s %s' % (self.firstname, self.lastname)
706 if (self.firstname and self.lastname) else self.username)
706 if (self.firstname and self.lastname) else self.username)
707
707
708 @property
708 @property
709 def full_contact(self):
709 def full_contact(self):
710 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
710 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
711
711
712 @property
712 @property
713 def short_contact(self):
713 def short_contact(self):
714 return '%s %s' % (self.firstname, self.lastname)
714 return '%s %s' % (self.firstname, self.lastname)
715
715
716 @property
716 @property
717 def is_admin(self):
717 def is_admin(self):
718 return self.admin
718 return self.admin
719
719
720 @property
720 @property
721 def AuthUser(self):
721 def AuthUser(self):
722 """
722 """
723 Returns instance of AuthUser for this user
723 Returns instance of AuthUser for this user
724 """
724 """
725 from rhodecode.lib.auth import AuthUser
725 from rhodecode.lib.auth import AuthUser
726 return AuthUser(user_id=self.user_id, username=self.username)
726 return AuthUser(user_id=self.user_id, username=self.username)
727
727
728 @hybrid_property
728 @hybrid_property
729 def user_data(self):
729 def user_data(self):
730 if not self._user_data:
730 if not self._user_data:
731 return {}
731 return {}
732
732
733 try:
733 try:
734 return json.loads(self._user_data)
734 return json.loads(self._user_data)
735 except TypeError:
735 except TypeError:
736 return {}
736 return {}
737
737
738 @user_data.setter
738 @user_data.setter
739 def user_data(self, val):
739 def user_data(self, val):
740 if not isinstance(val, dict):
740 if not isinstance(val, dict):
741 raise Exception('user_data must be dict, got %s' % type(val))
741 raise Exception('user_data must be dict, got %s' % type(val))
742 try:
742 try:
743 self._user_data = json.dumps(val)
743 self._user_data = json.dumps(val)
744 except Exception:
744 except Exception:
745 log.error(traceback.format_exc())
745 log.error(traceback.format_exc())
746
746
747 @classmethod
747 @classmethod
748 def get_by_username(cls, username, case_insensitive=False,
748 def get_by_username(cls, username, case_insensitive=False,
749 cache=False, identity_cache=False):
749 cache=False, identity_cache=False):
750 session = Session()
750 session = Session()
751
751
752 if case_insensitive:
752 if case_insensitive:
753 q = cls.query().filter(
753 q = cls.query().filter(
754 func.lower(cls.username) == func.lower(username))
754 func.lower(cls.username) == func.lower(username))
755 else:
755 else:
756 q = cls.query().filter(cls.username == username)
756 q = cls.query().filter(cls.username == username)
757
757
758 if cache:
758 if cache:
759 if identity_cache:
759 if identity_cache:
760 val = cls.identity_cache(session, 'username', username)
760 val = cls.identity_cache(session, 'username', username)
761 if val:
761 if val:
762 return val
762 return val
763 else:
763 else:
764 q = q.options(
764 q = q.options(
765 FromCache("sql_cache_short",
765 FromCache("sql_cache_short",
766 "get_user_by_name_%s" % _hash_key(username)))
766 "get_user_by_name_%s" % _hash_key(username)))
767
767
768 return q.scalar()
768 return q.scalar()
769
769
770 @classmethod
770 @classmethod
771 def get_by_auth_token(cls, auth_token, cache=False):
771 def get_by_auth_token(cls, auth_token, cache=False):
772 q = UserApiKeys.query()\
772 q = UserApiKeys.query()\
773 .filter(UserApiKeys.api_key == auth_token)\
773 .filter(UserApiKeys.api_key == auth_token)\
774 .filter(or_(UserApiKeys.expires == -1,
774 .filter(or_(UserApiKeys.expires == -1,
775 UserApiKeys.expires >= time.time()))
775 UserApiKeys.expires >= time.time()))
776 if cache:
776 if cache:
777 q = q.options(FromCache("sql_cache_short",
777 q = q.options(FromCache("sql_cache_short",
778 "get_auth_token_%s" % auth_token))
778 "get_auth_token_%s" % auth_token))
779
779
780 match = q.first()
780 match = q.first()
781 if match:
781 if match:
782 return match.user
782 return match.user
783
783
784 @classmethod
784 @classmethod
785 def get_by_email(cls, email, case_insensitive=False, cache=False):
785 def get_by_email(cls, email, case_insensitive=False, cache=False):
786
786
787 if case_insensitive:
787 if case_insensitive:
788 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
788 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
789
789
790 else:
790 else:
791 q = cls.query().filter(cls.email == email)
791 q = cls.query().filter(cls.email == email)
792
792
793 if cache:
793 if cache:
794 q = q.options(FromCache("sql_cache_short",
794 q = q.options(FromCache("sql_cache_short",
795 "get_email_key_%s" % _hash_key(email)))
795 "get_email_key_%s" % _hash_key(email)))
796
796
797 ret = q.scalar()
797 ret = q.scalar()
798 if ret is None:
798 if ret is None:
799 q = UserEmailMap.query()
799 q = UserEmailMap.query()
800 # try fetching in alternate email map
800 # try fetching in alternate email map
801 if case_insensitive:
801 if case_insensitive:
802 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
802 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
803 else:
803 else:
804 q = q.filter(UserEmailMap.email == email)
804 q = q.filter(UserEmailMap.email == email)
805 q = q.options(joinedload(UserEmailMap.user))
805 q = q.options(joinedload(UserEmailMap.user))
806 if cache:
806 if cache:
807 q = q.options(FromCache("sql_cache_short",
807 q = q.options(FromCache("sql_cache_short",
808 "get_email_map_key_%s" % email))
808 "get_email_map_key_%s" % email))
809 ret = getattr(q.scalar(), 'user', None)
809 ret = getattr(q.scalar(), 'user', None)
810
810
811 return ret
811 return ret
812
812
813 @classmethod
813 @classmethod
814 def get_from_cs_author(cls, author):
814 def get_from_cs_author(cls, author):
815 """
815 """
816 Tries to get User objects out of commit author string
816 Tries to get User objects out of commit author string
817
817
818 :param author:
818 :param author:
819 """
819 """
820 from rhodecode.lib.helpers import email, author_name
820 from rhodecode.lib.helpers import email, author_name
821 # Valid email in the attribute passed, see if they're in the system
821 # Valid email in the attribute passed, see if they're in the system
822 _email = email(author)
822 _email = email(author)
823 if _email:
823 if _email:
824 user = cls.get_by_email(_email, case_insensitive=True)
824 user = cls.get_by_email(_email, case_insensitive=True)
825 if user:
825 if user:
826 return user
826 return user
827 # Maybe we can match by username?
827 # Maybe we can match by username?
828 _author = author_name(author)
828 _author = author_name(author)
829 user = cls.get_by_username(_author, case_insensitive=True)
829 user = cls.get_by_username(_author, case_insensitive=True)
830 if user:
830 if user:
831 return user
831 return user
832
832
833 def update_userdata(self, **kwargs):
833 def update_userdata(self, **kwargs):
834 usr = self
834 usr = self
835 old = usr.user_data
835 old = usr.user_data
836 old.update(**kwargs)
836 old.update(**kwargs)
837 usr.user_data = old
837 usr.user_data = old
838 Session().add(usr)
838 Session().add(usr)
839 log.debug('updated userdata with ', kwargs)
839 log.debug('updated userdata with ', kwargs)
840
840
841 def update_lastlogin(self):
841 def update_lastlogin(self):
842 """Update user lastlogin"""
842 """Update user lastlogin"""
843 self.last_login = datetime.datetime.now()
843 self.last_login = datetime.datetime.now()
844 Session().add(self)
844 Session().add(self)
845 log.debug('updated user %s lastlogin', self.username)
845 log.debug('updated user %s lastlogin', self.username)
846
846
847 def update_lastactivity(self):
847 def update_lastactivity(self):
848 """Update user lastactivity"""
848 """Update user lastactivity"""
849 self.last_activity = datetime.datetime.now()
849 self.last_activity = datetime.datetime.now()
850 Session().add(self)
850 Session().add(self)
851 log.debug('updated user %s lastactivity', self.username)
851 log.debug('updated user %s lastactivity', self.username)
852
852
853 def update_password(self, new_password):
853 def update_password(self, new_password):
854 from rhodecode.lib.auth import get_crypt_password
854 from rhodecode.lib.auth import get_crypt_password
855
855
856 self.password = get_crypt_password(new_password)
856 self.password = get_crypt_password(new_password)
857 Session().add(self)
857 Session().add(self)
858
858
859 @classmethod
859 @classmethod
860 def get_first_super_admin(cls):
860 def get_first_super_admin(cls):
861 user = User.query().filter(User.admin == true()).first()
861 user = User.query().filter(User.admin == true()).first()
862 if user is None:
862 if user is None:
863 raise Exception('FATAL: Missing administrative account!')
863 raise Exception('FATAL: Missing administrative account!')
864 return user
864 return user
865
865
866 @classmethod
866 @classmethod
867 def get_all_super_admins(cls):
867 def get_all_super_admins(cls):
868 """
868 """
869 Returns all admin accounts sorted by username
869 Returns all admin accounts sorted by username
870 """
870 """
871 return User.query().filter(User.admin == true())\
871 return User.query().filter(User.admin == true())\
872 .order_by(User.username.asc()).all()
872 .order_by(User.username.asc()).all()
873
873
874 @classmethod
874 @classmethod
875 def get_default_user(cls, cache=False):
875 def get_default_user(cls, cache=False):
876 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
876 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
877 if user is None:
877 if user is None:
878 raise Exception('FATAL: Missing default account!')
878 raise Exception('FATAL: Missing default account!')
879 return user
879 return user
880
880
881 def _get_default_perms(self, user, suffix=''):
881 def _get_default_perms(self, user, suffix=''):
882 from rhodecode.model.permission import PermissionModel
882 from rhodecode.model.permission import PermissionModel
883 return PermissionModel().get_default_perms(user.user_perms, suffix)
883 return PermissionModel().get_default_perms(user.user_perms, suffix)
884
884
885 def get_default_perms(self, suffix=''):
885 def get_default_perms(self, suffix=''):
886 return self._get_default_perms(self, suffix)
886 return self._get_default_perms(self, suffix)
887
887
888 def get_api_data(self, include_secrets=False, details='full'):
888 def get_api_data(self, include_secrets=False, details='full'):
889 """
889 """
890 Common function for generating user related data for API
890 Common function for generating user related data for API
891
891
892 :param include_secrets: By default secrets in the API data will be replaced
892 :param include_secrets: By default secrets in the API data will be replaced
893 by a placeholder value to prevent exposing this data by accident. In case
893 by a placeholder value to prevent exposing this data by accident. In case
894 this data shall be exposed, set this flag to ``True``.
894 this data shall be exposed, set this flag to ``True``.
895
895
896 :param details: details can be 'basic|full' basic gives only a subset of
896 :param details: details can be 'basic|full' basic gives only a subset of
897 the available user information that includes user_id, name and emails.
897 the available user information that includes user_id, name and emails.
898 """
898 """
899 user = self
899 user = self
900 user_data = self.user_data
900 user_data = self.user_data
901 data = {
901 data = {
902 'user_id': user.user_id,
902 'user_id': user.user_id,
903 'username': user.username,
903 'username': user.username,
904 'firstname': user.name,
904 'firstname': user.name,
905 'lastname': user.lastname,
905 'lastname': user.lastname,
906 'email': user.email,
906 'email': user.email,
907 'emails': user.emails,
907 'emails': user.emails,
908 }
908 }
909 if details == 'basic':
909 if details == 'basic':
910 return data
910 return data
911
911
912 api_key_length = 40
912 api_key_length = 40
913 api_key_replacement = '*' * api_key_length
913 api_key_replacement = '*' * api_key_length
914
914
915 extras = {
915 extras = {
916 'api_keys': [api_key_replacement],
916 'api_keys': [api_key_replacement],
917 'auth_tokens': [api_key_replacement],
917 'auth_tokens': [api_key_replacement],
918 'active': user.active,
918 'active': user.active,
919 'admin': user.admin,
919 'admin': user.admin,
920 'extern_type': user.extern_type,
920 'extern_type': user.extern_type,
921 'extern_name': user.extern_name,
921 'extern_name': user.extern_name,
922 'last_login': user.last_login,
922 'last_login': user.last_login,
923 'last_activity': user.last_activity,
923 'last_activity': user.last_activity,
924 'ip_addresses': user.ip_addresses,
924 'ip_addresses': user.ip_addresses,
925 'language': user_data.get('language')
925 'language': user_data.get('language')
926 }
926 }
927 data.update(extras)
927 data.update(extras)
928
928
929 if include_secrets:
929 if include_secrets:
930 data['api_keys'] = user.auth_tokens
930 data['api_keys'] = user.auth_tokens
931 data['auth_tokens'] = user.extra_auth_tokens
931 data['auth_tokens'] = user.extra_auth_tokens
932 return data
932 return data
933
933
934 def __json__(self):
934 def __json__(self):
935 data = {
935 data = {
936 'full_name': self.full_name,
936 'full_name': self.full_name,
937 'full_name_or_username': self.full_name_or_username,
937 'full_name_or_username': self.full_name_or_username,
938 'short_contact': self.short_contact,
938 'short_contact': self.short_contact,
939 'full_contact': self.full_contact,
939 'full_contact': self.full_contact,
940 }
940 }
941 data.update(self.get_api_data())
941 data.update(self.get_api_data())
942 return data
942 return data
943
943
944
944
945 class UserApiKeys(Base, BaseModel):
945 class UserApiKeys(Base, BaseModel):
946 __tablename__ = 'user_api_keys'
946 __tablename__ = 'user_api_keys'
947 __table_args__ = (
947 __table_args__ = (
948 Index('uak_api_key_idx', 'api_key'),
948 Index('uak_api_key_idx', 'api_key'),
949 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
949 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
950 UniqueConstraint('api_key'),
950 UniqueConstraint('api_key'),
951 {'extend_existing': True, 'mysql_engine': 'InnoDB',
951 {'extend_existing': True, 'mysql_engine': 'InnoDB',
952 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
952 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
953 )
953 )
954 __mapper_args__ = {}
954 __mapper_args__ = {}
955
955
956 # ApiKey role
956 # ApiKey role
957 ROLE_ALL = 'token_role_all'
957 ROLE_ALL = 'token_role_all'
958 ROLE_HTTP = 'token_role_http'
958 ROLE_HTTP = 'token_role_http'
959 ROLE_VCS = 'token_role_vcs'
959 ROLE_VCS = 'token_role_vcs'
960 ROLE_API = 'token_role_api'
960 ROLE_API = 'token_role_api'
961 ROLE_FEED = 'token_role_feed'
961 ROLE_FEED = 'token_role_feed'
962 ROLE_PASSWORD_RESET = 'token_password_reset'
962 ROLE_PASSWORD_RESET = 'token_password_reset'
963
963
964 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
964 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
965
965
966 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
966 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
967 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
967 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
968 api_key = Column("api_key", String(255), nullable=False, unique=True)
968 api_key = Column("api_key", String(255), nullable=False, unique=True)
969 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
969 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
970 expires = Column('expires', Float(53), nullable=False)
970 expires = Column('expires', Float(53), nullable=False)
971 role = Column('role', String(255), nullable=True)
971 role = Column('role', String(255), nullable=True)
972 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
972 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
973
973
974 # scope columns
974 # scope columns
975 repo_id = Column(
975 repo_id = Column(
976 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
976 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
977 nullable=True, unique=None, default=None)
977 nullable=True, unique=None, default=None)
978 repo = relationship('Repository', lazy='joined')
978 repo = relationship('Repository', lazy='joined')
979
979
980 repo_group_id = Column(
980 repo_group_id = Column(
981 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
981 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
982 nullable=True, unique=None, default=None)
982 nullable=True, unique=None, default=None)
983 repo_group = relationship('RepoGroup', lazy='joined')
983 repo_group = relationship('RepoGroup', lazy='joined')
984
984
985 user = relationship('User', lazy='joined')
985 user = relationship('User', lazy='joined')
986
986
987 def __unicode__(self):
987 def __unicode__(self):
988 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
988 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
989
989
990 def __json__(self):
990 def __json__(self):
991 data = {
991 data = {
992 'auth_token': self.api_key,
992 'auth_token': self.api_key,
993 'role': self.role,
993 'role': self.role,
994 'scope': self.scope_humanized,
994 'scope': self.scope_humanized,
995 'expired': self.expired
995 'expired': self.expired
996 }
996 }
997 return data
997 return data
998
998
999 @property
999 @property
1000 def expired(self):
1000 def expired(self):
1001 if self.expires == -1:
1001 if self.expires == -1:
1002 return False
1002 return False
1003 return time.time() > self.expires
1003 return time.time() > self.expires
1004
1004
1005 @classmethod
1005 @classmethod
1006 def _get_role_name(cls, role):
1006 def _get_role_name(cls, role):
1007 return {
1007 return {
1008 cls.ROLE_ALL: _('all'),
1008 cls.ROLE_ALL: _('all'),
1009 cls.ROLE_HTTP: _('http/web interface'),
1009 cls.ROLE_HTTP: _('http/web interface'),
1010 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1010 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1011 cls.ROLE_API: _('api calls'),
1011 cls.ROLE_API: _('api calls'),
1012 cls.ROLE_FEED: _('feed access'),
1012 cls.ROLE_FEED: _('feed access'),
1013 }.get(role, role)
1013 }.get(role, role)
1014
1014
1015 @property
1015 @property
1016 def role_humanized(self):
1016 def role_humanized(self):
1017 return self._get_role_name(self.role)
1017 return self._get_role_name(self.role)
1018
1018
1019 def _get_scope(self):
1019 def _get_scope(self):
1020 if self.repo:
1020 if self.repo:
1021 return repr(self.repo)
1021 return repr(self.repo)
1022 if self.repo_group:
1022 if self.repo_group:
1023 return repr(self.repo_group) + ' (recursive)'
1023 return repr(self.repo_group) + ' (recursive)'
1024 return 'global'
1024 return 'global'
1025
1025
1026 @property
1026 @property
1027 def scope_humanized(self):
1027 def scope_humanized(self):
1028 return self._get_scope()
1028 return self._get_scope()
1029
1029
1030
1030
1031 class UserEmailMap(Base, BaseModel):
1031 class UserEmailMap(Base, BaseModel):
1032 __tablename__ = 'user_email_map'
1032 __tablename__ = 'user_email_map'
1033 __table_args__ = (
1033 __table_args__ = (
1034 Index('uem_email_idx', 'email'),
1034 Index('uem_email_idx', 'email'),
1035 UniqueConstraint('email'),
1035 UniqueConstraint('email'),
1036 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1036 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1037 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1037 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1038 )
1038 )
1039 __mapper_args__ = {}
1039 __mapper_args__ = {}
1040
1040
1041 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1041 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1042 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1042 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1043 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1043 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1044 user = relationship('User', lazy='joined')
1044 user = relationship('User', lazy='joined')
1045
1045
1046 @validates('_email')
1046 @validates('_email')
1047 def validate_email(self, key, email):
1047 def validate_email(self, key, email):
1048 # check if this email is not main one
1048 # check if this email is not main one
1049 main_email = Session().query(User).filter(User.email == email).scalar()
1049 main_email = Session().query(User).filter(User.email == email).scalar()
1050 if main_email is not None:
1050 if main_email is not None:
1051 raise AttributeError('email %s is present is user table' % email)
1051 raise AttributeError('email %s is present is user table' % email)
1052 return email
1052 return email
1053
1053
1054 @hybrid_property
1054 @hybrid_property
1055 def email(self):
1055 def email(self):
1056 return self._email
1056 return self._email
1057
1057
1058 @email.setter
1058 @email.setter
1059 def email(self, val):
1059 def email(self, val):
1060 self._email = val.lower() if val else None
1060 self._email = val.lower() if val else None
1061
1061
1062
1062
1063 class UserIpMap(Base, BaseModel):
1063 class UserIpMap(Base, BaseModel):
1064 __tablename__ = 'user_ip_map'
1064 __tablename__ = 'user_ip_map'
1065 __table_args__ = (
1065 __table_args__ = (
1066 UniqueConstraint('user_id', 'ip_addr'),
1066 UniqueConstraint('user_id', 'ip_addr'),
1067 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1067 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1068 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1068 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1069 )
1069 )
1070 __mapper_args__ = {}
1070 __mapper_args__ = {}
1071
1071
1072 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1072 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1073 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1073 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1074 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1074 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1075 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1075 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1076 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1076 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1077 user = relationship('User', lazy='joined')
1077 user = relationship('User', lazy='joined')
1078
1078
1079 @classmethod
1079 @classmethod
1080 def _get_ip_range(cls, ip_addr):
1080 def _get_ip_range(cls, ip_addr):
1081 net = ipaddress.ip_network(ip_addr, strict=False)
1081 net = ipaddress.ip_network(ip_addr, strict=False)
1082 return [str(net.network_address), str(net.broadcast_address)]
1082 return [str(net.network_address), str(net.broadcast_address)]
1083
1083
1084 def __json__(self):
1084 def __json__(self):
1085 return {
1085 return {
1086 'ip_addr': self.ip_addr,
1086 'ip_addr': self.ip_addr,
1087 'ip_range': self._get_ip_range(self.ip_addr),
1087 'ip_range': self._get_ip_range(self.ip_addr),
1088 }
1088 }
1089
1089
1090 def __unicode__(self):
1090 def __unicode__(self):
1091 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1091 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1092 self.user_id, self.ip_addr)
1092 self.user_id, self.ip_addr)
1093
1093
1094
1094
1095 class UserLog(Base, BaseModel):
1095 class UserLog(Base, BaseModel):
1096 __tablename__ = 'user_logs'
1096 __tablename__ = 'user_logs'
1097 __table_args__ = (
1097 __table_args__ = (
1098 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1098 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1099 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1099 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1100 )
1100 )
1101 VERSION_1 = 'v1'
1101 VERSION_1 = 'v1'
1102 VERSION_2 = 'v2'
1102 VERSION_2 = 'v2'
1103 VERSIONS = [VERSION_1, VERSION_2]
1103 VERSIONS = [VERSION_1, VERSION_2]
1104
1104
1105 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1105 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1106 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1106 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1107 username = Column("username", String(255), nullable=True, unique=None, default=None)
1107 username = Column("username", String(255), nullable=True, unique=None, default=None)
1108 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
1108 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
1109 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1109 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1110 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1110 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1111 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1111 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1112 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1112 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1113
1113
1114 version = Column("version", String(255), nullable=True, default=VERSION_1)
1114 version = Column("version", String(255), nullable=True, default=VERSION_1)
1115 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
1115 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
1116 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
1116 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
1117
1117
1118 def __unicode__(self):
1118 def __unicode__(self):
1119 return u"<%s('id:%s:%s')>" % (
1119 return u"<%s('id:%s:%s')>" % (
1120 self.__class__.__name__, self.repository_name, self.action)
1120 self.__class__.__name__, self.repository_name, self.action)
1121
1121
1122 def __json__(self):
1122 def __json__(self):
1123 return {
1123 return {
1124 'user_id': self.user_id,
1124 'user_id': self.user_id,
1125 'username': self.username,
1125 'username': self.username,
1126 'repository_id': self.repository_id,
1126 'repository_id': self.repository_id,
1127 'repository_name': self.repository_name,
1127 'repository_name': self.repository_name,
1128 'user_ip': self.user_ip,
1128 'user_ip': self.user_ip,
1129 'action_date': self.action_date,
1129 'action_date': self.action_date,
1130 'action': self.action,
1130 'action': self.action,
1131 }
1131 }
1132
1132
1133 @property
1133 @property
1134 def action_as_day(self):
1134 def action_as_day(self):
1135 return datetime.date(*self.action_date.timetuple()[:3])
1135 return datetime.date(*self.action_date.timetuple()[:3])
1136
1136
1137 user = relationship('User')
1137 user = relationship('User')
1138 repository = relationship('Repository', cascade='')
1138 repository = relationship('Repository', cascade='')
1139
1139
1140
1140
1141 class UserGroup(Base, BaseModel):
1141 class UserGroup(Base, BaseModel):
1142 __tablename__ = 'users_groups'
1142 __tablename__ = 'users_groups'
1143 __table_args__ = (
1143 __table_args__ = (
1144 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1144 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1145 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1145 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1146 )
1146 )
1147
1147
1148 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1148 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1149 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1149 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1150 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1150 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1151 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1151 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1152 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1152 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1153 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1153 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1154 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1154 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1155 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1155 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1156
1156
1157 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1157 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1158 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1158 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1159 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1159 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1160 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1160 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1161 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1161 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1162 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1162 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1163
1163
1164 user = relationship('User')
1164 user = relationship('User')
1165
1165
1166 @hybrid_property
1166 @hybrid_property
1167 def group_data(self):
1167 def group_data(self):
1168 if not self._group_data:
1168 if not self._group_data:
1169 return {}
1169 return {}
1170
1170
1171 try:
1171 try:
1172 return json.loads(self._group_data)
1172 return json.loads(self._group_data)
1173 except TypeError:
1173 except TypeError:
1174 return {}
1174 return {}
1175
1175
1176 @group_data.setter
1176 @group_data.setter
1177 def group_data(self, val):
1177 def group_data(self, val):
1178 try:
1178 try:
1179 self._group_data = json.dumps(val)
1179 self._group_data = json.dumps(val)
1180 except Exception:
1180 except Exception:
1181 log.error(traceback.format_exc())
1181 log.error(traceback.format_exc())
1182
1182
1183 def __unicode__(self):
1183 def __unicode__(self):
1184 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1184 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1185 self.users_group_id,
1185 self.users_group_id,
1186 self.users_group_name)
1186 self.users_group_name)
1187
1187
1188 @classmethod
1188 @classmethod
1189 def get_by_group_name(cls, group_name, cache=False,
1189 def get_by_group_name(cls, group_name, cache=False,
1190 case_insensitive=False):
1190 case_insensitive=False):
1191 if case_insensitive:
1191 if case_insensitive:
1192 q = cls.query().filter(func.lower(cls.users_group_name) ==
1192 q = cls.query().filter(func.lower(cls.users_group_name) ==
1193 func.lower(group_name))
1193 func.lower(group_name))
1194
1194
1195 else:
1195 else:
1196 q = cls.query().filter(cls.users_group_name == group_name)
1196 q = cls.query().filter(cls.users_group_name == group_name)
1197 if cache:
1197 if cache:
1198 q = q.options(FromCache(
1198 q = q.options(FromCache(
1199 "sql_cache_short",
1199 "sql_cache_short",
1200 "get_group_%s" % _hash_key(group_name)))
1200 "get_group_%s" % _hash_key(group_name)))
1201 return q.scalar()
1201 return q.scalar()
1202
1202
1203 @classmethod
1203 @classmethod
1204 def get(cls, user_group_id, cache=False):
1204 def get(cls, user_group_id, cache=False):
1205 user_group = cls.query()
1205 user_group = cls.query()
1206 if cache:
1206 if cache:
1207 user_group = user_group.options(FromCache("sql_cache_short",
1207 user_group = user_group.options(FromCache("sql_cache_short",
1208 "get_users_group_%s" % user_group_id))
1208 "get_users_group_%s" % user_group_id))
1209 return user_group.get(user_group_id)
1209 return user_group.get(user_group_id)
1210
1210
1211 def permissions(self, with_admins=True, with_owner=True):
1211 def permissions(self, with_admins=True, with_owner=True):
1212 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1212 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1213 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1213 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1214 joinedload(UserUserGroupToPerm.user),
1214 joinedload(UserUserGroupToPerm.user),
1215 joinedload(UserUserGroupToPerm.permission),)
1215 joinedload(UserUserGroupToPerm.permission),)
1216
1216
1217 # get owners and admins and permissions. We do a trick of re-writing
1217 # get owners and admins and permissions. We do a trick of re-writing
1218 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1218 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1219 # has a global reference and changing one object propagates to all
1219 # has a global reference and changing one object propagates to all
1220 # others. This means if admin is also an owner admin_row that change
1220 # others. This means if admin is also an owner admin_row that change
1221 # would propagate to both objects
1221 # would propagate to both objects
1222 perm_rows = []
1222 perm_rows = []
1223 for _usr in q.all():
1223 for _usr in q.all():
1224 usr = AttributeDict(_usr.user.get_dict())
1224 usr = AttributeDict(_usr.user.get_dict())
1225 usr.permission = _usr.permission.permission_name
1225 usr.permission = _usr.permission.permission_name
1226 perm_rows.append(usr)
1226 perm_rows.append(usr)
1227
1227
1228 # filter the perm rows by 'default' first and then sort them by
1228 # filter the perm rows by 'default' first and then sort them by
1229 # admin,write,read,none permissions sorted again alphabetically in
1229 # admin,write,read,none permissions sorted again alphabetically in
1230 # each group
1230 # each group
1231 perm_rows = sorted(perm_rows, key=display_sort)
1231 perm_rows = sorted(perm_rows, key=display_sort)
1232
1232
1233 _admin_perm = 'usergroup.admin'
1233 _admin_perm = 'usergroup.admin'
1234 owner_row = []
1234 owner_row = []
1235 if with_owner:
1235 if with_owner:
1236 usr = AttributeDict(self.user.get_dict())
1236 usr = AttributeDict(self.user.get_dict())
1237 usr.owner_row = True
1237 usr.owner_row = True
1238 usr.permission = _admin_perm
1238 usr.permission = _admin_perm
1239 owner_row.append(usr)
1239 owner_row.append(usr)
1240
1240
1241 super_admin_rows = []
1241 super_admin_rows = []
1242 if with_admins:
1242 if with_admins:
1243 for usr in User.get_all_super_admins():
1243 for usr in User.get_all_super_admins():
1244 # if this admin is also owner, don't double the record
1244 # if this admin is also owner, don't double the record
1245 if usr.user_id == owner_row[0].user_id:
1245 if usr.user_id == owner_row[0].user_id:
1246 owner_row[0].admin_row = True
1246 owner_row[0].admin_row = True
1247 else:
1247 else:
1248 usr = AttributeDict(usr.get_dict())
1248 usr = AttributeDict(usr.get_dict())
1249 usr.admin_row = True
1249 usr.admin_row = True
1250 usr.permission = _admin_perm
1250 usr.permission = _admin_perm
1251 super_admin_rows.append(usr)
1251 super_admin_rows.append(usr)
1252
1252
1253 return super_admin_rows + owner_row + perm_rows
1253 return super_admin_rows + owner_row + perm_rows
1254
1254
1255 def permission_user_groups(self):
1255 def permission_user_groups(self):
1256 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1256 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1257 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1257 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1258 joinedload(UserGroupUserGroupToPerm.target_user_group),
1258 joinedload(UserGroupUserGroupToPerm.target_user_group),
1259 joinedload(UserGroupUserGroupToPerm.permission),)
1259 joinedload(UserGroupUserGroupToPerm.permission),)
1260
1260
1261 perm_rows = []
1261 perm_rows = []
1262 for _user_group in q.all():
1262 for _user_group in q.all():
1263 usr = AttributeDict(_user_group.user_group.get_dict())
1263 usr = AttributeDict(_user_group.user_group.get_dict())
1264 usr.permission = _user_group.permission.permission_name
1264 usr.permission = _user_group.permission.permission_name
1265 perm_rows.append(usr)
1265 perm_rows.append(usr)
1266
1266
1267 return perm_rows
1267 return perm_rows
1268
1268
1269 def _get_default_perms(self, user_group, suffix=''):
1269 def _get_default_perms(self, user_group, suffix=''):
1270 from rhodecode.model.permission import PermissionModel
1270 from rhodecode.model.permission import PermissionModel
1271 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1271 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1272
1272
1273 def get_default_perms(self, suffix=''):
1273 def get_default_perms(self, suffix=''):
1274 return self._get_default_perms(self, suffix)
1274 return self._get_default_perms(self, suffix)
1275
1275
1276 def get_api_data(self, with_group_members=True, include_secrets=False):
1276 def get_api_data(self, with_group_members=True, include_secrets=False):
1277 """
1277 """
1278 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1278 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1279 basically forwarded.
1279 basically forwarded.
1280
1280
1281 """
1281 """
1282 user_group = self
1282 user_group = self
1283 data = {
1283 data = {
1284 'users_group_id': user_group.users_group_id,
1284 'users_group_id': user_group.users_group_id,
1285 'group_name': user_group.users_group_name,
1285 'group_name': user_group.users_group_name,
1286 'group_description': user_group.user_group_description,
1286 'group_description': user_group.user_group_description,
1287 'active': user_group.users_group_active,
1287 'active': user_group.users_group_active,
1288 'owner': user_group.user.username,
1288 'owner': user_group.user.username,
1289 'owner_email': user_group.user.email,
1289 'owner_email': user_group.user.email,
1290 }
1290 }
1291
1291
1292 if with_group_members:
1292 if with_group_members:
1293 users = []
1293 users = []
1294 for user in user_group.members:
1294 for user in user_group.members:
1295 user = user.user
1295 user = user.user
1296 users.append(user.get_api_data(include_secrets=include_secrets))
1296 users.append(user.get_api_data(include_secrets=include_secrets))
1297 data['users'] = users
1297 data['users'] = users
1298
1298
1299 return data
1299 return data
1300
1300
1301
1301
1302 class UserGroupMember(Base, BaseModel):
1302 class UserGroupMember(Base, BaseModel):
1303 __tablename__ = 'users_groups_members'
1303 __tablename__ = 'users_groups_members'
1304 __table_args__ = (
1304 __table_args__ = (
1305 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1305 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1306 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1306 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1307 )
1307 )
1308
1308
1309 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1309 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1310 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1310 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1311 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1311 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1312
1312
1313 user = relationship('User', lazy='joined')
1313 user = relationship('User', lazy='joined')
1314 users_group = relationship('UserGroup')
1314 users_group = relationship('UserGroup')
1315
1315
1316 def __init__(self, gr_id='', u_id=''):
1316 def __init__(self, gr_id='', u_id=''):
1317 self.users_group_id = gr_id
1317 self.users_group_id = gr_id
1318 self.user_id = u_id
1318 self.user_id = u_id
1319
1319
1320
1320
1321 class RepositoryField(Base, BaseModel):
1321 class RepositoryField(Base, BaseModel):
1322 __tablename__ = 'repositories_fields'
1322 __tablename__ = 'repositories_fields'
1323 __table_args__ = (
1323 __table_args__ = (
1324 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1324 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1325 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1325 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1326 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1326 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1327 )
1327 )
1328 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1328 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1329
1329
1330 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1330 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1331 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1331 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1332 field_key = Column("field_key", String(250))
1332 field_key = Column("field_key", String(250))
1333 field_label = Column("field_label", String(1024), nullable=False)
1333 field_label = Column("field_label", String(1024), nullable=False)
1334 field_value = Column("field_value", String(10000), nullable=False)
1334 field_value = Column("field_value", String(10000), nullable=False)
1335 field_desc = Column("field_desc", String(1024), nullable=False)
1335 field_desc = Column("field_desc", String(1024), nullable=False)
1336 field_type = Column("field_type", String(255), nullable=False, unique=None)
1336 field_type = Column("field_type", String(255), nullable=False, unique=None)
1337 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1337 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1338
1338
1339 repository = relationship('Repository')
1339 repository = relationship('Repository')
1340
1340
1341 @property
1341 @property
1342 def field_key_prefixed(self):
1342 def field_key_prefixed(self):
1343 return 'ex_%s' % self.field_key
1343 return 'ex_%s' % self.field_key
1344
1344
1345 @classmethod
1345 @classmethod
1346 def un_prefix_key(cls, key):
1346 def un_prefix_key(cls, key):
1347 if key.startswith(cls.PREFIX):
1347 if key.startswith(cls.PREFIX):
1348 return key[len(cls.PREFIX):]
1348 return key[len(cls.PREFIX):]
1349 return key
1349 return key
1350
1350
1351 @classmethod
1351 @classmethod
1352 def get_by_key_name(cls, key, repo):
1352 def get_by_key_name(cls, key, repo):
1353 row = cls.query()\
1353 row = cls.query()\
1354 .filter(cls.repository == repo)\
1354 .filter(cls.repository == repo)\
1355 .filter(cls.field_key == key).scalar()
1355 .filter(cls.field_key == key).scalar()
1356 return row
1356 return row
1357
1357
1358
1358
1359 class Repository(Base, BaseModel):
1359 class Repository(Base, BaseModel):
1360 __tablename__ = 'repositories'
1360 __tablename__ = 'repositories'
1361 __table_args__ = (
1361 __table_args__ = (
1362 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1362 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1363 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1363 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1364 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1364 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1365 )
1365 )
1366 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1366 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1367 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1367 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1368
1368
1369 STATE_CREATED = 'repo_state_created'
1369 STATE_CREATED = 'repo_state_created'
1370 STATE_PENDING = 'repo_state_pending'
1370 STATE_PENDING = 'repo_state_pending'
1371 STATE_ERROR = 'repo_state_error'
1371 STATE_ERROR = 'repo_state_error'
1372
1372
1373 LOCK_AUTOMATIC = 'lock_auto'
1373 LOCK_AUTOMATIC = 'lock_auto'
1374 LOCK_API = 'lock_api'
1374 LOCK_API = 'lock_api'
1375 LOCK_WEB = 'lock_web'
1375 LOCK_WEB = 'lock_web'
1376 LOCK_PULL = 'lock_pull'
1376 LOCK_PULL = 'lock_pull'
1377
1377
1378 NAME_SEP = URL_SEP
1378 NAME_SEP = URL_SEP
1379
1379
1380 repo_id = Column(
1380 repo_id = Column(
1381 "repo_id", Integer(), nullable=False, unique=True, default=None,
1381 "repo_id", Integer(), nullable=False, unique=True, default=None,
1382 primary_key=True)
1382 primary_key=True)
1383 _repo_name = Column(
1383 _repo_name = Column(
1384 "repo_name", Text(), nullable=False, default=None)
1384 "repo_name", Text(), nullable=False, default=None)
1385 _repo_name_hash = Column(
1385 _repo_name_hash = Column(
1386 "repo_name_hash", String(255), nullable=False, unique=True)
1386 "repo_name_hash", String(255), nullable=False, unique=True)
1387 repo_state = Column("repo_state", String(255), nullable=True)
1387 repo_state = Column("repo_state", String(255), nullable=True)
1388
1388
1389 clone_uri = Column(
1389 clone_uri = Column(
1390 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1390 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1391 default=None)
1391 default=None)
1392 repo_type = Column(
1392 repo_type = Column(
1393 "repo_type", String(255), nullable=False, unique=False, default=None)
1393 "repo_type", String(255), nullable=False, unique=False, default=None)
1394 user_id = Column(
1394 user_id = Column(
1395 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1395 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1396 unique=False, default=None)
1396 unique=False, default=None)
1397 private = Column(
1397 private = Column(
1398 "private", Boolean(), nullable=True, unique=None, default=None)
1398 "private", Boolean(), nullable=True, unique=None, default=None)
1399 enable_statistics = Column(
1399 enable_statistics = Column(
1400 "statistics", Boolean(), nullable=True, unique=None, default=True)
1400 "statistics", Boolean(), nullable=True, unique=None, default=True)
1401 enable_downloads = Column(
1401 enable_downloads = Column(
1402 "downloads", Boolean(), nullable=True, unique=None, default=True)
1402 "downloads", Boolean(), nullable=True, unique=None, default=True)
1403 description = Column(
1403 description = Column(
1404 "description", String(10000), nullable=True, unique=None, default=None)
1404 "description", String(10000), nullable=True, unique=None, default=None)
1405 created_on = Column(
1405 created_on = Column(
1406 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1406 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1407 default=datetime.datetime.now)
1407 default=datetime.datetime.now)
1408 updated_on = Column(
1408 updated_on = Column(
1409 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1409 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1410 default=datetime.datetime.now)
1410 default=datetime.datetime.now)
1411 _landing_revision = Column(
1411 _landing_revision = Column(
1412 "landing_revision", String(255), nullable=False, unique=False,
1412 "landing_revision", String(255), nullable=False, unique=False,
1413 default=None)
1413 default=None)
1414 enable_locking = Column(
1414 enable_locking = Column(
1415 "enable_locking", Boolean(), nullable=False, unique=None,
1415 "enable_locking", Boolean(), nullable=False, unique=None,
1416 default=False)
1416 default=False)
1417 _locked = Column(
1417 _locked = Column(
1418 "locked", String(255), nullable=True, unique=False, default=None)
1418 "locked", String(255), nullable=True, unique=False, default=None)
1419 _changeset_cache = Column(
1419 _changeset_cache = Column(
1420 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1420 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1421
1421
1422 fork_id = Column(
1422 fork_id = Column(
1423 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1423 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1424 nullable=True, unique=False, default=None)
1424 nullable=True, unique=False, default=None)
1425 group_id = Column(
1425 group_id = Column(
1426 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1426 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1427 unique=False, default=None)
1427 unique=False, default=None)
1428
1428
1429 user = relationship('User', lazy='joined')
1429 user = relationship('User', lazy='joined')
1430 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1430 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1431 group = relationship('RepoGroup', lazy='joined')
1431 group = relationship('RepoGroup', lazy='joined')
1432 repo_to_perm = relationship(
1432 repo_to_perm = relationship(
1433 'UserRepoToPerm', cascade='all',
1433 'UserRepoToPerm', cascade='all',
1434 order_by='UserRepoToPerm.repo_to_perm_id')
1434 order_by='UserRepoToPerm.repo_to_perm_id')
1435 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1435 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1436 stats = relationship('Statistics', cascade='all', uselist=False)
1436 stats = relationship('Statistics', cascade='all', uselist=False)
1437
1437
1438 followers = relationship(
1438 followers = relationship(
1439 'UserFollowing',
1439 'UserFollowing',
1440 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1440 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1441 cascade='all')
1441 cascade='all')
1442 extra_fields = relationship(
1442 extra_fields = relationship(
1443 'RepositoryField', cascade="all, delete, delete-orphan")
1443 'RepositoryField', cascade="all, delete, delete-orphan")
1444 logs = relationship('UserLog')
1444 logs = relationship('UserLog')
1445 comments = relationship(
1445 comments = relationship(
1446 'ChangesetComment', cascade="all, delete, delete-orphan")
1446 'ChangesetComment', cascade="all, delete, delete-orphan")
1447 pull_requests_source = relationship(
1447 pull_requests_source = relationship(
1448 'PullRequest',
1448 'PullRequest',
1449 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1449 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1450 cascade="all, delete, delete-orphan")
1450 cascade="all, delete, delete-orphan")
1451 pull_requests_target = relationship(
1451 pull_requests_target = relationship(
1452 'PullRequest',
1452 'PullRequest',
1453 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1453 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1454 cascade="all, delete, delete-orphan")
1454 cascade="all, delete, delete-orphan")
1455 ui = relationship('RepoRhodeCodeUi', cascade="all")
1455 ui = relationship('RepoRhodeCodeUi', cascade="all")
1456 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1456 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1457 integrations = relationship('Integration',
1457 integrations = relationship('Integration',
1458 cascade="all, delete, delete-orphan")
1458 cascade="all, delete, delete-orphan")
1459
1459
1460 def __unicode__(self):
1460 def __unicode__(self):
1461 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1461 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1462 safe_unicode(self.repo_name))
1462 safe_unicode(self.repo_name))
1463
1463
1464 @hybrid_property
1464 @hybrid_property
1465 def landing_rev(self):
1465 def landing_rev(self):
1466 # always should return [rev_type, rev]
1466 # always should return [rev_type, rev]
1467 if self._landing_revision:
1467 if self._landing_revision:
1468 _rev_info = self._landing_revision.split(':')
1468 _rev_info = self._landing_revision.split(':')
1469 if len(_rev_info) < 2:
1469 if len(_rev_info) < 2:
1470 _rev_info.insert(0, 'rev')
1470 _rev_info.insert(0, 'rev')
1471 return [_rev_info[0], _rev_info[1]]
1471 return [_rev_info[0], _rev_info[1]]
1472 return [None, None]
1472 return [None, None]
1473
1473
1474 @landing_rev.setter
1474 @landing_rev.setter
1475 def landing_rev(self, val):
1475 def landing_rev(self, val):
1476 if ':' not in val:
1476 if ':' not in val:
1477 raise ValueError('value must be delimited with `:` and consist '
1477 raise ValueError('value must be delimited with `:` and consist '
1478 'of <rev_type>:<rev>, got %s instead' % val)
1478 'of <rev_type>:<rev>, got %s instead' % val)
1479 self._landing_revision = val
1479 self._landing_revision = val
1480
1480
1481 @hybrid_property
1481 @hybrid_property
1482 def locked(self):
1482 def locked(self):
1483 if self._locked:
1483 if self._locked:
1484 user_id, timelocked, reason = self._locked.split(':')
1484 user_id, timelocked, reason = self._locked.split(':')
1485 lock_values = int(user_id), timelocked, reason
1485 lock_values = int(user_id), timelocked, reason
1486 else:
1486 else:
1487 lock_values = [None, None, None]
1487 lock_values = [None, None, None]
1488 return lock_values
1488 return lock_values
1489
1489
1490 @locked.setter
1490 @locked.setter
1491 def locked(self, val):
1491 def locked(self, val):
1492 if val and isinstance(val, (list, tuple)):
1492 if val and isinstance(val, (list, tuple)):
1493 self._locked = ':'.join(map(str, val))
1493 self._locked = ':'.join(map(str, val))
1494 else:
1494 else:
1495 self._locked = None
1495 self._locked = None
1496
1496
1497 @hybrid_property
1497 @hybrid_property
1498 def changeset_cache(self):
1498 def changeset_cache(self):
1499 from rhodecode.lib.vcs.backends.base import EmptyCommit
1499 from rhodecode.lib.vcs.backends.base import EmptyCommit
1500 dummy = EmptyCommit().__json__()
1500 dummy = EmptyCommit().__json__()
1501 if not self._changeset_cache:
1501 if not self._changeset_cache:
1502 return dummy
1502 return dummy
1503 try:
1503 try:
1504 return json.loads(self._changeset_cache)
1504 return json.loads(self._changeset_cache)
1505 except TypeError:
1505 except TypeError:
1506 return dummy
1506 return dummy
1507 except Exception:
1507 except Exception:
1508 log.error(traceback.format_exc())
1508 log.error(traceback.format_exc())
1509 return dummy
1509 return dummy
1510
1510
1511 @changeset_cache.setter
1511 @changeset_cache.setter
1512 def changeset_cache(self, val):
1512 def changeset_cache(self, val):
1513 try:
1513 try:
1514 self._changeset_cache = json.dumps(val)
1514 self._changeset_cache = json.dumps(val)
1515 except Exception:
1515 except Exception:
1516 log.error(traceback.format_exc())
1516 log.error(traceback.format_exc())
1517
1517
1518 @hybrid_property
1518 @hybrid_property
1519 def repo_name(self):
1519 def repo_name(self):
1520 return self._repo_name
1520 return self._repo_name
1521
1521
1522 @repo_name.setter
1522 @repo_name.setter
1523 def repo_name(self, value):
1523 def repo_name(self, value):
1524 self._repo_name = value
1524 self._repo_name = value
1525 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1525 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1526
1526
1527 @classmethod
1527 @classmethod
1528 def normalize_repo_name(cls, repo_name):
1528 def normalize_repo_name(cls, repo_name):
1529 """
1529 """
1530 Normalizes os specific repo_name to the format internally stored inside
1530 Normalizes os specific repo_name to the format internally stored inside
1531 database using URL_SEP
1531 database using URL_SEP
1532
1532
1533 :param cls:
1533 :param cls:
1534 :param repo_name:
1534 :param repo_name:
1535 """
1535 """
1536 return cls.NAME_SEP.join(repo_name.split(os.sep))
1536 return cls.NAME_SEP.join(repo_name.split(os.sep))
1537
1537
1538 @classmethod
1538 @classmethod
1539 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1539 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1540 session = Session()
1540 session = Session()
1541 q = session.query(cls).filter(cls.repo_name == repo_name)
1541 q = session.query(cls).filter(cls.repo_name == repo_name)
1542
1542
1543 if cache:
1543 if cache:
1544 if identity_cache:
1544 if identity_cache:
1545 val = cls.identity_cache(session, 'repo_name', repo_name)
1545 val = cls.identity_cache(session, 'repo_name', repo_name)
1546 if val:
1546 if val:
1547 return val
1547 return val
1548 else:
1548 else:
1549 q = q.options(
1549 q = q.options(
1550 FromCache("sql_cache_short",
1550 FromCache("sql_cache_short",
1551 "get_repo_by_name_%s" % _hash_key(repo_name)))
1551 "get_repo_by_name_%s" % _hash_key(repo_name)))
1552
1552
1553 return q.scalar()
1553 return q.scalar()
1554
1554
1555 @classmethod
1555 @classmethod
1556 def get_by_full_path(cls, repo_full_path):
1556 def get_by_full_path(cls, repo_full_path):
1557 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1557 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1558 repo_name = cls.normalize_repo_name(repo_name)
1558 repo_name = cls.normalize_repo_name(repo_name)
1559 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1559 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1560
1560
1561 @classmethod
1561 @classmethod
1562 def get_repo_forks(cls, repo_id):
1562 def get_repo_forks(cls, repo_id):
1563 return cls.query().filter(Repository.fork_id == repo_id)
1563 return cls.query().filter(Repository.fork_id == repo_id)
1564
1564
1565 @classmethod
1565 @classmethod
1566 def base_path(cls):
1566 def base_path(cls):
1567 """
1567 """
1568 Returns base path when all repos are stored
1568 Returns base path when all repos are stored
1569
1569
1570 :param cls:
1570 :param cls:
1571 """
1571 """
1572 q = Session().query(RhodeCodeUi)\
1572 q = Session().query(RhodeCodeUi)\
1573 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1573 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1574 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1574 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1575 return q.one().ui_value
1575 return q.one().ui_value
1576
1576
1577 @classmethod
1577 @classmethod
1578 def is_valid(cls, repo_name):
1578 def is_valid(cls, repo_name):
1579 """
1579 """
1580 returns True if given repo name is a valid filesystem repository
1580 returns True if given repo name is a valid filesystem repository
1581
1581
1582 :param cls:
1582 :param cls:
1583 :param repo_name:
1583 :param repo_name:
1584 """
1584 """
1585 from rhodecode.lib.utils import is_valid_repo
1585 from rhodecode.lib.utils import is_valid_repo
1586
1586
1587 return is_valid_repo(repo_name, cls.base_path())
1587 return is_valid_repo(repo_name, cls.base_path())
1588
1588
1589 @classmethod
1589 @classmethod
1590 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1590 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1591 case_insensitive=True):
1591 case_insensitive=True):
1592 q = Repository.query()
1592 q = Repository.query()
1593
1593
1594 if not isinstance(user_id, Optional):
1594 if not isinstance(user_id, Optional):
1595 q = q.filter(Repository.user_id == user_id)
1595 q = q.filter(Repository.user_id == user_id)
1596
1596
1597 if not isinstance(group_id, Optional):
1597 if not isinstance(group_id, Optional):
1598 q = q.filter(Repository.group_id == group_id)
1598 q = q.filter(Repository.group_id == group_id)
1599
1599
1600 if case_insensitive:
1600 if case_insensitive:
1601 q = q.order_by(func.lower(Repository.repo_name))
1601 q = q.order_by(func.lower(Repository.repo_name))
1602 else:
1602 else:
1603 q = q.order_by(Repository.repo_name)
1603 q = q.order_by(Repository.repo_name)
1604 return q.all()
1604 return q.all()
1605
1605
1606 @property
1606 @property
1607 def forks(self):
1607 def forks(self):
1608 """
1608 """
1609 Return forks of this repo
1609 Return forks of this repo
1610 """
1610 """
1611 return Repository.get_repo_forks(self.repo_id)
1611 return Repository.get_repo_forks(self.repo_id)
1612
1612
1613 @property
1613 @property
1614 def parent(self):
1614 def parent(self):
1615 """
1615 """
1616 Returns fork parent
1616 Returns fork parent
1617 """
1617 """
1618 return self.fork
1618 return self.fork
1619
1619
1620 @property
1620 @property
1621 def just_name(self):
1621 def just_name(self):
1622 return self.repo_name.split(self.NAME_SEP)[-1]
1622 return self.repo_name.split(self.NAME_SEP)[-1]
1623
1623
1624 @property
1624 @property
1625 def groups_with_parents(self):
1625 def groups_with_parents(self):
1626 groups = []
1626 groups = []
1627 if self.group is None:
1627 if self.group is None:
1628 return groups
1628 return groups
1629
1629
1630 cur_gr = self.group
1630 cur_gr = self.group
1631 groups.insert(0, cur_gr)
1631 groups.insert(0, cur_gr)
1632 while 1:
1632 while 1:
1633 gr = getattr(cur_gr, 'parent_group', None)
1633 gr = getattr(cur_gr, 'parent_group', None)
1634 cur_gr = cur_gr.parent_group
1634 cur_gr = cur_gr.parent_group
1635 if gr is None:
1635 if gr is None:
1636 break
1636 break
1637 groups.insert(0, gr)
1637 groups.insert(0, gr)
1638
1638
1639 return groups
1639 return groups
1640
1640
1641 @property
1641 @property
1642 def groups_and_repo(self):
1642 def groups_and_repo(self):
1643 return self.groups_with_parents, self
1643 return self.groups_with_parents, self
1644
1644
1645 @LazyProperty
1645 @LazyProperty
1646 def repo_path(self):
1646 def repo_path(self):
1647 """
1647 """
1648 Returns base full path for that repository means where it actually
1648 Returns base full path for that repository means where it actually
1649 exists on a filesystem
1649 exists on a filesystem
1650 """
1650 """
1651 q = Session().query(RhodeCodeUi).filter(
1651 q = Session().query(RhodeCodeUi).filter(
1652 RhodeCodeUi.ui_key == self.NAME_SEP)
1652 RhodeCodeUi.ui_key == self.NAME_SEP)
1653 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1653 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1654 return q.one().ui_value
1654 return q.one().ui_value
1655
1655
1656 @property
1656 @property
1657 def repo_full_path(self):
1657 def repo_full_path(self):
1658 p = [self.repo_path]
1658 p = [self.repo_path]
1659 # we need to split the name by / since this is how we store the
1659 # we need to split the name by / since this is how we store the
1660 # names in the database, but that eventually needs to be converted
1660 # names in the database, but that eventually needs to be converted
1661 # into a valid system path
1661 # into a valid system path
1662 p += self.repo_name.split(self.NAME_SEP)
1662 p += self.repo_name.split(self.NAME_SEP)
1663 return os.path.join(*map(safe_unicode, p))
1663 return os.path.join(*map(safe_unicode, p))
1664
1664
1665 @property
1665 @property
1666 def cache_keys(self):
1666 def cache_keys(self):
1667 """
1667 """
1668 Returns associated cache keys for that repo
1668 Returns associated cache keys for that repo
1669 """
1669 """
1670 return CacheKey.query()\
1670 return CacheKey.query()\
1671 .filter(CacheKey.cache_args == self.repo_name)\
1671 .filter(CacheKey.cache_args == self.repo_name)\
1672 .order_by(CacheKey.cache_key)\
1672 .order_by(CacheKey.cache_key)\
1673 .all()
1673 .all()
1674
1674
1675 def get_new_name(self, repo_name):
1675 def get_new_name(self, repo_name):
1676 """
1676 """
1677 returns new full repository name based on assigned group and new new
1677 returns new full repository name based on assigned group and new new
1678
1678
1679 :param group_name:
1679 :param group_name:
1680 """
1680 """
1681 path_prefix = self.group.full_path_splitted if self.group else []
1681 path_prefix = self.group.full_path_splitted if self.group else []
1682 return self.NAME_SEP.join(path_prefix + [repo_name])
1682 return self.NAME_SEP.join(path_prefix + [repo_name])
1683
1683
1684 @property
1684 @property
1685 def _config(self):
1685 def _config(self):
1686 """
1686 """
1687 Returns db based config object.
1687 Returns db based config object.
1688 """
1688 """
1689 from rhodecode.lib.utils import make_db_config
1689 from rhodecode.lib.utils import make_db_config
1690 return make_db_config(clear_session=False, repo=self)
1690 return make_db_config(clear_session=False, repo=self)
1691
1691
1692 def permissions(self, with_admins=True, with_owner=True):
1692 def permissions(self, with_admins=True, with_owner=True):
1693 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1693 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1694 q = q.options(joinedload(UserRepoToPerm.repository),
1694 q = q.options(joinedload(UserRepoToPerm.repository),
1695 joinedload(UserRepoToPerm.user),
1695 joinedload(UserRepoToPerm.user),
1696 joinedload(UserRepoToPerm.permission),)
1696 joinedload(UserRepoToPerm.permission),)
1697
1697
1698 # get owners and admins and permissions. We do a trick of re-writing
1698 # get owners and admins and permissions. We do a trick of re-writing
1699 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1699 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1700 # has a global reference and changing one object propagates to all
1700 # has a global reference and changing one object propagates to all
1701 # others. This means if admin is also an owner admin_row that change
1701 # others. This means if admin is also an owner admin_row that change
1702 # would propagate to both objects
1702 # would propagate to both objects
1703 perm_rows = []
1703 perm_rows = []
1704 for _usr in q.all():
1704 for _usr in q.all():
1705 usr = AttributeDict(_usr.user.get_dict())
1705 usr = AttributeDict(_usr.user.get_dict())
1706 usr.permission = _usr.permission.permission_name
1706 usr.permission = _usr.permission.permission_name
1707 perm_rows.append(usr)
1707 perm_rows.append(usr)
1708
1708
1709 # filter the perm rows by 'default' first and then sort them by
1709 # filter the perm rows by 'default' first and then sort them by
1710 # admin,write,read,none permissions sorted again alphabetically in
1710 # admin,write,read,none permissions sorted again alphabetically in
1711 # each group
1711 # each group
1712 perm_rows = sorted(perm_rows, key=display_sort)
1712 perm_rows = sorted(perm_rows, key=display_sort)
1713
1713
1714 _admin_perm = 'repository.admin'
1714 _admin_perm = 'repository.admin'
1715 owner_row = []
1715 owner_row = []
1716 if with_owner:
1716 if with_owner:
1717 usr = AttributeDict(self.user.get_dict())
1717 usr = AttributeDict(self.user.get_dict())
1718 usr.owner_row = True
1718 usr.owner_row = True
1719 usr.permission = _admin_perm
1719 usr.permission = _admin_perm
1720 owner_row.append(usr)
1720 owner_row.append(usr)
1721
1721
1722 super_admin_rows = []
1722 super_admin_rows = []
1723 if with_admins:
1723 if with_admins:
1724 for usr in User.get_all_super_admins():
1724 for usr in User.get_all_super_admins():
1725 # if this admin is also owner, don't double the record
1725 # if this admin is also owner, don't double the record
1726 if usr.user_id == owner_row[0].user_id:
1726 if usr.user_id == owner_row[0].user_id:
1727 owner_row[0].admin_row = True
1727 owner_row[0].admin_row = True
1728 else:
1728 else:
1729 usr = AttributeDict(usr.get_dict())
1729 usr = AttributeDict(usr.get_dict())
1730 usr.admin_row = True
1730 usr.admin_row = True
1731 usr.permission = _admin_perm
1731 usr.permission = _admin_perm
1732 super_admin_rows.append(usr)
1732 super_admin_rows.append(usr)
1733
1733
1734 return super_admin_rows + owner_row + perm_rows
1734 return super_admin_rows + owner_row + perm_rows
1735
1735
1736 def permission_user_groups(self):
1736 def permission_user_groups(self):
1737 q = UserGroupRepoToPerm.query().filter(
1737 q = UserGroupRepoToPerm.query().filter(
1738 UserGroupRepoToPerm.repository == self)
1738 UserGroupRepoToPerm.repository == self)
1739 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1739 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1740 joinedload(UserGroupRepoToPerm.users_group),
1740 joinedload(UserGroupRepoToPerm.users_group),
1741 joinedload(UserGroupRepoToPerm.permission),)
1741 joinedload(UserGroupRepoToPerm.permission),)
1742
1742
1743 perm_rows = []
1743 perm_rows = []
1744 for _user_group in q.all():
1744 for _user_group in q.all():
1745 usr = AttributeDict(_user_group.users_group.get_dict())
1745 usr = AttributeDict(_user_group.users_group.get_dict())
1746 usr.permission = _user_group.permission.permission_name
1746 usr.permission = _user_group.permission.permission_name
1747 perm_rows.append(usr)
1747 perm_rows.append(usr)
1748
1748
1749 return perm_rows
1749 return perm_rows
1750
1750
1751 def get_api_data(self, include_secrets=False):
1751 def get_api_data(self, include_secrets=False):
1752 """
1752 """
1753 Common function for generating repo api data
1753 Common function for generating repo api data
1754
1754
1755 :param include_secrets: See :meth:`User.get_api_data`.
1755 :param include_secrets: See :meth:`User.get_api_data`.
1756
1756
1757 """
1757 """
1758 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1758 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1759 # move this methods on models level.
1759 # move this methods on models level.
1760 from rhodecode.model.settings import SettingsModel
1760 from rhodecode.model.settings import SettingsModel
1761
1761
1762 repo = self
1762 repo = self
1763 _user_id, _time, _reason = self.locked
1763 _user_id, _time, _reason = self.locked
1764
1764
1765 data = {
1765 data = {
1766 'repo_id': repo.repo_id,
1766 'repo_id': repo.repo_id,
1767 'repo_name': repo.repo_name,
1767 'repo_name': repo.repo_name,
1768 'repo_type': repo.repo_type,
1768 'repo_type': repo.repo_type,
1769 'clone_uri': repo.clone_uri or '',
1769 'clone_uri': repo.clone_uri or '',
1770 'url': url('summary_home', repo_name=self.repo_name, qualified=True),
1770 'url': url('summary_home', repo_name=self.repo_name, qualified=True),
1771 'private': repo.private,
1771 'private': repo.private,
1772 'created_on': repo.created_on,
1772 'created_on': repo.created_on,
1773 'description': repo.description,
1773 'description': repo.description,
1774 'landing_rev': repo.landing_rev,
1774 'landing_rev': repo.landing_rev,
1775 'owner': repo.user.username,
1775 'owner': repo.user.username,
1776 'fork_of': repo.fork.repo_name if repo.fork else None,
1776 'fork_of': repo.fork.repo_name if repo.fork else None,
1777 'enable_statistics': repo.enable_statistics,
1777 'enable_statistics': repo.enable_statistics,
1778 'enable_locking': repo.enable_locking,
1778 'enable_locking': repo.enable_locking,
1779 'enable_downloads': repo.enable_downloads,
1779 'enable_downloads': repo.enable_downloads,
1780 'last_changeset': repo.changeset_cache,
1780 'last_changeset': repo.changeset_cache,
1781 'locked_by': User.get(_user_id).get_api_data(
1781 'locked_by': User.get(_user_id).get_api_data(
1782 include_secrets=include_secrets) if _user_id else None,
1782 include_secrets=include_secrets) if _user_id else None,
1783 'locked_date': time_to_datetime(_time) if _time else None,
1783 'locked_date': time_to_datetime(_time) if _time else None,
1784 'lock_reason': _reason if _reason else None,
1784 'lock_reason': _reason if _reason else None,
1785 }
1785 }
1786
1786
1787 # TODO: mikhail: should be per-repo settings here
1787 # TODO: mikhail: should be per-repo settings here
1788 rc_config = SettingsModel().get_all_settings()
1788 rc_config = SettingsModel().get_all_settings()
1789 repository_fields = str2bool(
1789 repository_fields = str2bool(
1790 rc_config.get('rhodecode_repository_fields'))
1790 rc_config.get('rhodecode_repository_fields'))
1791 if repository_fields:
1791 if repository_fields:
1792 for f in self.extra_fields:
1792 for f in self.extra_fields:
1793 data[f.field_key_prefixed] = f.field_value
1793 data[f.field_key_prefixed] = f.field_value
1794
1794
1795 return data
1795 return data
1796
1796
1797 @classmethod
1797 @classmethod
1798 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1798 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1799 if not lock_time:
1799 if not lock_time:
1800 lock_time = time.time()
1800 lock_time = time.time()
1801 if not lock_reason:
1801 if not lock_reason:
1802 lock_reason = cls.LOCK_AUTOMATIC
1802 lock_reason = cls.LOCK_AUTOMATIC
1803 repo.locked = [user_id, lock_time, lock_reason]
1803 repo.locked = [user_id, lock_time, lock_reason]
1804 Session().add(repo)
1804 Session().add(repo)
1805 Session().commit()
1805 Session().commit()
1806
1806
1807 @classmethod
1807 @classmethod
1808 def unlock(cls, repo):
1808 def unlock(cls, repo):
1809 repo.locked = None
1809 repo.locked = None
1810 Session().add(repo)
1810 Session().add(repo)
1811 Session().commit()
1811 Session().commit()
1812
1812
1813 @classmethod
1813 @classmethod
1814 def getlock(cls, repo):
1814 def getlock(cls, repo):
1815 return repo.locked
1815 return repo.locked
1816
1816
1817 def is_user_lock(self, user_id):
1817 def is_user_lock(self, user_id):
1818 if self.lock[0]:
1818 if self.lock[0]:
1819 lock_user_id = safe_int(self.lock[0])
1819 lock_user_id = safe_int(self.lock[0])
1820 user_id = safe_int(user_id)
1820 user_id = safe_int(user_id)
1821 # both are ints, and they are equal
1821 # both are ints, and they are equal
1822 return all([lock_user_id, user_id]) and lock_user_id == user_id
1822 return all([lock_user_id, user_id]) and lock_user_id == user_id
1823
1823
1824 return False
1824 return False
1825
1825
1826 def get_locking_state(self, action, user_id, only_when_enabled=True):
1826 def get_locking_state(self, action, user_id, only_when_enabled=True):
1827 """
1827 """
1828 Checks locking on this repository, if locking is enabled and lock is
1828 Checks locking on this repository, if locking is enabled and lock is
1829 present returns a tuple of make_lock, locked, locked_by.
1829 present returns a tuple of make_lock, locked, locked_by.
1830 make_lock can have 3 states None (do nothing) True, make lock
1830 make_lock can have 3 states None (do nothing) True, make lock
1831 False release lock, This value is later propagated to hooks, which
1831 False release lock, This value is later propagated to hooks, which
1832 do the locking. Think about this as signals passed to hooks what to do.
1832 do the locking. Think about this as signals passed to hooks what to do.
1833
1833
1834 """
1834 """
1835 # TODO: johbo: This is part of the business logic and should be moved
1835 # TODO: johbo: This is part of the business logic and should be moved
1836 # into the RepositoryModel.
1836 # into the RepositoryModel.
1837
1837
1838 if action not in ('push', 'pull'):
1838 if action not in ('push', 'pull'):
1839 raise ValueError("Invalid action value: %s" % repr(action))
1839 raise ValueError("Invalid action value: %s" % repr(action))
1840
1840
1841 # defines if locked error should be thrown to user
1841 # defines if locked error should be thrown to user
1842 currently_locked = False
1842 currently_locked = False
1843 # defines if new lock should be made, tri-state
1843 # defines if new lock should be made, tri-state
1844 make_lock = None
1844 make_lock = None
1845 repo = self
1845 repo = self
1846 user = User.get(user_id)
1846 user = User.get(user_id)
1847
1847
1848 lock_info = repo.locked
1848 lock_info = repo.locked
1849
1849
1850 if repo and (repo.enable_locking or not only_when_enabled):
1850 if repo and (repo.enable_locking or not only_when_enabled):
1851 if action == 'push':
1851 if action == 'push':
1852 # check if it's already locked !, if it is compare users
1852 # check if it's already locked !, if it is compare users
1853 locked_by_user_id = lock_info[0]
1853 locked_by_user_id = lock_info[0]
1854 if user.user_id == locked_by_user_id:
1854 if user.user_id == locked_by_user_id:
1855 log.debug(
1855 log.debug(
1856 'Got `push` action from user %s, now unlocking', user)
1856 'Got `push` action from user %s, now unlocking', user)
1857 # unlock if we have push from user who locked
1857 # unlock if we have push from user who locked
1858 make_lock = False
1858 make_lock = False
1859 else:
1859 else:
1860 # we're not the same user who locked, ban with
1860 # we're not the same user who locked, ban with
1861 # code defined in settings (default is 423 HTTP Locked) !
1861 # code defined in settings (default is 423 HTTP Locked) !
1862 log.debug('Repo %s is currently locked by %s', repo, user)
1862 log.debug('Repo %s is currently locked by %s', repo, user)
1863 currently_locked = True
1863 currently_locked = True
1864 elif action == 'pull':
1864 elif action == 'pull':
1865 # [0] user [1] date
1865 # [0] user [1] date
1866 if lock_info[0] and lock_info[1]:
1866 if lock_info[0] and lock_info[1]:
1867 log.debug('Repo %s is currently locked by %s', repo, user)
1867 log.debug('Repo %s is currently locked by %s', repo, user)
1868 currently_locked = True
1868 currently_locked = True
1869 else:
1869 else:
1870 log.debug('Setting lock on repo %s by %s', repo, user)
1870 log.debug('Setting lock on repo %s by %s', repo, user)
1871 make_lock = True
1871 make_lock = True
1872
1872
1873 else:
1873 else:
1874 log.debug('Repository %s do not have locking enabled', repo)
1874 log.debug('Repository %s do not have locking enabled', repo)
1875
1875
1876 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1876 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1877 make_lock, currently_locked, lock_info)
1877 make_lock, currently_locked, lock_info)
1878
1878
1879 from rhodecode.lib.auth import HasRepoPermissionAny
1879 from rhodecode.lib.auth import HasRepoPermissionAny
1880 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1880 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1881 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1881 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1882 # if we don't have at least write permission we cannot make a lock
1882 # if we don't have at least write permission we cannot make a lock
1883 log.debug('lock state reset back to FALSE due to lack '
1883 log.debug('lock state reset back to FALSE due to lack '
1884 'of at least read permission')
1884 'of at least read permission')
1885 make_lock = False
1885 make_lock = False
1886
1886
1887 return make_lock, currently_locked, lock_info
1887 return make_lock, currently_locked, lock_info
1888
1888
1889 @property
1889 @property
1890 def last_db_change(self):
1890 def last_db_change(self):
1891 return self.updated_on
1891 return self.updated_on
1892
1892
1893 @property
1893 @property
1894 def clone_uri_hidden(self):
1894 def clone_uri_hidden(self):
1895 clone_uri = self.clone_uri
1895 clone_uri = self.clone_uri
1896 if clone_uri:
1896 if clone_uri:
1897 import urlobject
1897 import urlobject
1898 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
1898 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
1899 if url_obj.password:
1899 if url_obj.password:
1900 clone_uri = url_obj.with_password('*****')
1900 clone_uri = url_obj.with_password('*****')
1901 return clone_uri
1901 return clone_uri
1902
1902
1903 def clone_url(self, **override):
1903 def clone_url(self, **override):
1904 qualified_home_url = url('home', qualified=True)
1904 qualified_home_url = url('home', qualified=True)
1905
1905
1906 uri_tmpl = None
1906 uri_tmpl = None
1907 if 'with_id' in override:
1907 if 'with_id' in override:
1908 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1908 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1909 del override['with_id']
1909 del override['with_id']
1910
1910
1911 if 'uri_tmpl' in override:
1911 if 'uri_tmpl' in override:
1912 uri_tmpl = override['uri_tmpl']
1912 uri_tmpl = override['uri_tmpl']
1913 del override['uri_tmpl']
1913 del override['uri_tmpl']
1914
1914
1915 # we didn't override our tmpl from **overrides
1915 # we didn't override our tmpl from **overrides
1916 if not uri_tmpl:
1916 if not uri_tmpl:
1917 uri_tmpl = self.DEFAULT_CLONE_URI
1917 uri_tmpl = self.DEFAULT_CLONE_URI
1918 try:
1918 try:
1919 from pylons import tmpl_context as c
1919 from pylons import tmpl_context as c
1920 uri_tmpl = c.clone_uri_tmpl
1920 uri_tmpl = c.clone_uri_tmpl
1921 except Exception:
1921 except Exception:
1922 # in any case if we call this outside of request context,
1922 # in any case if we call this outside of request context,
1923 # ie, not having tmpl_context set up
1923 # ie, not having tmpl_context set up
1924 pass
1924 pass
1925
1925
1926 return get_clone_url(uri_tmpl=uri_tmpl,
1926 return get_clone_url(uri_tmpl=uri_tmpl,
1927 qualifed_home_url=qualified_home_url,
1927 qualifed_home_url=qualified_home_url,
1928 repo_name=self.repo_name,
1928 repo_name=self.repo_name,
1929 repo_id=self.repo_id, **override)
1929 repo_id=self.repo_id, **override)
1930
1930
1931 def set_state(self, state):
1931 def set_state(self, state):
1932 self.repo_state = state
1932 self.repo_state = state
1933 Session().add(self)
1933 Session().add(self)
1934 #==========================================================================
1934 #==========================================================================
1935 # SCM PROPERTIES
1935 # SCM PROPERTIES
1936 #==========================================================================
1936 #==========================================================================
1937
1937
1938 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
1938 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
1939 return get_commit_safe(
1939 return get_commit_safe(
1940 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
1940 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
1941
1941
1942 def get_changeset(self, rev=None, pre_load=None):
1942 def get_changeset(self, rev=None, pre_load=None):
1943 warnings.warn("Use get_commit", DeprecationWarning)
1943 warnings.warn("Use get_commit", DeprecationWarning)
1944 commit_id = None
1944 commit_id = None
1945 commit_idx = None
1945 commit_idx = None
1946 if isinstance(rev, basestring):
1946 if isinstance(rev, basestring):
1947 commit_id = rev
1947 commit_id = rev
1948 else:
1948 else:
1949 commit_idx = rev
1949 commit_idx = rev
1950 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
1950 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
1951 pre_load=pre_load)
1951 pre_load=pre_load)
1952
1952
1953 def get_landing_commit(self):
1953 def get_landing_commit(self):
1954 """
1954 """
1955 Returns landing commit, or if that doesn't exist returns the tip
1955 Returns landing commit, or if that doesn't exist returns the tip
1956 """
1956 """
1957 _rev_type, _rev = self.landing_rev
1957 _rev_type, _rev = self.landing_rev
1958 commit = self.get_commit(_rev)
1958 commit = self.get_commit(_rev)
1959 if isinstance(commit, EmptyCommit):
1959 if isinstance(commit, EmptyCommit):
1960 return self.get_commit()
1960 return self.get_commit()
1961 return commit
1961 return commit
1962
1962
1963 def update_commit_cache(self, cs_cache=None, config=None):
1963 def update_commit_cache(self, cs_cache=None, config=None):
1964 """
1964 """
1965 Update cache of last changeset for repository, keys should be::
1965 Update cache of last changeset for repository, keys should be::
1966
1966
1967 short_id
1967 short_id
1968 raw_id
1968 raw_id
1969 revision
1969 revision
1970 parents
1970 parents
1971 message
1971 message
1972 date
1972 date
1973 author
1973 author
1974
1974
1975 :param cs_cache:
1975 :param cs_cache:
1976 """
1976 """
1977 from rhodecode.lib.vcs.backends.base import BaseChangeset
1977 from rhodecode.lib.vcs.backends.base import BaseChangeset
1978 if cs_cache is None:
1978 if cs_cache is None:
1979 # use no-cache version here
1979 # use no-cache version here
1980 scm_repo = self.scm_instance(cache=False, config=config)
1980 scm_repo = self.scm_instance(cache=False, config=config)
1981 if scm_repo:
1981 if scm_repo:
1982 cs_cache = scm_repo.get_commit(
1982 cs_cache = scm_repo.get_commit(
1983 pre_load=["author", "date", "message", "parents"])
1983 pre_load=["author", "date", "message", "parents"])
1984 else:
1984 else:
1985 cs_cache = EmptyCommit()
1985 cs_cache = EmptyCommit()
1986
1986
1987 if isinstance(cs_cache, BaseChangeset):
1987 if isinstance(cs_cache, BaseChangeset):
1988 cs_cache = cs_cache.__json__()
1988 cs_cache = cs_cache.__json__()
1989
1989
1990 def is_outdated(new_cs_cache):
1990 def is_outdated(new_cs_cache):
1991 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
1991 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
1992 new_cs_cache['revision'] != self.changeset_cache['revision']):
1992 new_cs_cache['revision'] != self.changeset_cache['revision']):
1993 return True
1993 return True
1994 return False
1994 return False
1995
1995
1996 # check if we have maybe already latest cached revision
1996 # check if we have maybe already latest cached revision
1997 if is_outdated(cs_cache) or not self.changeset_cache:
1997 if is_outdated(cs_cache) or not self.changeset_cache:
1998 _default = datetime.datetime.fromtimestamp(0)
1998 _default = datetime.datetime.fromtimestamp(0)
1999 last_change = cs_cache.get('date') or _default
1999 last_change = cs_cache.get('date') or _default
2000 log.debug('updated repo %s with new cs cache %s',
2000 log.debug('updated repo %s with new cs cache %s',
2001 self.repo_name, cs_cache)
2001 self.repo_name, cs_cache)
2002 self.updated_on = last_change
2002 self.updated_on = last_change
2003 self.changeset_cache = cs_cache
2003 self.changeset_cache = cs_cache
2004 Session().add(self)
2004 Session().add(self)
2005 Session().commit()
2005 Session().commit()
2006 else:
2006 else:
2007 log.debug('Skipping update_commit_cache for repo:`%s` '
2007 log.debug('Skipping update_commit_cache for repo:`%s` '
2008 'commit already with latest changes', self.repo_name)
2008 'commit already with latest changes', self.repo_name)
2009
2009
2010 @property
2010 @property
2011 def tip(self):
2011 def tip(self):
2012 return self.get_commit('tip')
2012 return self.get_commit('tip')
2013
2013
2014 @property
2014 @property
2015 def author(self):
2015 def author(self):
2016 return self.tip.author
2016 return self.tip.author
2017
2017
2018 @property
2018 @property
2019 def last_change(self):
2019 def last_change(self):
2020 return self.scm_instance().last_change
2020 return self.scm_instance().last_change
2021
2021
2022 def get_comments(self, revisions=None):
2022 def get_comments(self, revisions=None):
2023 """
2023 """
2024 Returns comments for this repository grouped by revisions
2024 Returns comments for this repository grouped by revisions
2025
2025
2026 :param revisions: filter query by revisions only
2026 :param revisions: filter query by revisions only
2027 """
2027 """
2028 cmts = ChangesetComment.query()\
2028 cmts = ChangesetComment.query()\
2029 .filter(ChangesetComment.repo == self)
2029 .filter(ChangesetComment.repo == self)
2030 if revisions:
2030 if revisions:
2031 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2031 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2032 grouped = collections.defaultdict(list)
2032 grouped = collections.defaultdict(list)
2033 for cmt in cmts.all():
2033 for cmt in cmts.all():
2034 grouped[cmt.revision].append(cmt)
2034 grouped[cmt.revision].append(cmt)
2035 return grouped
2035 return grouped
2036
2036
2037 def statuses(self, revisions=None):
2037 def statuses(self, revisions=None):
2038 """
2038 """
2039 Returns statuses for this repository
2039 Returns statuses for this repository
2040
2040
2041 :param revisions: list of revisions to get statuses for
2041 :param revisions: list of revisions to get statuses for
2042 """
2042 """
2043 statuses = ChangesetStatus.query()\
2043 statuses = ChangesetStatus.query()\
2044 .filter(ChangesetStatus.repo == self)\
2044 .filter(ChangesetStatus.repo == self)\
2045 .filter(ChangesetStatus.version == 0)
2045 .filter(ChangesetStatus.version == 0)
2046
2046
2047 if revisions:
2047 if revisions:
2048 # Try doing the filtering in chunks to avoid hitting limits
2048 # Try doing the filtering in chunks to avoid hitting limits
2049 size = 500
2049 size = 500
2050 status_results = []
2050 status_results = []
2051 for chunk in xrange(0, len(revisions), size):
2051 for chunk in xrange(0, len(revisions), size):
2052 status_results += statuses.filter(
2052 status_results += statuses.filter(
2053 ChangesetStatus.revision.in_(
2053 ChangesetStatus.revision.in_(
2054 revisions[chunk: chunk+size])
2054 revisions[chunk: chunk+size])
2055 ).all()
2055 ).all()
2056 else:
2056 else:
2057 status_results = statuses.all()
2057 status_results = statuses.all()
2058
2058
2059 grouped = {}
2059 grouped = {}
2060
2060
2061 # maybe we have open new pullrequest without a status?
2061 # maybe we have open new pullrequest without a status?
2062 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2062 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2063 status_lbl = ChangesetStatus.get_status_lbl(stat)
2063 status_lbl = ChangesetStatus.get_status_lbl(stat)
2064 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2064 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2065 for rev in pr.revisions:
2065 for rev in pr.revisions:
2066 pr_id = pr.pull_request_id
2066 pr_id = pr.pull_request_id
2067 pr_repo = pr.target_repo.repo_name
2067 pr_repo = pr.target_repo.repo_name
2068 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2068 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2069
2069
2070 for stat in status_results:
2070 for stat in status_results:
2071 pr_id = pr_repo = None
2071 pr_id = pr_repo = None
2072 if stat.pull_request:
2072 if stat.pull_request:
2073 pr_id = stat.pull_request.pull_request_id
2073 pr_id = stat.pull_request.pull_request_id
2074 pr_repo = stat.pull_request.target_repo.repo_name
2074 pr_repo = stat.pull_request.target_repo.repo_name
2075 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2075 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2076 pr_id, pr_repo]
2076 pr_id, pr_repo]
2077 return grouped
2077 return grouped
2078
2078
2079 # ==========================================================================
2079 # ==========================================================================
2080 # SCM CACHE INSTANCE
2080 # SCM CACHE INSTANCE
2081 # ==========================================================================
2081 # ==========================================================================
2082
2082
2083 def scm_instance(self, **kwargs):
2083 def scm_instance(self, **kwargs):
2084 import rhodecode
2084 import rhodecode
2085
2085
2086 # Passing a config will not hit the cache currently only used
2086 # Passing a config will not hit the cache currently only used
2087 # for repo2dbmapper
2087 # for repo2dbmapper
2088 config = kwargs.pop('config', None)
2088 config = kwargs.pop('config', None)
2089 cache = kwargs.pop('cache', None)
2089 cache = kwargs.pop('cache', None)
2090 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2090 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2091 # if cache is NOT defined use default global, else we have a full
2091 # if cache is NOT defined use default global, else we have a full
2092 # control over cache behaviour
2092 # control over cache behaviour
2093 if cache is None and full_cache and not config:
2093 if cache is None and full_cache and not config:
2094 return self._get_instance_cached()
2094 return self._get_instance_cached()
2095 return self._get_instance(cache=bool(cache), config=config)
2095 return self._get_instance(cache=bool(cache), config=config)
2096
2096
2097 def _get_instance_cached(self):
2097 def _get_instance_cached(self):
2098 @cache_region('long_term')
2098 @cache_region('long_term')
2099 def _get_repo(cache_key):
2099 def _get_repo(cache_key):
2100 return self._get_instance()
2100 return self._get_instance()
2101
2101
2102 invalidator_context = CacheKey.repo_context_cache(
2102 invalidator_context = CacheKey.repo_context_cache(
2103 _get_repo, self.repo_name, None, thread_scoped=True)
2103 _get_repo, self.repo_name, None, thread_scoped=True)
2104
2104
2105 with invalidator_context as context:
2105 with invalidator_context as context:
2106 context.invalidate()
2106 context.invalidate()
2107 repo = context.compute()
2107 repo = context.compute()
2108
2108
2109 return repo
2109 return repo
2110
2110
2111 def _get_instance(self, cache=True, config=None):
2111 def _get_instance(self, cache=True, config=None):
2112 config = config or self._config
2112 config = config or self._config
2113 custom_wire = {
2113 custom_wire = {
2114 'cache': cache # controls the vcs.remote cache
2114 'cache': cache # controls the vcs.remote cache
2115 }
2115 }
2116 repo = get_vcs_instance(
2116 repo = get_vcs_instance(
2117 repo_path=safe_str(self.repo_full_path),
2117 repo_path=safe_str(self.repo_full_path),
2118 config=config,
2118 config=config,
2119 with_wire=custom_wire,
2119 with_wire=custom_wire,
2120 create=False,
2120 create=False,
2121 _vcs_alias=self.repo_type)
2121 _vcs_alias=self.repo_type)
2122
2122
2123 return repo
2123 return repo
2124
2124
2125 def __json__(self):
2125 def __json__(self):
2126 return {'landing_rev': self.landing_rev}
2126 return {'landing_rev': self.landing_rev}
2127
2127
2128 def get_dict(self):
2128 def get_dict(self):
2129
2129
2130 # Since we transformed `repo_name` to a hybrid property, we need to
2130 # Since we transformed `repo_name` to a hybrid property, we need to
2131 # keep compatibility with the code which uses `repo_name` field.
2131 # keep compatibility with the code which uses `repo_name` field.
2132
2132
2133 result = super(Repository, self).get_dict()
2133 result = super(Repository, self).get_dict()
2134 result['repo_name'] = result.pop('_repo_name', None)
2134 result['repo_name'] = result.pop('_repo_name', None)
2135 return result
2135 return result
2136
2136
2137
2137
2138 class RepoGroup(Base, BaseModel):
2138 class RepoGroup(Base, BaseModel):
2139 __tablename__ = 'groups'
2139 __tablename__ = 'groups'
2140 __table_args__ = (
2140 __table_args__ = (
2141 UniqueConstraint('group_name', 'group_parent_id'),
2141 UniqueConstraint('group_name', 'group_parent_id'),
2142 CheckConstraint('group_id != group_parent_id'),
2142 CheckConstraint('group_id != group_parent_id'),
2143 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2143 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2144 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2144 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2145 )
2145 )
2146 __mapper_args__ = {'order_by': 'group_name'}
2146 __mapper_args__ = {'order_by': 'group_name'}
2147
2147
2148 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2148 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2149
2149
2150 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2150 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2151 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2151 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2152 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2152 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2153 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2153 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2154 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2154 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2155 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2155 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2156 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2156 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2157 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2157 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2158
2158
2159 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2159 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2160 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2160 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2161 parent_group = relationship('RepoGroup', remote_side=group_id)
2161 parent_group = relationship('RepoGroup', remote_side=group_id)
2162 user = relationship('User')
2162 user = relationship('User')
2163 integrations = relationship('Integration',
2163 integrations = relationship('Integration',
2164 cascade="all, delete, delete-orphan")
2164 cascade="all, delete, delete-orphan")
2165
2165
2166 def __init__(self, group_name='', parent_group=None):
2166 def __init__(self, group_name='', parent_group=None):
2167 self.group_name = group_name
2167 self.group_name = group_name
2168 self.parent_group = parent_group
2168 self.parent_group = parent_group
2169
2169
2170 def __unicode__(self):
2170 def __unicode__(self):
2171 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
2171 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
2172 self.group_name)
2172 self.group_name)
2173
2173
2174 @classmethod
2174 @classmethod
2175 def _generate_choice(cls, repo_group):
2175 def _generate_choice(cls, repo_group):
2176 from webhelpers.html import literal as _literal
2176 from webhelpers.html import literal as _literal
2177 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2177 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2178 return repo_group.group_id, _name(repo_group.full_path_splitted)
2178 return repo_group.group_id, _name(repo_group.full_path_splitted)
2179
2179
2180 @classmethod
2180 @classmethod
2181 def groups_choices(cls, groups=None, show_empty_group=True):
2181 def groups_choices(cls, groups=None, show_empty_group=True):
2182 if not groups:
2182 if not groups:
2183 groups = cls.query().all()
2183 groups = cls.query().all()
2184
2184
2185 repo_groups = []
2185 repo_groups = []
2186 if show_empty_group:
2186 if show_empty_group:
2187 repo_groups = [('-1', u'-- %s --' % _('No parent'))]
2187 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2188
2188
2189 repo_groups.extend([cls._generate_choice(x) for x in groups])
2189 repo_groups.extend([cls._generate_choice(x) for x in groups])
2190
2190
2191 repo_groups = sorted(
2191 repo_groups = sorted(
2192 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2192 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2193 return repo_groups
2193 return repo_groups
2194
2194
2195 @classmethod
2195 @classmethod
2196 def url_sep(cls):
2196 def url_sep(cls):
2197 return URL_SEP
2197 return URL_SEP
2198
2198
2199 @classmethod
2199 @classmethod
2200 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2200 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2201 if case_insensitive:
2201 if case_insensitive:
2202 gr = cls.query().filter(func.lower(cls.group_name)
2202 gr = cls.query().filter(func.lower(cls.group_name)
2203 == func.lower(group_name))
2203 == func.lower(group_name))
2204 else:
2204 else:
2205 gr = cls.query().filter(cls.group_name == group_name)
2205 gr = cls.query().filter(cls.group_name == group_name)
2206 if cache:
2206 if cache:
2207 gr = gr.options(FromCache(
2207 gr = gr.options(FromCache(
2208 "sql_cache_short",
2208 "sql_cache_short",
2209 "get_group_%s" % _hash_key(group_name)))
2209 "get_group_%s" % _hash_key(group_name)))
2210 return gr.scalar()
2210 return gr.scalar()
2211
2211
2212 @classmethod
2212 @classmethod
2213 def get_user_personal_repo_group(cls, user_id):
2213 def get_user_personal_repo_group(cls, user_id):
2214 user = User.get(user_id)
2214 user = User.get(user_id)
2215 if user.username == User.DEFAULT_USER:
2215 if user.username == User.DEFAULT_USER:
2216 return None
2216 return None
2217
2217
2218 return cls.query()\
2218 return cls.query()\
2219 .filter(cls.personal == true()) \
2219 .filter(cls.personal == true()) \
2220 .filter(cls.user == user).scalar()
2220 .filter(cls.user == user).scalar()
2221
2221
2222 @classmethod
2222 @classmethod
2223 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2223 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2224 case_insensitive=True):
2224 case_insensitive=True):
2225 q = RepoGroup.query()
2225 q = RepoGroup.query()
2226
2226
2227 if not isinstance(user_id, Optional):
2227 if not isinstance(user_id, Optional):
2228 q = q.filter(RepoGroup.user_id == user_id)
2228 q = q.filter(RepoGroup.user_id == user_id)
2229
2229
2230 if not isinstance(group_id, Optional):
2230 if not isinstance(group_id, Optional):
2231 q = q.filter(RepoGroup.group_parent_id == group_id)
2231 q = q.filter(RepoGroup.group_parent_id == group_id)
2232
2232
2233 if case_insensitive:
2233 if case_insensitive:
2234 q = q.order_by(func.lower(RepoGroup.group_name))
2234 q = q.order_by(func.lower(RepoGroup.group_name))
2235 else:
2235 else:
2236 q = q.order_by(RepoGroup.group_name)
2236 q = q.order_by(RepoGroup.group_name)
2237 return q.all()
2237 return q.all()
2238
2238
2239 @property
2239 @property
2240 def parents(self):
2240 def parents(self):
2241 parents_recursion_limit = 10
2241 parents_recursion_limit = 10
2242 groups = []
2242 groups = []
2243 if self.parent_group is None:
2243 if self.parent_group is None:
2244 return groups
2244 return groups
2245 cur_gr = self.parent_group
2245 cur_gr = self.parent_group
2246 groups.insert(0, cur_gr)
2246 groups.insert(0, cur_gr)
2247 cnt = 0
2247 cnt = 0
2248 while 1:
2248 while 1:
2249 cnt += 1
2249 cnt += 1
2250 gr = getattr(cur_gr, 'parent_group', None)
2250 gr = getattr(cur_gr, 'parent_group', None)
2251 cur_gr = cur_gr.parent_group
2251 cur_gr = cur_gr.parent_group
2252 if gr is None:
2252 if gr is None:
2253 break
2253 break
2254 if cnt == parents_recursion_limit:
2254 if cnt == parents_recursion_limit:
2255 # this will prevent accidental infinit loops
2255 # this will prevent accidental infinit loops
2256 log.error(('more than %s parents found for group %s, stopping '
2256 log.error(('more than %s parents found for group %s, stopping '
2257 'recursive parent fetching' % (parents_recursion_limit, self)))
2257 'recursive parent fetching' % (parents_recursion_limit, self)))
2258 break
2258 break
2259
2259
2260 groups.insert(0, gr)
2260 groups.insert(0, gr)
2261 return groups
2261 return groups
2262
2262
2263 @property
2263 @property
2264 def children(self):
2264 def children(self):
2265 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2265 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2266
2266
2267 @property
2267 @property
2268 def name(self):
2268 def name(self):
2269 return self.group_name.split(RepoGroup.url_sep())[-1]
2269 return self.group_name.split(RepoGroup.url_sep())[-1]
2270
2270
2271 @property
2271 @property
2272 def full_path(self):
2272 def full_path(self):
2273 return self.group_name
2273 return self.group_name
2274
2274
2275 @property
2275 @property
2276 def full_path_splitted(self):
2276 def full_path_splitted(self):
2277 return self.group_name.split(RepoGroup.url_sep())
2277 return self.group_name.split(RepoGroup.url_sep())
2278
2278
2279 @property
2279 @property
2280 def repositories(self):
2280 def repositories(self):
2281 return Repository.query()\
2281 return Repository.query()\
2282 .filter(Repository.group == self)\
2282 .filter(Repository.group == self)\
2283 .order_by(Repository.repo_name)
2283 .order_by(Repository.repo_name)
2284
2284
2285 @property
2285 @property
2286 def repositories_recursive_count(self):
2286 def repositories_recursive_count(self):
2287 cnt = self.repositories.count()
2287 cnt = self.repositories.count()
2288
2288
2289 def children_count(group):
2289 def children_count(group):
2290 cnt = 0
2290 cnt = 0
2291 for child in group.children:
2291 for child in group.children:
2292 cnt += child.repositories.count()
2292 cnt += child.repositories.count()
2293 cnt += children_count(child)
2293 cnt += children_count(child)
2294 return cnt
2294 return cnt
2295
2295
2296 return cnt + children_count(self)
2296 return cnt + children_count(self)
2297
2297
2298 def _recursive_objects(self, include_repos=True):
2298 def _recursive_objects(self, include_repos=True):
2299 all_ = []
2299 all_ = []
2300
2300
2301 def _get_members(root_gr):
2301 def _get_members(root_gr):
2302 if include_repos:
2302 if include_repos:
2303 for r in root_gr.repositories:
2303 for r in root_gr.repositories:
2304 all_.append(r)
2304 all_.append(r)
2305 childs = root_gr.children.all()
2305 childs = root_gr.children.all()
2306 if childs:
2306 if childs:
2307 for gr in childs:
2307 for gr in childs:
2308 all_.append(gr)
2308 all_.append(gr)
2309 _get_members(gr)
2309 _get_members(gr)
2310
2310
2311 _get_members(self)
2311 _get_members(self)
2312 return [self] + all_
2312 return [self] + all_
2313
2313
2314 def recursive_groups_and_repos(self):
2314 def recursive_groups_and_repos(self):
2315 """
2315 """
2316 Recursive return all groups, with repositories in those groups
2316 Recursive return all groups, with repositories in those groups
2317 """
2317 """
2318 return self._recursive_objects()
2318 return self._recursive_objects()
2319
2319
2320 def recursive_groups(self):
2320 def recursive_groups(self):
2321 """
2321 """
2322 Returns all children groups for this group including children of children
2322 Returns all children groups for this group including children of children
2323 """
2323 """
2324 return self._recursive_objects(include_repos=False)
2324 return self._recursive_objects(include_repos=False)
2325
2325
2326 def get_new_name(self, group_name):
2326 def get_new_name(self, group_name):
2327 """
2327 """
2328 returns new full group name based on parent and new name
2328 returns new full group name based on parent and new name
2329
2329
2330 :param group_name:
2330 :param group_name:
2331 """
2331 """
2332 path_prefix = (self.parent_group.full_path_splitted if
2332 path_prefix = (self.parent_group.full_path_splitted if
2333 self.parent_group else [])
2333 self.parent_group else [])
2334 return RepoGroup.url_sep().join(path_prefix + [group_name])
2334 return RepoGroup.url_sep().join(path_prefix + [group_name])
2335
2335
2336 def permissions(self, with_admins=True, with_owner=True):
2336 def permissions(self, with_admins=True, with_owner=True):
2337 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2337 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2338 q = q.options(joinedload(UserRepoGroupToPerm.group),
2338 q = q.options(joinedload(UserRepoGroupToPerm.group),
2339 joinedload(UserRepoGroupToPerm.user),
2339 joinedload(UserRepoGroupToPerm.user),
2340 joinedload(UserRepoGroupToPerm.permission),)
2340 joinedload(UserRepoGroupToPerm.permission),)
2341
2341
2342 # get owners and admins and permissions. We do a trick of re-writing
2342 # get owners and admins and permissions. We do a trick of re-writing
2343 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2343 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2344 # has a global reference and changing one object propagates to all
2344 # has a global reference and changing one object propagates to all
2345 # others. This means if admin is also an owner admin_row that change
2345 # others. This means if admin is also an owner admin_row that change
2346 # would propagate to both objects
2346 # would propagate to both objects
2347 perm_rows = []
2347 perm_rows = []
2348 for _usr in q.all():
2348 for _usr in q.all():
2349 usr = AttributeDict(_usr.user.get_dict())
2349 usr = AttributeDict(_usr.user.get_dict())
2350 usr.permission = _usr.permission.permission_name
2350 usr.permission = _usr.permission.permission_name
2351 perm_rows.append(usr)
2351 perm_rows.append(usr)
2352
2352
2353 # filter the perm rows by 'default' first and then sort them by
2353 # filter the perm rows by 'default' first and then sort them by
2354 # admin,write,read,none permissions sorted again alphabetically in
2354 # admin,write,read,none permissions sorted again alphabetically in
2355 # each group
2355 # each group
2356 perm_rows = sorted(perm_rows, key=display_sort)
2356 perm_rows = sorted(perm_rows, key=display_sort)
2357
2357
2358 _admin_perm = 'group.admin'
2358 _admin_perm = 'group.admin'
2359 owner_row = []
2359 owner_row = []
2360 if with_owner:
2360 if with_owner:
2361 usr = AttributeDict(self.user.get_dict())
2361 usr = AttributeDict(self.user.get_dict())
2362 usr.owner_row = True
2362 usr.owner_row = True
2363 usr.permission = _admin_perm
2363 usr.permission = _admin_perm
2364 owner_row.append(usr)
2364 owner_row.append(usr)
2365
2365
2366 super_admin_rows = []
2366 super_admin_rows = []
2367 if with_admins:
2367 if with_admins:
2368 for usr in User.get_all_super_admins():
2368 for usr in User.get_all_super_admins():
2369 # if this admin is also owner, don't double the record
2369 # if this admin is also owner, don't double the record
2370 if usr.user_id == owner_row[0].user_id:
2370 if usr.user_id == owner_row[0].user_id:
2371 owner_row[0].admin_row = True
2371 owner_row[0].admin_row = True
2372 else:
2372 else:
2373 usr = AttributeDict(usr.get_dict())
2373 usr = AttributeDict(usr.get_dict())
2374 usr.admin_row = True
2374 usr.admin_row = True
2375 usr.permission = _admin_perm
2375 usr.permission = _admin_perm
2376 super_admin_rows.append(usr)
2376 super_admin_rows.append(usr)
2377
2377
2378 return super_admin_rows + owner_row + perm_rows
2378 return super_admin_rows + owner_row + perm_rows
2379
2379
2380 def permission_user_groups(self):
2380 def permission_user_groups(self):
2381 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2381 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2382 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2382 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2383 joinedload(UserGroupRepoGroupToPerm.users_group),
2383 joinedload(UserGroupRepoGroupToPerm.users_group),
2384 joinedload(UserGroupRepoGroupToPerm.permission),)
2384 joinedload(UserGroupRepoGroupToPerm.permission),)
2385
2385
2386 perm_rows = []
2386 perm_rows = []
2387 for _user_group in q.all():
2387 for _user_group in q.all():
2388 usr = AttributeDict(_user_group.users_group.get_dict())
2388 usr = AttributeDict(_user_group.users_group.get_dict())
2389 usr.permission = _user_group.permission.permission_name
2389 usr.permission = _user_group.permission.permission_name
2390 perm_rows.append(usr)
2390 perm_rows.append(usr)
2391
2391
2392 return perm_rows
2392 return perm_rows
2393
2393
2394 def get_api_data(self):
2394 def get_api_data(self):
2395 """
2395 """
2396 Common function for generating api data
2396 Common function for generating api data
2397
2397
2398 """
2398 """
2399 group = self
2399 group = self
2400 data = {
2400 data = {
2401 'group_id': group.group_id,
2401 'group_id': group.group_id,
2402 'group_name': group.group_name,
2402 'group_name': group.group_name,
2403 'group_description': group.group_description,
2403 'group_description': group.group_description,
2404 'parent_group': group.parent_group.group_name if group.parent_group else None,
2404 'parent_group': group.parent_group.group_name if group.parent_group else None,
2405 'repositories': [x.repo_name for x in group.repositories],
2405 'repositories': [x.repo_name for x in group.repositories],
2406 'owner': group.user.username,
2406 'owner': group.user.username,
2407 }
2407 }
2408 return data
2408 return data
2409
2409
2410
2410
2411 class Permission(Base, BaseModel):
2411 class Permission(Base, BaseModel):
2412 __tablename__ = 'permissions'
2412 __tablename__ = 'permissions'
2413 __table_args__ = (
2413 __table_args__ = (
2414 Index('p_perm_name_idx', 'permission_name'),
2414 Index('p_perm_name_idx', 'permission_name'),
2415 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2415 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2416 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2416 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2417 )
2417 )
2418 PERMS = [
2418 PERMS = [
2419 ('hg.admin', _('RhodeCode Super Administrator')),
2419 ('hg.admin', _('RhodeCode Super Administrator')),
2420
2420
2421 ('repository.none', _('Repository no access')),
2421 ('repository.none', _('Repository no access')),
2422 ('repository.read', _('Repository read access')),
2422 ('repository.read', _('Repository read access')),
2423 ('repository.write', _('Repository write access')),
2423 ('repository.write', _('Repository write access')),
2424 ('repository.admin', _('Repository admin access')),
2424 ('repository.admin', _('Repository admin access')),
2425
2425
2426 ('group.none', _('Repository group no access')),
2426 ('group.none', _('Repository group no access')),
2427 ('group.read', _('Repository group read access')),
2427 ('group.read', _('Repository group read access')),
2428 ('group.write', _('Repository group write access')),
2428 ('group.write', _('Repository group write access')),
2429 ('group.admin', _('Repository group admin access')),
2429 ('group.admin', _('Repository group admin access')),
2430
2430
2431 ('usergroup.none', _('User group no access')),
2431 ('usergroup.none', _('User group no access')),
2432 ('usergroup.read', _('User group read access')),
2432 ('usergroup.read', _('User group read access')),
2433 ('usergroup.write', _('User group write access')),
2433 ('usergroup.write', _('User group write access')),
2434 ('usergroup.admin', _('User group admin access')),
2434 ('usergroup.admin', _('User group admin access')),
2435
2435
2436 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2436 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2437 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2437 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2438
2438
2439 ('hg.usergroup.create.false', _('User Group creation disabled')),
2439 ('hg.usergroup.create.false', _('User Group creation disabled')),
2440 ('hg.usergroup.create.true', _('User Group creation enabled')),
2440 ('hg.usergroup.create.true', _('User Group creation enabled')),
2441
2441
2442 ('hg.create.none', _('Repository creation disabled')),
2442 ('hg.create.none', _('Repository creation disabled')),
2443 ('hg.create.repository', _('Repository creation enabled')),
2443 ('hg.create.repository', _('Repository creation enabled')),
2444 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2444 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2445 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2445 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2446
2446
2447 ('hg.fork.none', _('Repository forking disabled')),
2447 ('hg.fork.none', _('Repository forking disabled')),
2448 ('hg.fork.repository', _('Repository forking enabled')),
2448 ('hg.fork.repository', _('Repository forking enabled')),
2449
2449
2450 ('hg.register.none', _('Registration disabled')),
2450 ('hg.register.none', _('Registration disabled')),
2451 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2451 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2452 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2452 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2453
2453
2454 ('hg.password_reset.enabled', _('Password reset enabled')),
2454 ('hg.password_reset.enabled', _('Password reset enabled')),
2455 ('hg.password_reset.hidden', _('Password reset hidden')),
2455 ('hg.password_reset.hidden', _('Password reset hidden')),
2456 ('hg.password_reset.disabled', _('Password reset disabled')),
2456 ('hg.password_reset.disabled', _('Password reset disabled')),
2457
2457
2458 ('hg.extern_activate.manual', _('Manual activation of external account')),
2458 ('hg.extern_activate.manual', _('Manual activation of external account')),
2459 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2459 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2460
2460
2461 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2461 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2462 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2462 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2463 ]
2463 ]
2464
2464
2465 # definition of system default permissions for DEFAULT user
2465 # definition of system default permissions for DEFAULT user
2466 DEFAULT_USER_PERMISSIONS = [
2466 DEFAULT_USER_PERMISSIONS = [
2467 'repository.read',
2467 'repository.read',
2468 'group.read',
2468 'group.read',
2469 'usergroup.read',
2469 'usergroup.read',
2470 'hg.create.repository',
2470 'hg.create.repository',
2471 'hg.repogroup.create.false',
2471 'hg.repogroup.create.false',
2472 'hg.usergroup.create.false',
2472 'hg.usergroup.create.false',
2473 'hg.create.write_on_repogroup.true',
2473 'hg.create.write_on_repogroup.true',
2474 'hg.fork.repository',
2474 'hg.fork.repository',
2475 'hg.register.manual_activate',
2475 'hg.register.manual_activate',
2476 'hg.password_reset.enabled',
2476 'hg.password_reset.enabled',
2477 'hg.extern_activate.auto',
2477 'hg.extern_activate.auto',
2478 'hg.inherit_default_perms.true',
2478 'hg.inherit_default_perms.true',
2479 ]
2479 ]
2480
2480
2481 # defines which permissions are more important higher the more important
2481 # defines which permissions are more important higher the more important
2482 # Weight defines which permissions are more important.
2482 # Weight defines which permissions are more important.
2483 # The higher number the more important.
2483 # The higher number the more important.
2484 PERM_WEIGHTS = {
2484 PERM_WEIGHTS = {
2485 'repository.none': 0,
2485 'repository.none': 0,
2486 'repository.read': 1,
2486 'repository.read': 1,
2487 'repository.write': 3,
2487 'repository.write': 3,
2488 'repository.admin': 4,
2488 'repository.admin': 4,
2489
2489
2490 'group.none': 0,
2490 'group.none': 0,
2491 'group.read': 1,
2491 'group.read': 1,
2492 'group.write': 3,
2492 'group.write': 3,
2493 'group.admin': 4,
2493 'group.admin': 4,
2494
2494
2495 'usergroup.none': 0,
2495 'usergroup.none': 0,
2496 'usergroup.read': 1,
2496 'usergroup.read': 1,
2497 'usergroup.write': 3,
2497 'usergroup.write': 3,
2498 'usergroup.admin': 4,
2498 'usergroup.admin': 4,
2499
2499
2500 'hg.repogroup.create.false': 0,
2500 'hg.repogroup.create.false': 0,
2501 'hg.repogroup.create.true': 1,
2501 'hg.repogroup.create.true': 1,
2502
2502
2503 'hg.usergroup.create.false': 0,
2503 'hg.usergroup.create.false': 0,
2504 'hg.usergroup.create.true': 1,
2504 'hg.usergroup.create.true': 1,
2505
2505
2506 'hg.fork.none': 0,
2506 'hg.fork.none': 0,
2507 'hg.fork.repository': 1,
2507 'hg.fork.repository': 1,
2508 'hg.create.none': 0,
2508 'hg.create.none': 0,
2509 'hg.create.repository': 1
2509 'hg.create.repository': 1
2510 }
2510 }
2511
2511
2512 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2512 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2513 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2513 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2514 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2514 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2515
2515
2516 def __unicode__(self):
2516 def __unicode__(self):
2517 return u"<%s('%s:%s')>" % (
2517 return u"<%s('%s:%s')>" % (
2518 self.__class__.__name__, self.permission_id, self.permission_name
2518 self.__class__.__name__, self.permission_id, self.permission_name
2519 )
2519 )
2520
2520
2521 @classmethod
2521 @classmethod
2522 def get_by_key(cls, key):
2522 def get_by_key(cls, key):
2523 return cls.query().filter(cls.permission_name == key).scalar()
2523 return cls.query().filter(cls.permission_name == key).scalar()
2524
2524
2525 @classmethod
2525 @classmethod
2526 def get_default_repo_perms(cls, user_id, repo_id=None):
2526 def get_default_repo_perms(cls, user_id, repo_id=None):
2527 q = Session().query(UserRepoToPerm, Repository, Permission)\
2527 q = Session().query(UserRepoToPerm, Repository, Permission)\
2528 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2528 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2529 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2529 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2530 .filter(UserRepoToPerm.user_id == user_id)
2530 .filter(UserRepoToPerm.user_id == user_id)
2531 if repo_id:
2531 if repo_id:
2532 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2532 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2533 return q.all()
2533 return q.all()
2534
2534
2535 @classmethod
2535 @classmethod
2536 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2536 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2537 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2537 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2538 .join(
2538 .join(
2539 Permission,
2539 Permission,
2540 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2540 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2541 .join(
2541 .join(
2542 Repository,
2542 Repository,
2543 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2543 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2544 .join(
2544 .join(
2545 UserGroup,
2545 UserGroup,
2546 UserGroupRepoToPerm.users_group_id ==
2546 UserGroupRepoToPerm.users_group_id ==
2547 UserGroup.users_group_id)\
2547 UserGroup.users_group_id)\
2548 .join(
2548 .join(
2549 UserGroupMember,
2549 UserGroupMember,
2550 UserGroupRepoToPerm.users_group_id ==
2550 UserGroupRepoToPerm.users_group_id ==
2551 UserGroupMember.users_group_id)\
2551 UserGroupMember.users_group_id)\
2552 .filter(
2552 .filter(
2553 UserGroupMember.user_id == user_id,
2553 UserGroupMember.user_id == user_id,
2554 UserGroup.users_group_active == true())
2554 UserGroup.users_group_active == true())
2555 if repo_id:
2555 if repo_id:
2556 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2556 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2557 return q.all()
2557 return q.all()
2558
2558
2559 @classmethod
2559 @classmethod
2560 def get_default_group_perms(cls, user_id, repo_group_id=None):
2560 def get_default_group_perms(cls, user_id, repo_group_id=None):
2561 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2561 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2562 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2562 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2563 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2563 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2564 .filter(UserRepoGroupToPerm.user_id == user_id)
2564 .filter(UserRepoGroupToPerm.user_id == user_id)
2565 if repo_group_id:
2565 if repo_group_id:
2566 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2566 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2567 return q.all()
2567 return q.all()
2568
2568
2569 @classmethod
2569 @classmethod
2570 def get_default_group_perms_from_user_group(
2570 def get_default_group_perms_from_user_group(
2571 cls, user_id, repo_group_id=None):
2571 cls, user_id, repo_group_id=None):
2572 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2572 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2573 .join(
2573 .join(
2574 Permission,
2574 Permission,
2575 UserGroupRepoGroupToPerm.permission_id ==
2575 UserGroupRepoGroupToPerm.permission_id ==
2576 Permission.permission_id)\
2576 Permission.permission_id)\
2577 .join(
2577 .join(
2578 RepoGroup,
2578 RepoGroup,
2579 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2579 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2580 .join(
2580 .join(
2581 UserGroup,
2581 UserGroup,
2582 UserGroupRepoGroupToPerm.users_group_id ==
2582 UserGroupRepoGroupToPerm.users_group_id ==
2583 UserGroup.users_group_id)\
2583 UserGroup.users_group_id)\
2584 .join(
2584 .join(
2585 UserGroupMember,
2585 UserGroupMember,
2586 UserGroupRepoGroupToPerm.users_group_id ==
2586 UserGroupRepoGroupToPerm.users_group_id ==
2587 UserGroupMember.users_group_id)\
2587 UserGroupMember.users_group_id)\
2588 .filter(
2588 .filter(
2589 UserGroupMember.user_id == user_id,
2589 UserGroupMember.user_id == user_id,
2590 UserGroup.users_group_active == true())
2590 UserGroup.users_group_active == true())
2591 if repo_group_id:
2591 if repo_group_id:
2592 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2592 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2593 return q.all()
2593 return q.all()
2594
2594
2595 @classmethod
2595 @classmethod
2596 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2596 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2597 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2597 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2598 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2598 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2599 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2599 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2600 .filter(UserUserGroupToPerm.user_id == user_id)
2600 .filter(UserUserGroupToPerm.user_id == user_id)
2601 if user_group_id:
2601 if user_group_id:
2602 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2602 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2603 return q.all()
2603 return q.all()
2604
2604
2605 @classmethod
2605 @classmethod
2606 def get_default_user_group_perms_from_user_group(
2606 def get_default_user_group_perms_from_user_group(
2607 cls, user_id, user_group_id=None):
2607 cls, user_id, user_group_id=None):
2608 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2608 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2609 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2609 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2610 .join(
2610 .join(
2611 Permission,
2611 Permission,
2612 UserGroupUserGroupToPerm.permission_id ==
2612 UserGroupUserGroupToPerm.permission_id ==
2613 Permission.permission_id)\
2613 Permission.permission_id)\
2614 .join(
2614 .join(
2615 TargetUserGroup,
2615 TargetUserGroup,
2616 UserGroupUserGroupToPerm.target_user_group_id ==
2616 UserGroupUserGroupToPerm.target_user_group_id ==
2617 TargetUserGroup.users_group_id)\
2617 TargetUserGroup.users_group_id)\
2618 .join(
2618 .join(
2619 UserGroup,
2619 UserGroup,
2620 UserGroupUserGroupToPerm.user_group_id ==
2620 UserGroupUserGroupToPerm.user_group_id ==
2621 UserGroup.users_group_id)\
2621 UserGroup.users_group_id)\
2622 .join(
2622 .join(
2623 UserGroupMember,
2623 UserGroupMember,
2624 UserGroupUserGroupToPerm.user_group_id ==
2624 UserGroupUserGroupToPerm.user_group_id ==
2625 UserGroupMember.users_group_id)\
2625 UserGroupMember.users_group_id)\
2626 .filter(
2626 .filter(
2627 UserGroupMember.user_id == user_id,
2627 UserGroupMember.user_id == user_id,
2628 UserGroup.users_group_active == true())
2628 UserGroup.users_group_active == true())
2629 if user_group_id:
2629 if user_group_id:
2630 q = q.filter(
2630 q = q.filter(
2631 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2631 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2632
2632
2633 return q.all()
2633 return q.all()
2634
2634
2635
2635
2636 class UserRepoToPerm(Base, BaseModel):
2636 class UserRepoToPerm(Base, BaseModel):
2637 __tablename__ = 'repo_to_perm'
2637 __tablename__ = 'repo_to_perm'
2638 __table_args__ = (
2638 __table_args__ = (
2639 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2639 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2640 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2640 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2641 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2641 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2642 )
2642 )
2643 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2643 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2644 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2644 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2645 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2645 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2646 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2646 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2647
2647
2648 user = relationship('User')
2648 user = relationship('User')
2649 repository = relationship('Repository')
2649 repository = relationship('Repository')
2650 permission = relationship('Permission')
2650 permission = relationship('Permission')
2651
2651
2652 @classmethod
2652 @classmethod
2653 def create(cls, user, repository, permission):
2653 def create(cls, user, repository, permission):
2654 n = cls()
2654 n = cls()
2655 n.user = user
2655 n.user = user
2656 n.repository = repository
2656 n.repository = repository
2657 n.permission = permission
2657 n.permission = permission
2658 Session().add(n)
2658 Session().add(n)
2659 return n
2659 return n
2660
2660
2661 def __unicode__(self):
2661 def __unicode__(self):
2662 return u'<%s => %s >' % (self.user, self.repository)
2662 return u'<%s => %s >' % (self.user, self.repository)
2663
2663
2664
2664
2665 class UserUserGroupToPerm(Base, BaseModel):
2665 class UserUserGroupToPerm(Base, BaseModel):
2666 __tablename__ = 'user_user_group_to_perm'
2666 __tablename__ = 'user_user_group_to_perm'
2667 __table_args__ = (
2667 __table_args__ = (
2668 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2668 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2669 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2669 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2670 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2670 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2671 )
2671 )
2672 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2672 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2673 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2673 user_id = Column("user_id", Integer(), ForeignKey('users.user_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 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2675 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2676
2676
2677 user = relationship('User')
2677 user = relationship('User')
2678 user_group = relationship('UserGroup')
2678 user_group = relationship('UserGroup')
2679 permission = relationship('Permission')
2679 permission = relationship('Permission')
2680
2680
2681 @classmethod
2681 @classmethod
2682 def create(cls, user, user_group, permission):
2682 def create(cls, user, user_group, permission):
2683 n = cls()
2683 n = cls()
2684 n.user = user
2684 n.user = user
2685 n.user_group = user_group
2685 n.user_group = user_group
2686 n.permission = permission
2686 n.permission = permission
2687 Session().add(n)
2687 Session().add(n)
2688 return n
2688 return n
2689
2689
2690 def __unicode__(self):
2690 def __unicode__(self):
2691 return u'<%s => %s >' % (self.user, self.user_group)
2691 return u'<%s => %s >' % (self.user, self.user_group)
2692
2692
2693
2693
2694 class UserToPerm(Base, BaseModel):
2694 class UserToPerm(Base, BaseModel):
2695 __tablename__ = 'user_to_perm'
2695 __tablename__ = 'user_to_perm'
2696 __table_args__ = (
2696 __table_args__ = (
2697 UniqueConstraint('user_id', 'permission_id'),
2697 UniqueConstraint('user_id', 'permission_id'),
2698 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2698 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2699 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2699 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2700 )
2700 )
2701 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2701 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2702 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2702 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2703 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2703 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2704
2704
2705 user = relationship('User')
2705 user = relationship('User')
2706 permission = relationship('Permission', lazy='joined')
2706 permission = relationship('Permission', lazy='joined')
2707
2707
2708 def __unicode__(self):
2708 def __unicode__(self):
2709 return u'<%s => %s >' % (self.user, self.permission)
2709 return u'<%s => %s >' % (self.user, self.permission)
2710
2710
2711
2711
2712 class UserGroupRepoToPerm(Base, BaseModel):
2712 class UserGroupRepoToPerm(Base, BaseModel):
2713 __tablename__ = 'users_group_repo_to_perm'
2713 __tablename__ = 'users_group_repo_to_perm'
2714 __table_args__ = (
2714 __table_args__ = (
2715 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2715 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2716 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2716 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2717 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2717 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2718 )
2718 )
2719 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2719 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2720 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2720 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2721 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2721 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2722 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2722 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2723
2723
2724 users_group = relationship('UserGroup')
2724 users_group = relationship('UserGroup')
2725 permission = relationship('Permission')
2725 permission = relationship('Permission')
2726 repository = relationship('Repository')
2726 repository = relationship('Repository')
2727
2727
2728 @classmethod
2728 @classmethod
2729 def create(cls, users_group, repository, permission):
2729 def create(cls, users_group, repository, permission):
2730 n = cls()
2730 n = cls()
2731 n.users_group = users_group
2731 n.users_group = users_group
2732 n.repository = repository
2732 n.repository = repository
2733 n.permission = permission
2733 n.permission = permission
2734 Session().add(n)
2734 Session().add(n)
2735 return n
2735 return n
2736
2736
2737 def __unicode__(self):
2737 def __unicode__(self):
2738 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2738 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2739
2739
2740
2740
2741 class UserGroupUserGroupToPerm(Base, BaseModel):
2741 class UserGroupUserGroupToPerm(Base, BaseModel):
2742 __tablename__ = 'user_group_user_group_to_perm'
2742 __tablename__ = 'user_group_user_group_to_perm'
2743 __table_args__ = (
2743 __table_args__ = (
2744 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2744 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2745 CheckConstraint('target_user_group_id != user_group_id'),
2745 CheckConstraint('target_user_group_id != user_group_id'),
2746 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2746 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2747 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2747 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2748 )
2748 )
2749 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)
2749 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)
2750 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2750 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2751 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2751 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2752 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2752 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2753
2753
2754 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2754 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2755 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2755 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2756 permission = relationship('Permission')
2756 permission = relationship('Permission')
2757
2757
2758 @classmethod
2758 @classmethod
2759 def create(cls, target_user_group, user_group, permission):
2759 def create(cls, target_user_group, user_group, permission):
2760 n = cls()
2760 n = cls()
2761 n.target_user_group = target_user_group
2761 n.target_user_group = target_user_group
2762 n.user_group = user_group
2762 n.user_group = user_group
2763 n.permission = permission
2763 n.permission = permission
2764 Session().add(n)
2764 Session().add(n)
2765 return n
2765 return n
2766
2766
2767 def __unicode__(self):
2767 def __unicode__(self):
2768 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2768 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2769
2769
2770
2770
2771 class UserGroupToPerm(Base, BaseModel):
2771 class UserGroupToPerm(Base, BaseModel):
2772 __tablename__ = 'users_group_to_perm'
2772 __tablename__ = 'users_group_to_perm'
2773 __table_args__ = (
2773 __table_args__ = (
2774 UniqueConstraint('users_group_id', 'permission_id',),
2774 UniqueConstraint('users_group_id', 'permission_id',),
2775 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2775 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2776 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2776 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2777 )
2777 )
2778 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2778 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2779 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2779 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2780 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2780 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2781
2781
2782 users_group = relationship('UserGroup')
2782 users_group = relationship('UserGroup')
2783 permission = relationship('Permission')
2783 permission = relationship('Permission')
2784
2784
2785
2785
2786 class UserRepoGroupToPerm(Base, BaseModel):
2786 class UserRepoGroupToPerm(Base, BaseModel):
2787 __tablename__ = 'user_repo_group_to_perm'
2787 __tablename__ = 'user_repo_group_to_perm'
2788 __table_args__ = (
2788 __table_args__ = (
2789 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2789 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2790 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2790 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2791 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2791 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2792 )
2792 )
2793
2793
2794 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2794 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2795 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2795 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2796 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2796 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2797 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2797 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2798
2798
2799 user = relationship('User')
2799 user = relationship('User')
2800 group = relationship('RepoGroup')
2800 group = relationship('RepoGroup')
2801 permission = relationship('Permission')
2801 permission = relationship('Permission')
2802
2802
2803 @classmethod
2803 @classmethod
2804 def create(cls, user, repository_group, permission):
2804 def create(cls, user, repository_group, permission):
2805 n = cls()
2805 n = cls()
2806 n.user = user
2806 n.user = user
2807 n.group = repository_group
2807 n.group = repository_group
2808 n.permission = permission
2808 n.permission = permission
2809 Session().add(n)
2809 Session().add(n)
2810 return n
2810 return n
2811
2811
2812
2812
2813 class UserGroupRepoGroupToPerm(Base, BaseModel):
2813 class UserGroupRepoGroupToPerm(Base, BaseModel):
2814 __tablename__ = 'users_group_repo_group_to_perm'
2814 __tablename__ = 'users_group_repo_group_to_perm'
2815 __table_args__ = (
2815 __table_args__ = (
2816 UniqueConstraint('users_group_id', 'group_id'),
2816 UniqueConstraint('users_group_id', 'group_id'),
2817 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2817 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2818 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2818 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2819 )
2819 )
2820
2820
2821 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)
2821 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)
2822 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2822 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2823 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2823 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2824 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2824 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2825
2825
2826 users_group = relationship('UserGroup')
2826 users_group = relationship('UserGroup')
2827 permission = relationship('Permission')
2827 permission = relationship('Permission')
2828 group = relationship('RepoGroup')
2828 group = relationship('RepoGroup')
2829
2829
2830 @classmethod
2830 @classmethod
2831 def create(cls, user_group, repository_group, permission):
2831 def create(cls, user_group, repository_group, permission):
2832 n = cls()
2832 n = cls()
2833 n.users_group = user_group
2833 n.users_group = user_group
2834 n.group = repository_group
2834 n.group = repository_group
2835 n.permission = permission
2835 n.permission = permission
2836 Session().add(n)
2836 Session().add(n)
2837 return n
2837 return n
2838
2838
2839 def __unicode__(self):
2839 def __unicode__(self):
2840 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2840 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2841
2841
2842
2842
2843 class Statistics(Base, BaseModel):
2843 class Statistics(Base, BaseModel):
2844 __tablename__ = 'statistics'
2844 __tablename__ = 'statistics'
2845 __table_args__ = (
2845 __table_args__ = (
2846 UniqueConstraint('repository_id'),
2846 UniqueConstraint('repository_id'),
2847 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2847 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2848 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2848 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2849 )
2849 )
2850 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2850 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2851 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2851 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2852 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2852 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2853 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2853 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2854 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2854 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2855 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2855 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2856
2856
2857 repository = relationship('Repository', single_parent=True)
2857 repository = relationship('Repository', single_parent=True)
2858
2858
2859
2859
2860 class UserFollowing(Base, BaseModel):
2860 class UserFollowing(Base, BaseModel):
2861 __tablename__ = 'user_followings'
2861 __tablename__ = 'user_followings'
2862 __table_args__ = (
2862 __table_args__ = (
2863 UniqueConstraint('user_id', 'follows_repository_id'),
2863 UniqueConstraint('user_id', 'follows_repository_id'),
2864 UniqueConstraint('user_id', 'follows_user_id'),
2864 UniqueConstraint('user_id', 'follows_user_id'),
2865 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2865 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2866 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2866 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2867 )
2867 )
2868
2868
2869 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2869 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2870 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2870 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2871 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2871 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2872 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2872 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2873 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2873 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2874
2874
2875 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2875 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2876
2876
2877 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2877 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2878 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2878 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2879
2879
2880 @classmethod
2880 @classmethod
2881 def get_repo_followers(cls, repo_id):
2881 def get_repo_followers(cls, repo_id):
2882 return cls.query().filter(cls.follows_repo_id == repo_id)
2882 return cls.query().filter(cls.follows_repo_id == repo_id)
2883
2883
2884
2884
2885 class CacheKey(Base, BaseModel):
2885 class CacheKey(Base, BaseModel):
2886 __tablename__ = 'cache_invalidation'
2886 __tablename__ = 'cache_invalidation'
2887 __table_args__ = (
2887 __table_args__ = (
2888 UniqueConstraint('cache_key'),
2888 UniqueConstraint('cache_key'),
2889 Index('key_idx', 'cache_key'),
2889 Index('key_idx', 'cache_key'),
2890 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2890 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2891 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2891 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2892 )
2892 )
2893 CACHE_TYPE_ATOM = 'ATOM'
2893 CACHE_TYPE_ATOM = 'ATOM'
2894 CACHE_TYPE_RSS = 'RSS'
2894 CACHE_TYPE_RSS = 'RSS'
2895 CACHE_TYPE_README = 'README'
2895 CACHE_TYPE_README = 'README'
2896
2896
2897 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2897 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2898 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2898 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2899 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2899 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2900 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2900 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2901
2901
2902 def __init__(self, cache_key, cache_args=''):
2902 def __init__(self, cache_key, cache_args=''):
2903 self.cache_key = cache_key
2903 self.cache_key = cache_key
2904 self.cache_args = cache_args
2904 self.cache_args = cache_args
2905 self.cache_active = False
2905 self.cache_active = False
2906
2906
2907 def __unicode__(self):
2907 def __unicode__(self):
2908 return u"<%s('%s:%s[%s]')>" % (
2908 return u"<%s('%s:%s[%s]')>" % (
2909 self.__class__.__name__,
2909 self.__class__.__name__,
2910 self.cache_id, self.cache_key, self.cache_active)
2910 self.cache_id, self.cache_key, self.cache_active)
2911
2911
2912 def _cache_key_partition(self):
2912 def _cache_key_partition(self):
2913 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2913 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2914 return prefix, repo_name, suffix
2914 return prefix, repo_name, suffix
2915
2915
2916 def get_prefix(self):
2916 def get_prefix(self):
2917 """
2917 """
2918 Try to extract prefix from existing cache key. The key could consist
2918 Try to extract prefix from existing cache key. The key could consist
2919 of prefix, repo_name, suffix
2919 of prefix, repo_name, suffix
2920 """
2920 """
2921 # this returns prefix, repo_name, suffix
2921 # this returns prefix, repo_name, suffix
2922 return self._cache_key_partition()[0]
2922 return self._cache_key_partition()[0]
2923
2923
2924 def get_suffix(self):
2924 def get_suffix(self):
2925 """
2925 """
2926 get suffix that might have been used in _get_cache_key to
2926 get suffix that might have been used in _get_cache_key to
2927 generate self.cache_key. Only used for informational purposes
2927 generate self.cache_key. Only used for informational purposes
2928 in repo_edit.mako.
2928 in repo_edit.mako.
2929 """
2929 """
2930 # prefix, repo_name, suffix
2930 # prefix, repo_name, suffix
2931 return self._cache_key_partition()[2]
2931 return self._cache_key_partition()[2]
2932
2932
2933 @classmethod
2933 @classmethod
2934 def delete_all_cache(cls):
2934 def delete_all_cache(cls):
2935 """
2935 """
2936 Delete all cache keys from database.
2936 Delete all cache keys from database.
2937 Should only be run when all instances are down and all entries
2937 Should only be run when all instances are down and all entries
2938 thus stale.
2938 thus stale.
2939 """
2939 """
2940 cls.query().delete()
2940 cls.query().delete()
2941 Session().commit()
2941 Session().commit()
2942
2942
2943 @classmethod
2943 @classmethod
2944 def get_cache_key(cls, repo_name, cache_type):
2944 def get_cache_key(cls, repo_name, cache_type):
2945 """
2945 """
2946
2946
2947 Generate a cache key for this process of RhodeCode instance.
2947 Generate a cache key for this process of RhodeCode instance.
2948 Prefix most likely will be process id or maybe explicitly set
2948 Prefix most likely will be process id or maybe explicitly set
2949 instance_id from .ini file.
2949 instance_id from .ini file.
2950 """
2950 """
2951 import rhodecode
2951 import rhodecode
2952 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
2952 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
2953
2953
2954 repo_as_unicode = safe_unicode(repo_name)
2954 repo_as_unicode = safe_unicode(repo_name)
2955 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
2955 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
2956 if cache_type else repo_as_unicode
2956 if cache_type else repo_as_unicode
2957
2957
2958 return u'{}{}'.format(prefix, key)
2958 return u'{}{}'.format(prefix, key)
2959
2959
2960 @classmethod
2960 @classmethod
2961 def set_invalidate(cls, repo_name, delete=False):
2961 def set_invalidate(cls, repo_name, delete=False):
2962 """
2962 """
2963 Mark all caches of a repo as invalid in the database.
2963 Mark all caches of a repo as invalid in the database.
2964 """
2964 """
2965
2965
2966 try:
2966 try:
2967 qry = Session().query(cls).filter(cls.cache_args == repo_name)
2967 qry = Session().query(cls).filter(cls.cache_args == repo_name)
2968 if delete:
2968 if delete:
2969 log.debug('cache objects deleted for repo %s',
2969 log.debug('cache objects deleted for repo %s',
2970 safe_str(repo_name))
2970 safe_str(repo_name))
2971 qry.delete()
2971 qry.delete()
2972 else:
2972 else:
2973 log.debug('cache objects marked as invalid for repo %s',
2973 log.debug('cache objects marked as invalid for repo %s',
2974 safe_str(repo_name))
2974 safe_str(repo_name))
2975 qry.update({"cache_active": False})
2975 qry.update({"cache_active": False})
2976
2976
2977 Session().commit()
2977 Session().commit()
2978 except Exception:
2978 except Exception:
2979 log.exception(
2979 log.exception(
2980 'Cache key invalidation failed for repository %s',
2980 'Cache key invalidation failed for repository %s',
2981 safe_str(repo_name))
2981 safe_str(repo_name))
2982 Session().rollback()
2982 Session().rollback()
2983
2983
2984 @classmethod
2984 @classmethod
2985 def get_active_cache(cls, cache_key):
2985 def get_active_cache(cls, cache_key):
2986 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2986 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2987 if inv_obj:
2987 if inv_obj:
2988 return inv_obj
2988 return inv_obj
2989 return None
2989 return None
2990
2990
2991 @classmethod
2991 @classmethod
2992 def repo_context_cache(cls, compute_func, repo_name, cache_type,
2992 def repo_context_cache(cls, compute_func, repo_name, cache_type,
2993 thread_scoped=False):
2993 thread_scoped=False):
2994 """
2994 """
2995 @cache_region('long_term')
2995 @cache_region('long_term')
2996 def _heavy_calculation(cache_key):
2996 def _heavy_calculation(cache_key):
2997 return 'result'
2997 return 'result'
2998
2998
2999 cache_context = CacheKey.repo_context_cache(
2999 cache_context = CacheKey.repo_context_cache(
3000 _heavy_calculation, repo_name, cache_type)
3000 _heavy_calculation, repo_name, cache_type)
3001
3001
3002 with cache_context as context:
3002 with cache_context as context:
3003 context.invalidate()
3003 context.invalidate()
3004 computed = context.compute()
3004 computed = context.compute()
3005
3005
3006 assert computed == 'result'
3006 assert computed == 'result'
3007 """
3007 """
3008 from rhodecode.lib import caches
3008 from rhodecode.lib import caches
3009 return caches.InvalidationContext(
3009 return caches.InvalidationContext(
3010 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
3010 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
3011
3011
3012
3012
3013 class ChangesetComment(Base, BaseModel):
3013 class ChangesetComment(Base, BaseModel):
3014 __tablename__ = 'changeset_comments'
3014 __tablename__ = 'changeset_comments'
3015 __table_args__ = (
3015 __table_args__ = (
3016 Index('cc_revision_idx', 'revision'),
3016 Index('cc_revision_idx', 'revision'),
3017 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3017 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3018 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3018 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3019 )
3019 )
3020
3020
3021 COMMENT_OUTDATED = u'comment_outdated'
3021 COMMENT_OUTDATED = u'comment_outdated'
3022 COMMENT_TYPE_NOTE = u'note'
3022 COMMENT_TYPE_NOTE = u'note'
3023 COMMENT_TYPE_TODO = u'todo'
3023 COMMENT_TYPE_TODO = u'todo'
3024 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3024 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3025
3025
3026 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3026 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3027 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3027 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3028 revision = Column('revision', String(40), nullable=True)
3028 revision = Column('revision', String(40), nullable=True)
3029 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3029 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3030 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3030 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3031 line_no = Column('line_no', Unicode(10), nullable=True)
3031 line_no = Column('line_no', Unicode(10), nullable=True)
3032 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3032 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3033 f_path = Column('f_path', Unicode(1000), nullable=True)
3033 f_path = Column('f_path', Unicode(1000), nullable=True)
3034 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3034 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3035 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3035 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3036 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3036 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3037 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3037 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3038 renderer = Column('renderer', Unicode(64), nullable=True)
3038 renderer = Column('renderer', Unicode(64), nullable=True)
3039 display_state = Column('display_state', Unicode(128), nullable=True)
3039 display_state = Column('display_state', Unicode(128), nullable=True)
3040
3040
3041 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3041 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3042 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3042 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3043 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
3043 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
3044 author = relationship('User', lazy='joined')
3044 author = relationship('User', lazy='joined')
3045 repo = relationship('Repository')
3045 repo = relationship('Repository')
3046 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3046 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3047 pull_request = relationship('PullRequest', lazy='joined')
3047 pull_request = relationship('PullRequest', lazy='joined')
3048 pull_request_version = relationship('PullRequestVersion')
3048 pull_request_version = relationship('PullRequestVersion')
3049
3049
3050 @classmethod
3050 @classmethod
3051 def get_users(cls, revision=None, pull_request_id=None):
3051 def get_users(cls, revision=None, pull_request_id=None):
3052 """
3052 """
3053 Returns user associated with this ChangesetComment. ie those
3053 Returns user associated with this ChangesetComment. ie those
3054 who actually commented
3054 who actually commented
3055
3055
3056 :param cls:
3056 :param cls:
3057 :param revision:
3057 :param revision:
3058 """
3058 """
3059 q = Session().query(User)\
3059 q = Session().query(User)\
3060 .join(ChangesetComment.author)
3060 .join(ChangesetComment.author)
3061 if revision:
3061 if revision:
3062 q = q.filter(cls.revision == revision)
3062 q = q.filter(cls.revision == revision)
3063 elif pull_request_id:
3063 elif pull_request_id:
3064 q = q.filter(cls.pull_request_id == pull_request_id)
3064 q = q.filter(cls.pull_request_id == pull_request_id)
3065 return q.all()
3065 return q.all()
3066
3066
3067 @classmethod
3067 @classmethod
3068 def get_index_from_version(cls, pr_version, versions):
3068 def get_index_from_version(cls, pr_version, versions):
3069 num_versions = [x.pull_request_version_id for x in versions]
3069 num_versions = [x.pull_request_version_id for x in versions]
3070 try:
3070 try:
3071 return num_versions.index(pr_version) +1
3071 return num_versions.index(pr_version) +1
3072 except (IndexError, ValueError):
3072 except (IndexError, ValueError):
3073 return
3073 return
3074
3074
3075 @property
3075 @property
3076 def outdated(self):
3076 def outdated(self):
3077 return self.display_state == self.COMMENT_OUTDATED
3077 return self.display_state == self.COMMENT_OUTDATED
3078
3078
3079 def outdated_at_version(self, version):
3079 def outdated_at_version(self, version):
3080 """
3080 """
3081 Checks if comment is outdated for given pull request version
3081 Checks if comment is outdated for given pull request version
3082 """
3082 """
3083 return self.outdated and self.pull_request_version_id != version
3083 return self.outdated and self.pull_request_version_id != version
3084
3084
3085 def older_than_version(self, version):
3085 def older_than_version(self, version):
3086 """
3086 """
3087 Checks if comment is made from previous version than given
3087 Checks if comment is made from previous version than given
3088 """
3088 """
3089 if version is None:
3089 if version is None:
3090 return self.pull_request_version_id is not None
3090 return self.pull_request_version_id is not None
3091
3091
3092 return self.pull_request_version_id < version
3092 return self.pull_request_version_id < version
3093
3093
3094 @property
3094 @property
3095 def resolved(self):
3095 def resolved(self):
3096 return self.resolved_by[0] if self.resolved_by else None
3096 return self.resolved_by[0] if self.resolved_by else None
3097
3097
3098 @property
3098 @property
3099 def is_todo(self):
3099 def is_todo(self):
3100 return self.comment_type == self.COMMENT_TYPE_TODO
3100 return self.comment_type == self.COMMENT_TYPE_TODO
3101
3101
3102 def get_index_version(self, versions):
3102 def get_index_version(self, versions):
3103 return self.get_index_from_version(
3103 return self.get_index_from_version(
3104 self.pull_request_version_id, versions)
3104 self.pull_request_version_id, versions)
3105
3105
3106 def __repr__(self):
3106 def __repr__(self):
3107 if self.comment_id:
3107 if self.comment_id:
3108 return '<DB:Comment #%s>' % self.comment_id
3108 return '<DB:Comment #%s>' % self.comment_id
3109 else:
3109 else:
3110 return '<DB:Comment at %#x>' % id(self)
3110 return '<DB:Comment at %#x>' % id(self)
3111
3111
3112
3112
3113 class ChangesetStatus(Base, BaseModel):
3113 class ChangesetStatus(Base, BaseModel):
3114 __tablename__ = 'changeset_statuses'
3114 __tablename__ = 'changeset_statuses'
3115 __table_args__ = (
3115 __table_args__ = (
3116 Index('cs_revision_idx', 'revision'),
3116 Index('cs_revision_idx', 'revision'),
3117 Index('cs_version_idx', 'version'),
3117 Index('cs_version_idx', 'version'),
3118 UniqueConstraint('repo_id', 'revision', 'version'),
3118 UniqueConstraint('repo_id', 'revision', 'version'),
3119 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3119 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3120 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3120 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3121 )
3121 )
3122 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3122 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3123 STATUS_APPROVED = 'approved'
3123 STATUS_APPROVED = 'approved'
3124 STATUS_REJECTED = 'rejected'
3124 STATUS_REJECTED = 'rejected'
3125 STATUS_UNDER_REVIEW = 'under_review'
3125 STATUS_UNDER_REVIEW = 'under_review'
3126
3126
3127 STATUSES = [
3127 STATUSES = [
3128 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3128 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3129 (STATUS_APPROVED, _("Approved")),
3129 (STATUS_APPROVED, _("Approved")),
3130 (STATUS_REJECTED, _("Rejected")),
3130 (STATUS_REJECTED, _("Rejected")),
3131 (STATUS_UNDER_REVIEW, _("Under Review")),
3131 (STATUS_UNDER_REVIEW, _("Under Review")),
3132 ]
3132 ]
3133
3133
3134 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3134 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3135 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3135 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3136 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3136 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3137 revision = Column('revision', String(40), nullable=False)
3137 revision = Column('revision', String(40), nullable=False)
3138 status = Column('status', String(128), nullable=False, default=DEFAULT)
3138 status = Column('status', String(128), nullable=False, default=DEFAULT)
3139 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3139 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3140 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3140 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3141 version = Column('version', Integer(), nullable=False, default=0)
3141 version = Column('version', Integer(), nullable=False, default=0)
3142 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3142 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3143
3143
3144 author = relationship('User', lazy='joined')
3144 author = relationship('User', lazy='joined')
3145 repo = relationship('Repository')
3145 repo = relationship('Repository')
3146 comment = relationship('ChangesetComment', lazy='joined')
3146 comment = relationship('ChangesetComment', lazy='joined')
3147 pull_request = relationship('PullRequest', lazy='joined')
3147 pull_request = relationship('PullRequest', lazy='joined')
3148
3148
3149 def __unicode__(self):
3149 def __unicode__(self):
3150 return u"<%s('%s[v%s]:%s')>" % (
3150 return u"<%s('%s[v%s]:%s')>" % (
3151 self.__class__.__name__,
3151 self.__class__.__name__,
3152 self.status, self.version, self.author
3152 self.status, self.version, self.author
3153 )
3153 )
3154
3154
3155 @classmethod
3155 @classmethod
3156 def get_status_lbl(cls, value):
3156 def get_status_lbl(cls, value):
3157 return dict(cls.STATUSES).get(value)
3157 return dict(cls.STATUSES).get(value)
3158
3158
3159 @property
3159 @property
3160 def status_lbl(self):
3160 def status_lbl(self):
3161 return ChangesetStatus.get_status_lbl(self.status)
3161 return ChangesetStatus.get_status_lbl(self.status)
3162
3162
3163
3163
3164 class _PullRequestBase(BaseModel):
3164 class _PullRequestBase(BaseModel):
3165 """
3165 """
3166 Common attributes of pull request and version entries.
3166 Common attributes of pull request and version entries.
3167 """
3167 """
3168
3168
3169 # .status values
3169 # .status values
3170 STATUS_NEW = u'new'
3170 STATUS_NEW = u'new'
3171 STATUS_OPEN = u'open'
3171 STATUS_OPEN = u'open'
3172 STATUS_CLOSED = u'closed'
3172 STATUS_CLOSED = u'closed'
3173
3173
3174 title = Column('title', Unicode(255), nullable=True)
3174 title = Column('title', Unicode(255), nullable=True)
3175 description = Column(
3175 description = Column(
3176 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3176 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3177 nullable=True)
3177 nullable=True)
3178 # new/open/closed status of pull request (not approve/reject/etc)
3178 # new/open/closed status of pull request (not approve/reject/etc)
3179 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3179 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3180 created_on = Column(
3180 created_on = Column(
3181 'created_on', DateTime(timezone=False), nullable=False,
3181 'created_on', DateTime(timezone=False), nullable=False,
3182 default=datetime.datetime.now)
3182 default=datetime.datetime.now)
3183 updated_on = Column(
3183 updated_on = Column(
3184 'updated_on', DateTime(timezone=False), nullable=False,
3184 'updated_on', DateTime(timezone=False), nullable=False,
3185 default=datetime.datetime.now)
3185 default=datetime.datetime.now)
3186
3186
3187 @declared_attr
3187 @declared_attr
3188 def user_id(cls):
3188 def user_id(cls):
3189 return Column(
3189 return Column(
3190 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3190 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3191 unique=None)
3191 unique=None)
3192
3192
3193 # 500 revisions max
3193 # 500 revisions max
3194 _revisions = Column(
3194 _revisions = Column(
3195 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3195 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3196
3196
3197 @declared_attr
3197 @declared_attr
3198 def source_repo_id(cls):
3198 def source_repo_id(cls):
3199 # TODO: dan: rename column to source_repo_id
3199 # TODO: dan: rename column to source_repo_id
3200 return Column(
3200 return Column(
3201 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3201 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3202 nullable=False)
3202 nullable=False)
3203
3203
3204 source_ref = Column('org_ref', Unicode(255), nullable=False)
3204 source_ref = Column('org_ref', Unicode(255), nullable=False)
3205
3205
3206 @declared_attr
3206 @declared_attr
3207 def target_repo_id(cls):
3207 def target_repo_id(cls):
3208 # TODO: dan: rename column to target_repo_id
3208 # TODO: dan: rename column to target_repo_id
3209 return Column(
3209 return Column(
3210 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3210 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3211 nullable=False)
3211 nullable=False)
3212
3212
3213 target_ref = Column('other_ref', Unicode(255), nullable=False)
3213 target_ref = Column('other_ref', Unicode(255), nullable=False)
3214 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3214 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3215
3215
3216 # TODO: dan: rename column to last_merge_source_rev
3216 # TODO: dan: rename column to last_merge_source_rev
3217 _last_merge_source_rev = Column(
3217 _last_merge_source_rev = Column(
3218 'last_merge_org_rev', String(40), nullable=True)
3218 'last_merge_org_rev', String(40), nullable=True)
3219 # TODO: dan: rename column to last_merge_target_rev
3219 # TODO: dan: rename column to last_merge_target_rev
3220 _last_merge_target_rev = Column(
3220 _last_merge_target_rev = Column(
3221 'last_merge_other_rev', String(40), nullable=True)
3221 'last_merge_other_rev', String(40), nullable=True)
3222 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3222 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3223 merge_rev = Column('merge_rev', String(40), nullable=True)
3223 merge_rev = Column('merge_rev', String(40), nullable=True)
3224
3224
3225 @hybrid_property
3225 @hybrid_property
3226 def revisions(self):
3226 def revisions(self):
3227 return self._revisions.split(':') if self._revisions else []
3227 return self._revisions.split(':') if self._revisions else []
3228
3228
3229 @revisions.setter
3229 @revisions.setter
3230 def revisions(self, val):
3230 def revisions(self, val):
3231 self._revisions = ':'.join(val)
3231 self._revisions = ':'.join(val)
3232
3232
3233 @declared_attr
3233 @declared_attr
3234 def author(cls):
3234 def author(cls):
3235 return relationship('User', lazy='joined')
3235 return relationship('User', lazy='joined')
3236
3236
3237 @declared_attr
3237 @declared_attr
3238 def source_repo(cls):
3238 def source_repo(cls):
3239 return relationship(
3239 return relationship(
3240 'Repository',
3240 'Repository',
3241 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3241 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3242
3242
3243 @property
3243 @property
3244 def source_ref_parts(self):
3244 def source_ref_parts(self):
3245 return self.unicode_to_reference(self.source_ref)
3245 return self.unicode_to_reference(self.source_ref)
3246
3246
3247 @declared_attr
3247 @declared_attr
3248 def target_repo(cls):
3248 def target_repo(cls):
3249 return relationship(
3249 return relationship(
3250 'Repository',
3250 'Repository',
3251 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3251 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3252
3252
3253 @property
3253 @property
3254 def target_ref_parts(self):
3254 def target_ref_parts(self):
3255 return self.unicode_to_reference(self.target_ref)
3255 return self.unicode_to_reference(self.target_ref)
3256
3256
3257 @property
3257 @property
3258 def shadow_merge_ref(self):
3258 def shadow_merge_ref(self):
3259 return self.unicode_to_reference(self._shadow_merge_ref)
3259 return self.unicode_to_reference(self._shadow_merge_ref)
3260
3260
3261 @shadow_merge_ref.setter
3261 @shadow_merge_ref.setter
3262 def shadow_merge_ref(self, ref):
3262 def shadow_merge_ref(self, ref):
3263 self._shadow_merge_ref = self.reference_to_unicode(ref)
3263 self._shadow_merge_ref = self.reference_to_unicode(ref)
3264
3264
3265 def unicode_to_reference(self, raw):
3265 def unicode_to_reference(self, raw):
3266 """
3266 """
3267 Convert a unicode (or string) to a reference object.
3267 Convert a unicode (or string) to a reference object.
3268 If unicode evaluates to False it returns None.
3268 If unicode evaluates to False it returns None.
3269 """
3269 """
3270 if raw:
3270 if raw:
3271 refs = raw.split(':')
3271 refs = raw.split(':')
3272 return Reference(*refs)
3272 return Reference(*refs)
3273 else:
3273 else:
3274 return None
3274 return None
3275
3275
3276 def reference_to_unicode(self, ref):
3276 def reference_to_unicode(self, ref):
3277 """
3277 """
3278 Convert a reference object to unicode.
3278 Convert a reference object to unicode.
3279 If reference is None it returns None.
3279 If reference is None it returns None.
3280 """
3280 """
3281 if ref:
3281 if ref:
3282 return u':'.join(ref)
3282 return u':'.join(ref)
3283 else:
3283 else:
3284 return None
3284 return None
3285
3285
3286 def get_api_data(self):
3286 def get_api_data(self):
3287 from rhodecode.model.pull_request import PullRequestModel
3287 from rhodecode.model.pull_request import PullRequestModel
3288 pull_request = self
3288 pull_request = self
3289 merge_status = PullRequestModel().merge_status(pull_request)
3289 merge_status = PullRequestModel().merge_status(pull_request)
3290
3290
3291 pull_request_url = url(
3291 pull_request_url = url(
3292 'pullrequest_show', repo_name=self.target_repo.repo_name,
3292 'pullrequest_show', repo_name=self.target_repo.repo_name,
3293 pull_request_id=self.pull_request_id, qualified=True)
3293 pull_request_id=self.pull_request_id, qualified=True)
3294
3294
3295 merge_data = {
3295 merge_data = {
3296 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3296 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3297 'reference': (
3297 'reference': (
3298 pull_request.shadow_merge_ref._asdict()
3298 pull_request.shadow_merge_ref._asdict()
3299 if pull_request.shadow_merge_ref else None),
3299 if pull_request.shadow_merge_ref else None),
3300 }
3300 }
3301
3301
3302 data = {
3302 data = {
3303 'pull_request_id': pull_request.pull_request_id,
3303 'pull_request_id': pull_request.pull_request_id,
3304 'url': pull_request_url,
3304 'url': pull_request_url,
3305 'title': pull_request.title,
3305 'title': pull_request.title,
3306 'description': pull_request.description,
3306 'description': pull_request.description,
3307 'status': pull_request.status,
3307 'status': pull_request.status,
3308 'created_on': pull_request.created_on,
3308 'created_on': pull_request.created_on,
3309 'updated_on': pull_request.updated_on,
3309 'updated_on': pull_request.updated_on,
3310 'commit_ids': pull_request.revisions,
3310 'commit_ids': pull_request.revisions,
3311 'review_status': pull_request.calculated_review_status(),
3311 'review_status': pull_request.calculated_review_status(),
3312 'mergeable': {
3312 'mergeable': {
3313 'status': merge_status[0],
3313 'status': merge_status[0],
3314 'message': unicode(merge_status[1]),
3314 'message': unicode(merge_status[1]),
3315 },
3315 },
3316 'source': {
3316 'source': {
3317 'clone_url': pull_request.source_repo.clone_url(),
3317 'clone_url': pull_request.source_repo.clone_url(),
3318 'repository': pull_request.source_repo.repo_name,
3318 'repository': pull_request.source_repo.repo_name,
3319 'reference': {
3319 'reference': {
3320 'name': pull_request.source_ref_parts.name,
3320 'name': pull_request.source_ref_parts.name,
3321 'type': pull_request.source_ref_parts.type,
3321 'type': pull_request.source_ref_parts.type,
3322 'commit_id': pull_request.source_ref_parts.commit_id,
3322 'commit_id': pull_request.source_ref_parts.commit_id,
3323 },
3323 },
3324 },
3324 },
3325 'target': {
3325 'target': {
3326 'clone_url': pull_request.target_repo.clone_url(),
3326 'clone_url': pull_request.target_repo.clone_url(),
3327 'repository': pull_request.target_repo.repo_name,
3327 'repository': pull_request.target_repo.repo_name,
3328 'reference': {
3328 'reference': {
3329 'name': pull_request.target_ref_parts.name,
3329 'name': pull_request.target_ref_parts.name,
3330 'type': pull_request.target_ref_parts.type,
3330 'type': pull_request.target_ref_parts.type,
3331 'commit_id': pull_request.target_ref_parts.commit_id,
3331 'commit_id': pull_request.target_ref_parts.commit_id,
3332 },
3332 },
3333 },
3333 },
3334 'merge': merge_data,
3334 'merge': merge_data,
3335 'author': pull_request.author.get_api_data(include_secrets=False,
3335 'author': pull_request.author.get_api_data(include_secrets=False,
3336 details='basic'),
3336 details='basic'),
3337 'reviewers': [
3337 'reviewers': [
3338 {
3338 {
3339 'user': reviewer.get_api_data(include_secrets=False,
3339 'user': reviewer.get_api_data(include_secrets=False,
3340 details='basic'),
3340 details='basic'),
3341 'reasons': reasons,
3341 'reasons': reasons,
3342 'review_status': st[0][1].status if st else 'not_reviewed',
3342 'review_status': st[0][1].status if st else 'not_reviewed',
3343 }
3343 }
3344 for reviewer, reasons, st in pull_request.reviewers_statuses()
3344 for reviewer, reasons, st in pull_request.reviewers_statuses()
3345 ]
3345 ]
3346 }
3346 }
3347
3347
3348 return data
3348 return data
3349
3349
3350
3350
3351 class PullRequest(Base, _PullRequestBase):
3351 class PullRequest(Base, _PullRequestBase):
3352 __tablename__ = 'pull_requests'
3352 __tablename__ = 'pull_requests'
3353 __table_args__ = (
3353 __table_args__ = (
3354 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3354 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3355 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3355 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3356 )
3356 )
3357
3357
3358 pull_request_id = Column(
3358 pull_request_id = Column(
3359 'pull_request_id', Integer(), nullable=False, primary_key=True)
3359 'pull_request_id', Integer(), nullable=False, primary_key=True)
3360
3360
3361 def __repr__(self):
3361 def __repr__(self):
3362 if self.pull_request_id:
3362 if self.pull_request_id:
3363 return '<DB:PullRequest #%s>' % self.pull_request_id
3363 return '<DB:PullRequest #%s>' % self.pull_request_id
3364 else:
3364 else:
3365 return '<DB:PullRequest at %#x>' % id(self)
3365 return '<DB:PullRequest at %#x>' % id(self)
3366
3366
3367 reviewers = relationship('PullRequestReviewers',
3367 reviewers = relationship('PullRequestReviewers',
3368 cascade="all, delete, delete-orphan")
3368 cascade="all, delete, delete-orphan")
3369 statuses = relationship('ChangesetStatus')
3369 statuses = relationship('ChangesetStatus')
3370 comments = relationship('ChangesetComment',
3370 comments = relationship('ChangesetComment',
3371 cascade="all, delete, delete-orphan")
3371 cascade="all, delete, delete-orphan")
3372 versions = relationship('PullRequestVersion',
3372 versions = relationship('PullRequestVersion',
3373 cascade="all, delete, delete-orphan",
3373 cascade="all, delete, delete-orphan",
3374 lazy='dynamic')
3374 lazy='dynamic')
3375
3375
3376 @classmethod
3376 @classmethod
3377 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3377 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3378 internal_methods=None):
3378 internal_methods=None):
3379
3379
3380 class PullRequestDisplay(object):
3380 class PullRequestDisplay(object):
3381 """
3381 """
3382 Special object wrapper for showing PullRequest data via Versions
3382 Special object wrapper for showing PullRequest data via Versions
3383 It mimics PR object as close as possible. This is read only object
3383 It mimics PR object as close as possible. This is read only object
3384 just for display
3384 just for display
3385 """
3385 """
3386
3386
3387 def __init__(self, attrs, internal=None):
3387 def __init__(self, attrs, internal=None):
3388 self.attrs = attrs
3388 self.attrs = attrs
3389 # internal have priority over the given ones via attrs
3389 # internal have priority over the given ones via attrs
3390 self.internal = internal or ['versions']
3390 self.internal = internal or ['versions']
3391
3391
3392 def __getattr__(self, item):
3392 def __getattr__(self, item):
3393 if item in self.internal:
3393 if item in self.internal:
3394 return getattr(self, item)
3394 return getattr(self, item)
3395 try:
3395 try:
3396 return self.attrs[item]
3396 return self.attrs[item]
3397 except KeyError:
3397 except KeyError:
3398 raise AttributeError(
3398 raise AttributeError(
3399 '%s object has no attribute %s' % (self, item))
3399 '%s object has no attribute %s' % (self, item))
3400
3400
3401 def __repr__(self):
3401 def __repr__(self):
3402 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3402 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3403
3403
3404 def versions(self):
3404 def versions(self):
3405 return pull_request_obj.versions.order_by(
3405 return pull_request_obj.versions.order_by(
3406 PullRequestVersion.pull_request_version_id).all()
3406 PullRequestVersion.pull_request_version_id).all()
3407
3407
3408 def is_closed(self):
3408 def is_closed(self):
3409 return pull_request_obj.is_closed()
3409 return pull_request_obj.is_closed()
3410
3410
3411 @property
3411 @property
3412 def pull_request_version_id(self):
3412 def pull_request_version_id(self):
3413 return getattr(pull_request_obj, 'pull_request_version_id', None)
3413 return getattr(pull_request_obj, 'pull_request_version_id', None)
3414
3414
3415 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3415 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3416
3416
3417 attrs.author = StrictAttributeDict(
3417 attrs.author = StrictAttributeDict(
3418 pull_request_obj.author.get_api_data())
3418 pull_request_obj.author.get_api_data())
3419 if pull_request_obj.target_repo:
3419 if pull_request_obj.target_repo:
3420 attrs.target_repo = StrictAttributeDict(
3420 attrs.target_repo = StrictAttributeDict(
3421 pull_request_obj.target_repo.get_api_data())
3421 pull_request_obj.target_repo.get_api_data())
3422 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3422 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3423
3423
3424 if pull_request_obj.source_repo:
3424 if pull_request_obj.source_repo:
3425 attrs.source_repo = StrictAttributeDict(
3425 attrs.source_repo = StrictAttributeDict(
3426 pull_request_obj.source_repo.get_api_data())
3426 pull_request_obj.source_repo.get_api_data())
3427 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3427 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3428
3428
3429 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3429 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3430 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3430 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3431 attrs.revisions = pull_request_obj.revisions
3431 attrs.revisions = pull_request_obj.revisions
3432
3432
3433 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3433 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3434
3434
3435 return PullRequestDisplay(attrs, internal=internal_methods)
3435 return PullRequestDisplay(attrs, internal=internal_methods)
3436
3436
3437 def is_closed(self):
3437 def is_closed(self):
3438 return self.status == self.STATUS_CLOSED
3438 return self.status == self.STATUS_CLOSED
3439
3439
3440 def __json__(self):
3440 def __json__(self):
3441 return {
3441 return {
3442 'revisions': self.revisions,
3442 'revisions': self.revisions,
3443 }
3443 }
3444
3444
3445 def calculated_review_status(self):
3445 def calculated_review_status(self):
3446 from rhodecode.model.changeset_status import ChangesetStatusModel
3446 from rhodecode.model.changeset_status import ChangesetStatusModel
3447 return ChangesetStatusModel().calculated_review_status(self)
3447 return ChangesetStatusModel().calculated_review_status(self)
3448
3448
3449 def reviewers_statuses(self):
3449 def reviewers_statuses(self):
3450 from rhodecode.model.changeset_status import ChangesetStatusModel
3450 from rhodecode.model.changeset_status import ChangesetStatusModel
3451 return ChangesetStatusModel().reviewers_statuses(self)
3451 return ChangesetStatusModel().reviewers_statuses(self)
3452
3452
3453 @property
3453 @property
3454 def workspace_id(self):
3454 def workspace_id(self):
3455 from rhodecode.model.pull_request import PullRequestModel
3455 from rhodecode.model.pull_request import PullRequestModel
3456 return PullRequestModel()._workspace_id(self)
3456 return PullRequestModel()._workspace_id(self)
3457
3457
3458 def get_shadow_repo(self):
3458 def get_shadow_repo(self):
3459 workspace_id = self.workspace_id
3459 workspace_id = self.workspace_id
3460 vcs_obj = self.target_repo.scm_instance()
3460 vcs_obj = self.target_repo.scm_instance()
3461 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3461 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3462 workspace_id)
3462 workspace_id)
3463 return vcs_obj._get_shadow_instance(shadow_repository_path)
3463 return vcs_obj._get_shadow_instance(shadow_repository_path)
3464
3464
3465
3465
3466 class PullRequestVersion(Base, _PullRequestBase):
3466 class PullRequestVersion(Base, _PullRequestBase):
3467 __tablename__ = 'pull_request_versions'
3467 __tablename__ = 'pull_request_versions'
3468 __table_args__ = (
3468 __table_args__ = (
3469 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3469 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3470 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3470 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3471 )
3471 )
3472
3472
3473 pull_request_version_id = Column(
3473 pull_request_version_id = Column(
3474 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3474 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3475 pull_request_id = Column(
3475 pull_request_id = Column(
3476 'pull_request_id', Integer(),
3476 'pull_request_id', Integer(),
3477 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3477 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3478 pull_request = relationship('PullRequest')
3478 pull_request = relationship('PullRequest')
3479
3479
3480 def __repr__(self):
3480 def __repr__(self):
3481 if self.pull_request_version_id:
3481 if self.pull_request_version_id:
3482 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3482 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3483 else:
3483 else:
3484 return '<DB:PullRequestVersion at %#x>' % id(self)
3484 return '<DB:PullRequestVersion at %#x>' % id(self)
3485
3485
3486 @property
3486 @property
3487 def reviewers(self):
3487 def reviewers(self):
3488 return self.pull_request.reviewers
3488 return self.pull_request.reviewers
3489
3489
3490 @property
3490 @property
3491 def versions(self):
3491 def versions(self):
3492 return self.pull_request.versions
3492 return self.pull_request.versions
3493
3493
3494 def is_closed(self):
3494 def is_closed(self):
3495 # calculate from original
3495 # calculate from original
3496 return self.pull_request.status == self.STATUS_CLOSED
3496 return self.pull_request.status == self.STATUS_CLOSED
3497
3497
3498 def calculated_review_status(self):
3498 def calculated_review_status(self):
3499 return self.pull_request.calculated_review_status()
3499 return self.pull_request.calculated_review_status()
3500
3500
3501 def reviewers_statuses(self):
3501 def reviewers_statuses(self):
3502 return self.pull_request.reviewers_statuses()
3502 return self.pull_request.reviewers_statuses()
3503
3503
3504
3504
3505 class PullRequestReviewers(Base, BaseModel):
3505 class PullRequestReviewers(Base, BaseModel):
3506 __tablename__ = 'pull_request_reviewers'
3506 __tablename__ = 'pull_request_reviewers'
3507 __table_args__ = (
3507 __table_args__ = (
3508 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3508 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3509 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3509 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3510 )
3510 )
3511
3511
3512 def __init__(self, user=None, pull_request=None, reasons=None):
3512 def __init__(self, user=None, pull_request=None, reasons=None):
3513 self.user = user
3513 self.user = user
3514 self.pull_request = pull_request
3514 self.pull_request = pull_request
3515 self.reasons = reasons or []
3515 self.reasons = reasons or []
3516
3516
3517 @hybrid_property
3517 @hybrid_property
3518 def reasons(self):
3518 def reasons(self):
3519 if not self._reasons:
3519 if not self._reasons:
3520 return []
3520 return []
3521 return self._reasons
3521 return self._reasons
3522
3522
3523 @reasons.setter
3523 @reasons.setter
3524 def reasons(self, val):
3524 def reasons(self, val):
3525 val = val or []
3525 val = val or []
3526 if any(not isinstance(x, basestring) for x in val):
3526 if any(not isinstance(x, basestring) for x in val):
3527 raise Exception('invalid reasons type, must be list of strings')
3527 raise Exception('invalid reasons type, must be list of strings')
3528 self._reasons = val
3528 self._reasons = val
3529
3529
3530 pull_requests_reviewers_id = Column(
3530 pull_requests_reviewers_id = Column(
3531 'pull_requests_reviewers_id', Integer(), nullable=False,
3531 'pull_requests_reviewers_id', Integer(), nullable=False,
3532 primary_key=True)
3532 primary_key=True)
3533 pull_request_id = Column(
3533 pull_request_id = Column(
3534 "pull_request_id", Integer(),
3534 "pull_request_id", Integer(),
3535 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3535 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3536 user_id = Column(
3536 user_id = Column(
3537 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3537 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3538 _reasons = Column(
3538 _reasons = Column(
3539 'reason', MutationList.as_mutable(
3539 'reason', MutationList.as_mutable(
3540 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3540 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3541
3541
3542 user = relationship('User')
3542 user = relationship('User')
3543 pull_request = relationship('PullRequest')
3543 pull_request = relationship('PullRequest')
3544
3544
3545
3545
3546 class Notification(Base, BaseModel):
3546 class Notification(Base, BaseModel):
3547 __tablename__ = 'notifications'
3547 __tablename__ = 'notifications'
3548 __table_args__ = (
3548 __table_args__ = (
3549 Index('notification_type_idx', 'type'),
3549 Index('notification_type_idx', 'type'),
3550 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3550 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3551 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3551 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3552 )
3552 )
3553
3553
3554 TYPE_CHANGESET_COMMENT = u'cs_comment'
3554 TYPE_CHANGESET_COMMENT = u'cs_comment'
3555 TYPE_MESSAGE = u'message'
3555 TYPE_MESSAGE = u'message'
3556 TYPE_MENTION = u'mention'
3556 TYPE_MENTION = u'mention'
3557 TYPE_REGISTRATION = u'registration'
3557 TYPE_REGISTRATION = u'registration'
3558 TYPE_PULL_REQUEST = u'pull_request'
3558 TYPE_PULL_REQUEST = u'pull_request'
3559 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3559 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3560
3560
3561 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3561 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3562 subject = Column('subject', Unicode(512), nullable=True)
3562 subject = Column('subject', Unicode(512), nullable=True)
3563 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3563 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3564 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3564 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3565 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3565 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3566 type_ = Column('type', Unicode(255))
3566 type_ = Column('type', Unicode(255))
3567
3567
3568 created_by_user = relationship('User')
3568 created_by_user = relationship('User')
3569 notifications_to_users = relationship('UserNotification', lazy='joined',
3569 notifications_to_users = relationship('UserNotification', lazy='joined',
3570 cascade="all, delete, delete-orphan")
3570 cascade="all, delete, delete-orphan")
3571
3571
3572 @property
3572 @property
3573 def recipients(self):
3573 def recipients(self):
3574 return [x.user for x in UserNotification.query()\
3574 return [x.user for x in UserNotification.query()\
3575 .filter(UserNotification.notification == self)\
3575 .filter(UserNotification.notification == self)\
3576 .order_by(UserNotification.user_id.asc()).all()]
3576 .order_by(UserNotification.user_id.asc()).all()]
3577
3577
3578 @classmethod
3578 @classmethod
3579 def create(cls, created_by, subject, body, recipients, type_=None):
3579 def create(cls, created_by, subject, body, recipients, type_=None):
3580 if type_ is None:
3580 if type_ is None:
3581 type_ = Notification.TYPE_MESSAGE
3581 type_ = Notification.TYPE_MESSAGE
3582
3582
3583 notification = cls()
3583 notification = cls()
3584 notification.created_by_user = created_by
3584 notification.created_by_user = created_by
3585 notification.subject = subject
3585 notification.subject = subject
3586 notification.body = body
3586 notification.body = body
3587 notification.type_ = type_
3587 notification.type_ = type_
3588 notification.created_on = datetime.datetime.now()
3588 notification.created_on = datetime.datetime.now()
3589
3589
3590 for u in recipients:
3590 for u in recipients:
3591 assoc = UserNotification()
3591 assoc = UserNotification()
3592 assoc.notification = notification
3592 assoc.notification = notification
3593
3593
3594 # if created_by is inside recipients mark his notification
3594 # if created_by is inside recipients mark his notification
3595 # as read
3595 # as read
3596 if u.user_id == created_by.user_id:
3596 if u.user_id == created_by.user_id:
3597 assoc.read = True
3597 assoc.read = True
3598
3598
3599 u.notifications.append(assoc)
3599 u.notifications.append(assoc)
3600 Session().add(notification)
3600 Session().add(notification)
3601
3601
3602 return notification
3602 return notification
3603
3603
3604 @property
3604 @property
3605 def description(self):
3605 def description(self):
3606 from rhodecode.model.notification import NotificationModel
3606 from rhodecode.model.notification import NotificationModel
3607 return NotificationModel().make_description(self)
3607 return NotificationModel().make_description(self)
3608
3608
3609
3609
3610 class UserNotification(Base, BaseModel):
3610 class UserNotification(Base, BaseModel):
3611 __tablename__ = 'user_to_notification'
3611 __tablename__ = 'user_to_notification'
3612 __table_args__ = (
3612 __table_args__ = (
3613 UniqueConstraint('user_id', 'notification_id'),
3613 UniqueConstraint('user_id', 'notification_id'),
3614 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3614 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3615 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3615 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3616 )
3616 )
3617 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3617 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3618 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3618 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3619 read = Column('read', Boolean, default=False)
3619 read = Column('read', Boolean, default=False)
3620 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3620 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3621
3621
3622 user = relationship('User', lazy="joined")
3622 user = relationship('User', lazy="joined")
3623 notification = relationship('Notification', lazy="joined",
3623 notification = relationship('Notification', lazy="joined",
3624 order_by=lambda: Notification.created_on.desc(),)
3624 order_by=lambda: Notification.created_on.desc(),)
3625
3625
3626 def mark_as_read(self):
3626 def mark_as_read(self):
3627 self.read = True
3627 self.read = True
3628 Session().add(self)
3628 Session().add(self)
3629
3629
3630
3630
3631 class Gist(Base, BaseModel):
3631 class Gist(Base, BaseModel):
3632 __tablename__ = 'gists'
3632 __tablename__ = 'gists'
3633 __table_args__ = (
3633 __table_args__ = (
3634 Index('g_gist_access_id_idx', 'gist_access_id'),
3634 Index('g_gist_access_id_idx', 'gist_access_id'),
3635 Index('g_created_on_idx', 'created_on'),
3635 Index('g_created_on_idx', 'created_on'),
3636 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3636 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3637 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3637 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3638 )
3638 )
3639 GIST_PUBLIC = u'public'
3639 GIST_PUBLIC = u'public'
3640 GIST_PRIVATE = u'private'
3640 GIST_PRIVATE = u'private'
3641 DEFAULT_FILENAME = u'gistfile1.txt'
3641 DEFAULT_FILENAME = u'gistfile1.txt'
3642
3642
3643 ACL_LEVEL_PUBLIC = u'acl_public'
3643 ACL_LEVEL_PUBLIC = u'acl_public'
3644 ACL_LEVEL_PRIVATE = u'acl_private'
3644 ACL_LEVEL_PRIVATE = u'acl_private'
3645
3645
3646 gist_id = Column('gist_id', Integer(), primary_key=True)
3646 gist_id = Column('gist_id', Integer(), primary_key=True)
3647 gist_access_id = Column('gist_access_id', Unicode(250))
3647 gist_access_id = Column('gist_access_id', Unicode(250))
3648 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3648 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3649 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3649 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3650 gist_expires = Column('gist_expires', Float(53), nullable=False)
3650 gist_expires = Column('gist_expires', Float(53), nullable=False)
3651 gist_type = Column('gist_type', Unicode(128), nullable=False)
3651 gist_type = Column('gist_type', Unicode(128), nullable=False)
3652 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3652 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3653 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3653 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3654 acl_level = Column('acl_level', Unicode(128), nullable=True)
3654 acl_level = Column('acl_level', Unicode(128), nullable=True)
3655
3655
3656 owner = relationship('User')
3656 owner = relationship('User')
3657
3657
3658 def __repr__(self):
3658 def __repr__(self):
3659 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3659 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3660
3660
3661 @classmethod
3661 @classmethod
3662 def get_or_404(cls, id_, pyramid_exc=False):
3662 def get_or_404(cls, id_, pyramid_exc=False):
3663
3663
3664 if pyramid_exc:
3664 if pyramid_exc:
3665 from pyramid.httpexceptions import HTTPNotFound
3665 from pyramid.httpexceptions import HTTPNotFound
3666 else:
3666 else:
3667 from webob.exc import HTTPNotFound
3667 from webob.exc import HTTPNotFound
3668
3668
3669 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3669 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3670 if not res:
3670 if not res:
3671 raise HTTPNotFound
3671 raise HTTPNotFound
3672 return res
3672 return res
3673
3673
3674 @classmethod
3674 @classmethod
3675 def get_by_access_id(cls, gist_access_id):
3675 def get_by_access_id(cls, gist_access_id):
3676 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3676 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3677
3677
3678 def gist_url(self):
3678 def gist_url(self):
3679 import rhodecode
3679 import rhodecode
3680 alias_url = rhodecode.CONFIG.get('gist_alias_url')
3680 alias_url = rhodecode.CONFIG.get('gist_alias_url')
3681 if alias_url:
3681 if alias_url:
3682 return alias_url.replace('{gistid}', self.gist_access_id)
3682 return alias_url.replace('{gistid}', self.gist_access_id)
3683
3683
3684 return url('gist', gist_id=self.gist_access_id, qualified=True)
3684 return url('gist', gist_id=self.gist_access_id, qualified=True)
3685
3685
3686 @classmethod
3686 @classmethod
3687 def base_path(cls):
3687 def base_path(cls):
3688 """
3688 """
3689 Returns base path when all gists are stored
3689 Returns base path when all gists are stored
3690
3690
3691 :param cls:
3691 :param cls:
3692 """
3692 """
3693 from rhodecode.model.gist import GIST_STORE_LOC
3693 from rhodecode.model.gist import GIST_STORE_LOC
3694 q = Session().query(RhodeCodeUi)\
3694 q = Session().query(RhodeCodeUi)\
3695 .filter(RhodeCodeUi.ui_key == URL_SEP)
3695 .filter(RhodeCodeUi.ui_key == URL_SEP)
3696 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3696 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3697 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3697 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3698
3698
3699 def get_api_data(self):
3699 def get_api_data(self):
3700 """
3700 """
3701 Common function for generating gist related data for API
3701 Common function for generating gist related data for API
3702 """
3702 """
3703 gist = self
3703 gist = self
3704 data = {
3704 data = {
3705 'gist_id': gist.gist_id,
3705 'gist_id': gist.gist_id,
3706 'type': gist.gist_type,
3706 'type': gist.gist_type,
3707 'access_id': gist.gist_access_id,
3707 'access_id': gist.gist_access_id,
3708 'description': gist.gist_description,
3708 'description': gist.gist_description,
3709 'url': gist.gist_url(),
3709 'url': gist.gist_url(),
3710 'expires': gist.gist_expires,
3710 'expires': gist.gist_expires,
3711 'created_on': gist.created_on,
3711 'created_on': gist.created_on,
3712 'modified_at': gist.modified_at,
3712 'modified_at': gist.modified_at,
3713 'content': None,
3713 'content': None,
3714 'acl_level': gist.acl_level,
3714 'acl_level': gist.acl_level,
3715 }
3715 }
3716 return data
3716 return data
3717
3717
3718 def __json__(self):
3718 def __json__(self):
3719 data = dict(
3719 data = dict(
3720 )
3720 )
3721 data.update(self.get_api_data())
3721 data.update(self.get_api_data())
3722 return data
3722 return data
3723 # SCM functions
3723 # SCM functions
3724
3724
3725 def scm_instance(self, **kwargs):
3725 def scm_instance(self, **kwargs):
3726 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3726 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3727 return get_vcs_instance(
3727 return get_vcs_instance(
3728 repo_path=safe_str(full_repo_path), create=False)
3728 repo_path=safe_str(full_repo_path), create=False)
3729
3729
3730
3730
3731 class ExternalIdentity(Base, BaseModel):
3731 class ExternalIdentity(Base, BaseModel):
3732 __tablename__ = 'external_identities'
3732 __tablename__ = 'external_identities'
3733 __table_args__ = (
3733 __table_args__ = (
3734 Index('local_user_id_idx', 'local_user_id'),
3734 Index('local_user_id_idx', 'local_user_id'),
3735 Index('external_id_idx', 'external_id'),
3735 Index('external_id_idx', 'external_id'),
3736 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3736 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3737 'mysql_charset': 'utf8'})
3737 'mysql_charset': 'utf8'})
3738
3738
3739 external_id = Column('external_id', Unicode(255), default=u'',
3739 external_id = Column('external_id', Unicode(255), default=u'',
3740 primary_key=True)
3740 primary_key=True)
3741 external_username = Column('external_username', Unicode(1024), default=u'')
3741 external_username = Column('external_username', Unicode(1024), default=u'')
3742 local_user_id = Column('local_user_id', Integer(),
3742 local_user_id = Column('local_user_id', Integer(),
3743 ForeignKey('users.user_id'), primary_key=True)
3743 ForeignKey('users.user_id'), primary_key=True)
3744 provider_name = Column('provider_name', Unicode(255), default=u'',
3744 provider_name = Column('provider_name', Unicode(255), default=u'',
3745 primary_key=True)
3745 primary_key=True)
3746 access_token = Column('access_token', String(1024), default=u'')
3746 access_token = Column('access_token', String(1024), default=u'')
3747 alt_token = Column('alt_token', String(1024), default=u'')
3747 alt_token = Column('alt_token', String(1024), default=u'')
3748 token_secret = Column('token_secret', String(1024), default=u'')
3748 token_secret = Column('token_secret', String(1024), default=u'')
3749
3749
3750 @classmethod
3750 @classmethod
3751 def by_external_id_and_provider(cls, external_id, provider_name,
3751 def by_external_id_and_provider(cls, external_id, provider_name,
3752 local_user_id=None):
3752 local_user_id=None):
3753 """
3753 """
3754 Returns ExternalIdentity instance based on search params
3754 Returns ExternalIdentity instance based on search params
3755
3755
3756 :param external_id:
3756 :param external_id:
3757 :param provider_name:
3757 :param provider_name:
3758 :return: ExternalIdentity
3758 :return: ExternalIdentity
3759 """
3759 """
3760 query = cls.query()
3760 query = cls.query()
3761 query = query.filter(cls.external_id == external_id)
3761 query = query.filter(cls.external_id == external_id)
3762 query = query.filter(cls.provider_name == provider_name)
3762 query = query.filter(cls.provider_name == provider_name)
3763 if local_user_id:
3763 if local_user_id:
3764 query = query.filter(cls.local_user_id == local_user_id)
3764 query = query.filter(cls.local_user_id == local_user_id)
3765 return query.first()
3765 return query.first()
3766
3766
3767 @classmethod
3767 @classmethod
3768 def user_by_external_id_and_provider(cls, external_id, provider_name):
3768 def user_by_external_id_and_provider(cls, external_id, provider_name):
3769 """
3769 """
3770 Returns User instance based on search params
3770 Returns User instance based on search params
3771
3771
3772 :param external_id:
3772 :param external_id:
3773 :param provider_name:
3773 :param provider_name:
3774 :return: User
3774 :return: User
3775 """
3775 """
3776 query = User.query()
3776 query = User.query()
3777 query = query.filter(cls.external_id == external_id)
3777 query = query.filter(cls.external_id == external_id)
3778 query = query.filter(cls.provider_name == provider_name)
3778 query = query.filter(cls.provider_name == provider_name)
3779 query = query.filter(User.user_id == cls.local_user_id)
3779 query = query.filter(User.user_id == cls.local_user_id)
3780 return query.first()
3780 return query.first()
3781
3781
3782 @classmethod
3782 @classmethod
3783 def by_local_user_id(cls, local_user_id):
3783 def by_local_user_id(cls, local_user_id):
3784 """
3784 """
3785 Returns all tokens for user
3785 Returns all tokens for user
3786
3786
3787 :param local_user_id:
3787 :param local_user_id:
3788 :return: ExternalIdentity
3788 :return: ExternalIdentity
3789 """
3789 """
3790 query = cls.query()
3790 query = cls.query()
3791 query = query.filter(cls.local_user_id == local_user_id)
3791 query = query.filter(cls.local_user_id == local_user_id)
3792 return query
3792 return query
3793
3793
3794
3794
3795 class Integration(Base, BaseModel):
3795 class Integration(Base, BaseModel):
3796 __tablename__ = 'integrations'
3796 __tablename__ = 'integrations'
3797 __table_args__ = (
3797 __table_args__ = (
3798 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3798 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3799 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3799 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3800 )
3800 )
3801
3801
3802 integration_id = Column('integration_id', Integer(), primary_key=True)
3802 integration_id = Column('integration_id', Integer(), primary_key=True)
3803 integration_type = Column('integration_type', String(255))
3803 integration_type = Column('integration_type', String(255))
3804 enabled = Column('enabled', Boolean(), nullable=False)
3804 enabled = Column('enabled', Boolean(), nullable=False)
3805 name = Column('name', String(255), nullable=False)
3805 name = Column('name', String(255), nullable=False)
3806 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
3806 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
3807 default=False)
3807 default=False)
3808
3808
3809 settings = Column(
3809 settings = Column(
3810 'settings_json', MutationObj.as_mutable(
3810 'settings_json', MutationObj.as_mutable(
3811 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3811 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3812 repo_id = Column(
3812 repo_id = Column(
3813 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
3813 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
3814 nullable=True, unique=None, default=None)
3814 nullable=True, unique=None, default=None)
3815 repo = relationship('Repository', lazy='joined')
3815 repo = relationship('Repository', lazy='joined')
3816
3816
3817 repo_group_id = Column(
3817 repo_group_id = Column(
3818 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
3818 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
3819 nullable=True, unique=None, default=None)
3819 nullable=True, unique=None, default=None)
3820 repo_group = relationship('RepoGroup', lazy='joined')
3820 repo_group = relationship('RepoGroup', lazy='joined')
3821
3821
3822 @property
3822 @property
3823 def scope(self):
3823 def scope(self):
3824 if self.repo:
3824 if self.repo:
3825 return repr(self.repo)
3825 return repr(self.repo)
3826 if self.repo_group:
3826 if self.repo_group:
3827 if self.child_repos_only:
3827 if self.child_repos_only:
3828 return repr(self.repo_group) + ' (child repos only)'
3828 return repr(self.repo_group) + ' (child repos only)'
3829 else:
3829 else:
3830 return repr(self.repo_group) + ' (recursive)'
3830 return repr(self.repo_group) + ' (recursive)'
3831 if self.child_repos_only:
3831 if self.child_repos_only:
3832 return 'root_repos'
3832 return 'root_repos'
3833 return 'global'
3833 return 'global'
3834
3834
3835 def __repr__(self):
3835 def __repr__(self):
3836 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
3836 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
3837
3837
3838
3838
3839 class RepoReviewRuleUser(Base, BaseModel):
3839 class RepoReviewRuleUser(Base, BaseModel):
3840 __tablename__ = 'repo_review_rules_users'
3840 __tablename__ = 'repo_review_rules_users'
3841 __table_args__ = (
3841 __table_args__ = (
3842 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3842 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3843 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3843 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3844 )
3844 )
3845 repo_review_rule_user_id = Column(
3845 repo_review_rule_user_id = Column(
3846 'repo_review_rule_user_id', Integer(), primary_key=True)
3846 'repo_review_rule_user_id', Integer(), primary_key=True)
3847 repo_review_rule_id = Column("repo_review_rule_id",
3847 repo_review_rule_id = Column("repo_review_rule_id",
3848 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3848 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3849 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'),
3849 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'),
3850 nullable=False)
3850 nullable=False)
3851 user = relationship('User')
3851 user = relationship('User')
3852
3852
3853
3853
3854 class RepoReviewRuleUserGroup(Base, BaseModel):
3854 class RepoReviewRuleUserGroup(Base, BaseModel):
3855 __tablename__ = 'repo_review_rules_users_groups'
3855 __tablename__ = 'repo_review_rules_users_groups'
3856 __table_args__ = (
3856 __table_args__ = (
3857 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3857 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3858 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3858 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3859 )
3859 )
3860 repo_review_rule_users_group_id = Column(
3860 repo_review_rule_users_group_id = Column(
3861 'repo_review_rule_users_group_id', Integer(), primary_key=True)
3861 'repo_review_rule_users_group_id', Integer(), primary_key=True)
3862 repo_review_rule_id = Column("repo_review_rule_id",
3862 repo_review_rule_id = Column("repo_review_rule_id",
3863 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3863 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3864 users_group_id = Column("users_group_id", Integer(),
3864 users_group_id = Column("users_group_id", Integer(),
3865 ForeignKey('users_groups.users_group_id'), nullable=False)
3865 ForeignKey('users_groups.users_group_id'), nullable=False)
3866 users_group = relationship('UserGroup')
3866 users_group = relationship('UserGroup')
3867
3867
3868
3868
3869 class RepoReviewRule(Base, BaseModel):
3869 class RepoReviewRule(Base, BaseModel):
3870 __tablename__ = 'repo_review_rules'
3870 __tablename__ = 'repo_review_rules'
3871 __table_args__ = (
3871 __table_args__ = (
3872 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3872 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3873 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3873 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3874 )
3874 )
3875
3875
3876 repo_review_rule_id = Column(
3876 repo_review_rule_id = Column(
3877 'repo_review_rule_id', Integer(), primary_key=True)
3877 'repo_review_rule_id', Integer(), primary_key=True)
3878 repo_id = Column(
3878 repo_id = Column(
3879 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
3879 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
3880 repo = relationship('Repository', backref='review_rules')
3880 repo = relationship('Repository', backref='review_rules')
3881
3881
3882 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3882 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3883 default=u'*') # glob
3883 default=u'*') # glob
3884 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3884 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3885 default=u'*') # glob
3885 default=u'*') # glob
3886
3886
3887 use_authors_for_review = Column("use_authors_for_review", Boolean(),
3887 use_authors_for_review = Column("use_authors_for_review", Boolean(),
3888 nullable=False, default=False)
3888 nullable=False, default=False)
3889 rule_users = relationship('RepoReviewRuleUser')
3889 rule_users = relationship('RepoReviewRuleUser')
3890 rule_user_groups = relationship('RepoReviewRuleUserGroup')
3890 rule_user_groups = relationship('RepoReviewRuleUserGroup')
3891
3891
3892 @hybrid_property
3892 @hybrid_property
3893 def branch_pattern(self):
3893 def branch_pattern(self):
3894 return self._branch_pattern or '*'
3894 return self._branch_pattern or '*'
3895
3895
3896 def _validate_glob(self, value):
3896 def _validate_glob(self, value):
3897 re.compile('^' + glob2re(value) + '$')
3897 re.compile('^' + glob2re(value) + '$')
3898
3898
3899 @branch_pattern.setter
3899 @branch_pattern.setter
3900 def branch_pattern(self, value):
3900 def branch_pattern(self, value):
3901 self._validate_glob(value)
3901 self._validate_glob(value)
3902 self._branch_pattern = value or '*'
3902 self._branch_pattern = value or '*'
3903
3903
3904 @hybrid_property
3904 @hybrid_property
3905 def file_pattern(self):
3905 def file_pattern(self):
3906 return self._file_pattern or '*'
3906 return self._file_pattern or '*'
3907
3907
3908 @file_pattern.setter
3908 @file_pattern.setter
3909 def file_pattern(self, value):
3909 def file_pattern(self, value):
3910 self._validate_glob(value)
3910 self._validate_glob(value)
3911 self._file_pattern = value or '*'
3911 self._file_pattern = value or '*'
3912
3912
3913 def matches(self, branch, files_changed):
3913 def matches(self, branch, files_changed):
3914 """
3914 """
3915 Check if this review rule matches a branch/files in a pull request
3915 Check if this review rule matches a branch/files in a pull request
3916
3916
3917 :param branch: branch name for the commit
3917 :param branch: branch name for the commit
3918 :param files_changed: list of file paths changed in the pull request
3918 :param files_changed: list of file paths changed in the pull request
3919 """
3919 """
3920
3920
3921 branch = branch or ''
3921 branch = branch or ''
3922 files_changed = files_changed or []
3922 files_changed = files_changed or []
3923
3923
3924 branch_matches = True
3924 branch_matches = True
3925 if branch:
3925 if branch:
3926 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
3926 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
3927 branch_matches = bool(branch_regex.search(branch))
3927 branch_matches = bool(branch_regex.search(branch))
3928
3928
3929 files_matches = True
3929 files_matches = True
3930 if self.file_pattern != '*':
3930 if self.file_pattern != '*':
3931 files_matches = False
3931 files_matches = False
3932 file_regex = re.compile(glob2re(self.file_pattern))
3932 file_regex = re.compile(glob2re(self.file_pattern))
3933 for filename in files_changed:
3933 for filename in files_changed:
3934 if file_regex.search(filename):
3934 if file_regex.search(filename):
3935 files_matches = True
3935 files_matches = True
3936 break
3936 break
3937
3937
3938 return branch_matches and files_matches
3938 return branch_matches and files_matches
3939
3939
3940 @property
3940 @property
3941 def review_users(self):
3941 def review_users(self):
3942 """ Returns the users which this rule applies to """
3942 """ Returns the users which this rule applies to """
3943
3943
3944 users = set()
3944 users = set()
3945 users |= set([
3945 users |= set([
3946 rule_user.user for rule_user in self.rule_users
3946 rule_user.user for rule_user in self.rule_users
3947 if rule_user.user.active])
3947 if rule_user.user.active])
3948 users |= set(
3948 users |= set(
3949 member.user
3949 member.user
3950 for rule_user_group in self.rule_user_groups
3950 for rule_user_group in self.rule_user_groups
3951 for member in rule_user_group.users_group.members
3951 for member in rule_user_group.users_group.members
3952 if member.user.active
3952 if member.user.active
3953 )
3953 )
3954 return users
3954 return users
3955
3955
3956 def __repr__(self):
3956 def __repr__(self):
3957 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
3957 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
3958 self.repo_review_rule_id, self.repo)
3958 self.repo_review_rule_id, self.repo)
3959
3959
3960
3960
3961 class DbMigrateVersion(Base, BaseModel):
3961 class DbMigrateVersion(Base, BaseModel):
3962 __tablename__ = 'db_migrate_version'
3962 __tablename__ = 'db_migrate_version'
3963 __table_args__ = (
3963 __table_args__ = (
3964 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3964 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3965 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3965 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3966 )
3966 )
3967 repository_id = Column('repository_id', String(250), primary_key=True)
3967 repository_id = Column('repository_id', String(250), primary_key=True)
3968 repository_path = Column('repository_path', Text)
3968 repository_path = Column('repository_path', Text)
3969 version = Column('version', Integer)
3969 version = Column('version', Integer)
3970
3970
3971
3971
3972 class DbSession(Base, BaseModel):
3972 class DbSession(Base, BaseModel):
3973 __tablename__ = 'db_session'
3973 __tablename__ = 'db_session'
3974 __table_args__ = (
3974 __table_args__ = (
3975 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3975 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3976 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3976 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3977 )
3977 )
3978
3978
3979 def __repr__(self):
3979 def __repr__(self):
3980 return '<DB:DbSession({})>'.format(self.id)
3980 return '<DB:DbSession({})>'.format(self.id)
3981
3981
3982 id = Column('id', Integer())
3982 id = Column('id', Integer())
3983 namespace = Column('namespace', String(255), primary_key=True)
3983 namespace = Column('namespace', String(255), primary_key=True)
3984 accessed = Column('accessed', DateTime, nullable=False)
3984 accessed = Column('accessed', DateTime, nullable=False)
3985 created = Column('created', DateTime, nullable=False)
3985 created = Column('created', DateTime, nullable=False)
3986 data = Column('data', PickleType, nullable=False)
3986 data = Column('data', PickleType, nullable=False)
@@ -1,999 +1,993 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 Repository model for rhodecode
22 Repository model for rhodecode
23 """
23 """
24
24
25 import logging
25 import logging
26 import os
26 import os
27 import re
27 import re
28 import shutil
28 import shutil
29 import time
29 import time
30 import traceback
30 import traceback
31 from datetime import datetime, timedelta
31 from datetime import datetime, timedelta
32
32
33 from zope.cachedescriptors.property import Lazy as LazyProperty
33 from zope.cachedescriptors.property import Lazy as LazyProperty
34
34
35 from rhodecode import events
35 from rhodecode import events
36 from rhodecode.lib import helpers as h
36 from rhodecode.lib import helpers as h
37 from rhodecode.lib.auth import HasUserGroupPermissionAny
37 from rhodecode.lib.auth import HasUserGroupPermissionAny
38 from rhodecode.lib.caching_query import FromCache
38 from rhodecode.lib.caching_query import FromCache
39 from rhodecode.lib.exceptions import AttachedForksError
39 from rhodecode.lib.exceptions import AttachedForksError
40 from rhodecode.lib.hooks_base import log_delete_repository
40 from rhodecode.lib.hooks_base import log_delete_repository
41 from rhodecode.lib.utils import make_db_config
41 from rhodecode.lib.utils import make_db_config
42 from rhodecode.lib.utils2 import (
42 from rhodecode.lib.utils2 import (
43 safe_str, safe_unicode, remove_prefix, obfuscate_url_pw,
43 safe_str, safe_unicode, remove_prefix, obfuscate_url_pw,
44 get_current_rhodecode_user, safe_int, datetime_to_time, action_logger_generic)
44 get_current_rhodecode_user, safe_int, datetime_to_time, action_logger_generic)
45 from rhodecode.lib.vcs.backends import get_backend
45 from rhodecode.lib.vcs.backends import get_backend
46 from rhodecode.model import BaseModel
46 from rhodecode.model import BaseModel
47 from rhodecode.model.db import (
47 from rhodecode.model.db import (
48 Repository, UserRepoToPerm, UserGroupRepoToPerm, UserRepoGroupToPerm,
48 Repository, UserRepoToPerm, UserGroupRepoToPerm, UserRepoGroupToPerm,
49 UserGroupRepoGroupToPerm, User, Permission, Statistics, UserGroup,
49 UserGroupRepoGroupToPerm, User, Permission, Statistics, UserGroup,
50 RepoGroup, RepositoryField)
50 RepoGroup, RepositoryField)
51
51
52 from rhodecode.model.settings import VcsSettingsModel
52 from rhodecode.model.settings import VcsSettingsModel
53
53
54
54
55 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
56
56
57
57
58 class RepoModel(BaseModel):
58 class RepoModel(BaseModel):
59
59
60 cls = Repository
60 cls = Repository
61
61
62 def _get_user_group(self, users_group):
62 def _get_user_group(self, users_group):
63 return self._get_instance(UserGroup, users_group,
63 return self._get_instance(UserGroup, users_group,
64 callback=UserGroup.get_by_group_name)
64 callback=UserGroup.get_by_group_name)
65
65
66 def _get_repo_group(self, repo_group):
66 def _get_repo_group(self, repo_group):
67 return self._get_instance(RepoGroup, repo_group,
67 return self._get_instance(RepoGroup, repo_group,
68 callback=RepoGroup.get_by_group_name)
68 callback=RepoGroup.get_by_group_name)
69
69
70 def _create_default_perms(self, repository, private):
70 def _create_default_perms(self, repository, private):
71 # create default permission
71 # create default permission
72 default = 'repository.read'
72 default = 'repository.read'
73 def_user = User.get_default_user()
73 def_user = User.get_default_user()
74 for p in def_user.user_perms:
74 for p in def_user.user_perms:
75 if p.permission.permission_name.startswith('repository.'):
75 if p.permission.permission_name.startswith('repository.'):
76 default = p.permission.permission_name
76 default = p.permission.permission_name
77 break
77 break
78
78
79 default_perm = 'repository.none' if private else default
79 default_perm = 'repository.none' if private else default
80
80
81 repo_to_perm = UserRepoToPerm()
81 repo_to_perm = UserRepoToPerm()
82 repo_to_perm.permission = Permission.get_by_key(default_perm)
82 repo_to_perm.permission = Permission.get_by_key(default_perm)
83
83
84 repo_to_perm.repository = repository
84 repo_to_perm.repository = repository
85 repo_to_perm.user_id = def_user.user_id
85 repo_to_perm.user_id = def_user.user_id
86
86
87 return repo_to_perm
87 return repo_to_perm
88
88
89 @LazyProperty
89 @LazyProperty
90 def repos_path(self):
90 def repos_path(self):
91 """
91 """
92 Gets the repositories root path from database
92 Gets the repositories root path from database
93 """
93 """
94 settings_model = VcsSettingsModel(sa=self.sa)
94 settings_model = VcsSettingsModel(sa=self.sa)
95 return settings_model.get_repos_location()
95 return settings_model.get_repos_location()
96
96
97 def get(self, repo_id, cache=False):
97 def get(self, repo_id, cache=False):
98 repo = self.sa.query(Repository) \
98 repo = self.sa.query(Repository) \
99 .filter(Repository.repo_id == repo_id)
99 .filter(Repository.repo_id == repo_id)
100
100
101 if cache:
101 if cache:
102 repo = repo.options(FromCache("sql_cache_short",
102 repo = repo.options(FromCache("sql_cache_short",
103 "get_repo_%s" % repo_id))
103 "get_repo_%s" % repo_id))
104 return repo.scalar()
104 return repo.scalar()
105
105
106 def get_repo(self, repository):
106 def get_repo(self, repository):
107 return self._get_repo(repository)
107 return self._get_repo(repository)
108
108
109 def get_by_repo_name(self, repo_name, cache=False):
109 def get_by_repo_name(self, repo_name, cache=False):
110 repo = self.sa.query(Repository) \
110 repo = self.sa.query(Repository) \
111 .filter(Repository.repo_name == repo_name)
111 .filter(Repository.repo_name == repo_name)
112
112
113 if cache:
113 if cache:
114 repo = repo.options(FromCache("sql_cache_short",
114 repo = repo.options(FromCache("sql_cache_short",
115 "get_repo_%s" % repo_name))
115 "get_repo_%s" % repo_name))
116 return repo.scalar()
116 return repo.scalar()
117
117
118 def _extract_id_from_repo_name(self, repo_name):
118 def _extract_id_from_repo_name(self, repo_name):
119 if repo_name.startswith('/'):
119 if repo_name.startswith('/'):
120 repo_name = repo_name.lstrip('/')
120 repo_name = repo_name.lstrip('/')
121 by_id_match = re.match(r'^_(\d{1,})', repo_name)
121 by_id_match = re.match(r'^_(\d{1,})', repo_name)
122 if by_id_match:
122 if by_id_match:
123 return by_id_match.groups()[0]
123 return by_id_match.groups()[0]
124
124
125 def get_repo_by_id(self, repo_name):
125 def get_repo_by_id(self, repo_name):
126 """
126 """
127 Extracts repo_name by id from special urls.
127 Extracts repo_name by id from special urls.
128 Example url is _11/repo_name
128 Example url is _11/repo_name
129
129
130 :param repo_name:
130 :param repo_name:
131 :return: repo object if matched else None
131 :return: repo object if matched else None
132 """
132 """
133 try:
133 try:
134 _repo_id = self._extract_id_from_repo_name(repo_name)
134 _repo_id = self._extract_id_from_repo_name(repo_name)
135 if _repo_id:
135 if _repo_id:
136 return self.get(_repo_id)
136 return self.get(_repo_id)
137 except Exception:
137 except Exception:
138 log.exception('Failed to extract repo_name from URL')
138 log.exception('Failed to extract repo_name from URL')
139
139
140 return None
140 return None
141
141
142 def get_repos_for_root(self, root, traverse=False):
142 def get_repos_for_root(self, root, traverse=False):
143 if traverse:
143 if traverse:
144 like_expression = u'{}%'.format(safe_unicode(root))
144 like_expression = u'{}%'.format(safe_unicode(root))
145 repos = Repository.query().filter(
145 repos = Repository.query().filter(
146 Repository.repo_name.like(like_expression)).all()
146 Repository.repo_name.like(like_expression)).all()
147 else:
147 else:
148 if root and not isinstance(root, RepoGroup):
148 if root and not isinstance(root, RepoGroup):
149 raise ValueError(
149 raise ValueError(
150 'Root must be an instance '
150 'Root must be an instance '
151 'of RepoGroup, got:{} instead'.format(type(root)))
151 'of RepoGroup, got:{} instead'.format(type(root)))
152 repos = Repository.query().filter(Repository.group == root).all()
152 repos = Repository.query().filter(Repository.group == root).all()
153 return repos
153 return repos
154
154
155 def get_url(self, repo):
155 def get_url(self, repo):
156 return h.url('summary_home', repo_name=safe_str(repo.repo_name),
156 return h.url('summary_home', repo_name=safe_str(repo.repo_name),
157 qualified=True)
157 qualified=True)
158
158
159 @classmethod
159 @classmethod
160 def update_repoinfo(cls, repositories=None):
160 def update_repoinfo(cls, repositories=None):
161 if not repositories:
161 if not repositories:
162 repositories = Repository.getAll()
162 repositories = Repository.getAll()
163 for repo in repositories:
163 for repo in repositories:
164 repo.update_commit_cache()
164 repo.update_commit_cache()
165
165
166 def get_repos_as_dict(self, repo_list=None, admin=False,
166 def get_repos_as_dict(self, repo_list=None, admin=False,
167 super_user_actions=False):
167 super_user_actions=False):
168
168
169 from rhodecode.lib.utils import PartialRenderer
169 from rhodecode.lib.utils import PartialRenderer
170 _render = PartialRenderer('data_table/_dt_elements.mako')
170 _render = PartialRenderer('data_table/_dt_elements.mako')
171 c = _render.c
171 c = _render.c
172
172
173 def quick_menu(repo_name):
173 def quick_menu(repo_name):
174 return _render('quick_menu', repo_name)
174 return _render('quick_menu', repo_name)
175
175
176 def repo_lnk(name, rtype, rstate, private, fork_of):
176 def repo_lnk(name, rtype, rstate, private, fork_of):
177 return _render('repo_name', name, rtype, rstate, private, fork_of,
177 return _render('repo_name', name, rtype, rstate, private, fork_of,
178 short_name=not admin, admin=False)
178 short_name=not admin, admin=False)
179
179
180 def last_change(last_change):
180 def last_change(last_change):
181 if admin and isinstance(last_change, datetime) and not last_change.tzinfo:
181 if admin and isinstance(last_change, datetime) and not last_change.tzinfo:
182 last_change = last_change + timedelta(seconds=
182 last_change = last_change + timedelta(seconds=
183 (datetime.now() - datetime.utcnow()).seconds)
183 (datetime.now() - datetime.utcnow()).seconds)
184 return _render("last_change", last_change)
184 return _render("last_change", last_change)
185
185
186 def rss_lnk(repo_name):
186 def rss_lnk(repo_name):
187 return _render("rss", repo_name)
187 return _render("rss", repo_name)
188
188
189 def atom_lnk(repo_name):
189 def atom_lnk(repo_name):
190 return _render("atom", repo_name)
190 return _render("atom", repo_name)
191
191
192 def last_rev(repo_name, cs_cache):
192 def last_rev(repo_name, cs_cache):
193 return _render('revision', repo_name, cs_cache.get('revision'),
193 return _render('revision', repo_name, cs_cache.get('revision'),
194 cs_cache.get('raw_id'), cs_cache.get('author'),
194 cs_cache.get('raw_id'), cs_cache.get('author'),
195 cs_cache.get('message'))
195 cs_cache.get('message'))
196
196
197 def desc(desc):
197 def desc(desc):
198 if c.visual.stylify_metatags:
198 if c.visual.stylify_metatags:
199 desc = h.urlify_text(h.escaped_stylize(desc))
199 desc = h.urlify_text(h.escaped_stylize(desc))
200 else:
200 else:
201 desc = h.urlify_text(h.html_escape(desc))
201 desc = h.urlify_text(h.html_escape(desc))
202
202
203 return _render('repo_desc', desc)
203 return _render('repo_desc', desc)
204
204
205 def state(repo_state):
205 def state(repo_state):
206 return _render("repo_state", repo_state)
206 return _render("repo_state", repo_state)
207
207
208 def repo_actions(repo_name):
208 def repo_actions(repo_name):
209 return _render('repo_actions', repo_name, super_user_actions)
209 return _render('repo_actions', repo_name, super_user_actions)
210
210
211 def user_profile(username):
211 def user_profile(username):
212 return _render('user_profile', username)
212 return _render('user_profile', username)
213
213
214 repos_data = []
214 repos_data = []
215 for repo in repo_list:
215 for repo in repo_list:
216 cs_cache = repo.changeset_cache
216 cs_cache = repo.changeset_cache
217 row = {
217 row = {
218 "menu": quick_menu(repo.repo_name),
218 "menu": quick_menu(repo.repo_name),
219
219
220 "name": repo_lnk(repo.repo_name, repo.repo_type,
220 "name": repo_lnk(repo.repo_name, repo.repo_type,
221 repo.repo_state, repo.private, repo.fork),
221 repo.repo_state, repo.private, repo.fork),
222 "name_raw": repo.repo_name.lower(),
222 "name_raw": repo.repo_name.lower(),
223
223
224 "last_change": last_change(repo.last_db_change),
224 "last_change": last_change(repo.last_db_change),
225 "last_change_raw": datetime_to_time(repo.last_db_change),
225 "last_change_raw": datetime_to_time(repo.last_db_change),
226
226
227 "last_changeset": last_rev(repo.repo_name, cs_cache),
227 "last_changeset": last_rev(repo.repo_name, cs_cache),
228 "last_changeset_raw": cs_cache.get('revision'),
228 "last_changeset_raw": cs_cache.get('revision'),
229
229
230 "desc": desc(repo.description),
230 "desc": desc(repo.description),
231 "owner": user_profile(repo.user.username),
231 "owner": user_profile(repo.user.username),
232
232
233 "state": state(repo.repo_state),
233 "state": state(repo.repo_state),
234 "rss": rss_lnk(repo.repo_name),
234 "rss": rss_lnk(repo.repo_name),
235
235
236 "atom": atom_lnk(repo.repo_name),
236 "atom": atom_lnk(repo.repo_name),
237 }
237 }
238 if admin:
238 if admin:
239 row.update({
239 row.update({
240 "action": repo_actions(repo.repo_name),
240 "action": repo_actions(repo.repo_name),
241 })
241 })
242 repos_data.append(row)
242 repos_data.append(row)
243
243
244 return repos_data
244 return repos_data
245
245
246 def _get_defaults(self, repo_name):
246 def _get_defaults(self, repo_name):
247 """
247 """
248 Gets information about repository, and returns a dict for
248 Gets information about repository, and returns a dict for
249 usage in forms
249 usage in forms
250
250
251 :param repo_name:
251 :param repo_name:
252 """
252 """
253
253
254 repo_info = Repository.get_by_repo_name(repo_name)
254 repo_info = Repository.get_by_repo_name(repo_name)
255
255
256 if repo_info is None:
256 if repo_info is None:
257 return None
257 return None
258
258
259 defaults = repo_info.get_dict()
259 defaults = repo_info.get_dict()
260 defaults['repo_name'] = repo_info.just_name
260 defaults['repo_name'] = repo_info.just_name
261
261
262 groups = repo_info.groups_with_parents
262 groups = repo_info.groups_with_parents
263 parent_group = groups[-1] if groups else None
263 parent_group = groups[-1] if groups else None
264
264
265 # we use -1 as this is how in HTML, we mark an empty group
265 # we use -1 as this is how in HTML, we mark an empty group
266 defaults['repo_group'] = getattr(parent_group, 'group_id', -1)
266 defaults['repo_group'] = getattr(parent_group, 'group_id', -1)
267
267
268 keys_to_process = (
268 keys_to_process = (
269 {'k': 'repo_type', 'strip': False},
269 {'k': 'repo_type', 'strip': False},
270 {'k': 'repo_enable_downloads', 'strip': True},
270 {'k': 'repo_enable_downloads', 'strip': True},
271 {'k': 'repo_description', 'strip': True},
271 {'k': 'repo_description', 'strip': True},
272 {'k': 'repo_enable_locking', 'strip': True},
272 {'k': 'repo_enable_locking', 'strip': True},
273 {'k': 'repo_landing_rev', 'strip': True},
273 {'k': 'repo_landing_rev', 'strip': True},
274 {'k': 'clone_uri', 'strip': False},
274 {'k': 'clone_uri', 'strip': False},
275 {'k': 'repo_private', 'strip': True},
275 {'k': 'repo_private', 'strip': True},
276 {'k': 'repo_enable_statistics', 'strip': True}
276 {'k': 'repo_enable_statistics', 'strip': True}
277 )
277 )
278
278
279 for item in keys_to_process:
279 for item in keys_to_process:
280 attr = item['k']
280 attr = item['k']
281 if item['strip']:
281 if item['strip']:
282 attr = remove_prefix(item['k'], 'repo_')
282 attr = remove_prefix(item['k'], 'repo_')
283
283
284 val = defaults[attr]
284 val = defaults[attr]
285 if item['k'] == 'repo_landing_rev':
285 if item['k'] == 'repo_landing_rev':
286 val = ':'.join(defaults[attr])
286 val = ':'.join(defaults[attr])
287 defaults[item['k']] = val
287 defaults[item['k']] = val
288 if item['k'] == 'clone_uri':
288 if item['k'] == 'clone_uri':
289 defaults['clone_uri_hidden'] = repo_info.clone_uri_hidden
289 defaults['clone_uri_hidden'] = repo_info.clone_uri_hidden
290
290
291 # fill owner
291 # fill owner
292 if repo_info.user:
292 if repo_info.user:
293 defaults.update({'user': repo_info.user.username})
293 defaults.update({'user': repo_info.user.username})
294 else:
294 else:
295 replacement_user = User.get_first_super_admin().username
295 replacement_user = User.get_first_super_admin().username
296 defaults.update({'user': replacement_user})
296 defaults.update({'user': replacement_user})
297
297
298 # fill repository users
298 # fill repository users
299 for p in repo_info.repo_to_perm:
299 for p in repo_info.repo_to_perm:
300 defaults.update({'u_perm_%s' % p.user.user_id:
300 defaults.update({'u_perm_%s' % p.user.user_id:
301 p.permission.permission_name})
301 p.permission.permission_name})
302
302
303 # fill repository groups
303 # fill repository groups
304 for p in repo_info.users_group_to_perm:
304 for p in repo_info.users_group_to_perm:
305 defaults.update({'g_perm_%s' % p.users_group.users_group_id:
305 defaults.update({'g_perm_%s' % p.users_group.users_group_id:
306 p.permission.permission_name})
306 p.permission.permission_name})
307
307
308 return defaults
308 return defaults
309
309
310 def update(self, repo, **kwargs):
310 def update(self, repo, **kwargs):
311 try:
311 try:
312 cur_repo = self._get_repo(repo)
312 cur_repo = self._get_repo(repo)
313 source_repo_name = cur_repo.repo_name
313 source_repo_name = cur_repo.repo_name
314 if 'user' in kwargs:
314 if 'user' in kwargs:
315 cur_repo.user = User.get_by_username(kwargs['user'])
315 cur_repo.user = User.get_by_username(kwargs['user'])
316
316
317 if 'repo_group' in kwargs:
317 if 'repo_group' in kwargs:
318 cur_repo.group = RepoGroup.get(kwargs['repo_group'])
318 cur_repo.group = RepoGroup.get(kwargs['repo_group'])
319 log.debug('Updating repo %s with params:%s', cur_repo, kwargs)
319 log.debug('Updating repo %s with params:%s', cur_repo, kwargs)
320
320
321 update_keys = [
321 update_keys = [
322 (1, 'repo_description'),
322 (1, 'repo_description'),
323 (1, 'repo_landing_rev'),
323 (1, 'repo_landing_rev'),
324 (1, 'repo_private'),
324 (1, 'repo_private'),
325 (1, 'repo_enable_downloads'),
325 (1, 'repo_enable_downloads'),
326 (1, 'repo_enable_locking'),
326 (1, 'repo_enable_locking'),
327 (1, 'repo_enable_statistics'),
327 (1, 'repo_enable_statistics'),
328 (0, 'clone_uri'),
328 (0, 'clone_uri'),
329 (0, 'fork_id')
329 (0, 'fork_id')
330 ]
330 ]
331 for strip, k in update_keys:
331 for strip, k in update_keys:
332 if k in kwargs:
332 if k in kwargs:
333 val = kwargs[k]
333 val = kwargs[k]
334 if strip:
334 if strip:
335 k = remove_prefix(k, 'repo_')
335 k = remove_prefix(k, 'repo_')
336 if k == 'clone_uri':
337 from rhodecode.model.validators import Missing
338 _change = kwargs.get('clone_uri_change')
339 if _change in [Missing, 'OLD']:
340 # we don't change the value, so use original one
341 val = cur_repo.clone_uri
342
336
343 setattr(cur_repo, k, val)
337 setattr(cur_repo, k, val)
344
338
345 new_name = cur_repo.get_new_name(kwargs['repo_name'])
339 new_name = cur_repo.get_new_name(kwargs['repo_name'])
346 cur_repo.repo_name = new_name
340 cur_repo.repo_name = new_name
347
341
348 # if private flag is set, reset default permission to NONE
342 # if private flag is set, reset default permission to NONE
349 if kwargs.get('repo_private'):
343 if kwargs.get('repo_private'):
350 EMPTY_PERM = 'repository.none'
344 EMPTY_PERM = 'repository.none'
351 RepoModel().grant_user_permission(
345 RepoModel().grant_user_permission(
352 repo=cur_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM
346 repo=cur_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM
353 )
347 )
354
348
355 # handle extra fields
349 # handle extra fields
356 for field in filter(lambda k: k.startswith(RepositoryField.PREFIX),
350 for field in filter(lambda k: k.startswith(RepositoryField.PREFIX),
357 kwargs):
351 kwargs):
358 k = RepositoryField.un_prefix_key(field)
352 k = RepositoryField.un_prefix_key(field)
359 ex_field = RepositoryField.get_by_key_name(
353 ex_field = RepositoryField.get_by_key_name(
360 key=k, repo=cur_repo)
354 key=k, repo=cur_repo)
361 if ex_field:
355 if ex_field:
362 ex_field.field_value = kwargs[field]
356 ex_field.field_value = kwargs[field]
363 self.sa.add(ex_field)
357 self.sa.add(ex_field)
364 self.sa.add(cur_repo)
358 self.sa.add(cur_repo)
365
359
366 if source_repo_name != new_name:
360 if source_repo_name != new_name:
367 # rename repository
361 # rename repository
368 self._rename_filesystem_repo(
362 self._rename_filesystem_repo(
369 old=source_repo_name, new=new_name)
363 old=source_repo_name, new=new_name)
370
364
371 return cur_repo
365 return cur_repo
372 except Exception:
366 except Exception:
373 log.error(traceback.format_exc())
367 log.error(traceback.format_exc())
374 raise
368 raise
375
369
376 def _create_repo(self, repo_name, repo_type, description, owner,
370 def _create_repo(self, repo_name, repo_type, description, owner,
377 private=False, clone_uri=None, repo_group=None,
371 private=False, clone_uri=None, repo_group=None,
378 landing_rev='rev:tip', fork_of=None,
372 landing_rev='rev:tip', fork_of=None,
379 copy_fork_permissions=False, enable_statistics=False,
373 copy_fork_permissions=False, enable_statistics=False,
380 enable_locking=False, enable_downloads=False,
374 enable_locking=False, enable_downloads=False,
381 copy_group_permissions=False,
375 copy_group_permissions=False,
382 state=Repository.STATE_PENDING):
376 state=Repository.STATE_PENDING):
383 """
377 """
384 Create repository inside database with PENDING state, this should be
378 Create repository inside database with PENDING state, this should be
385 only executed by create() repo. With exception of importing existing
379 only executed by create() repo. With exception of importing existing
386 repos
380 repos
387 """
381 """
388 from rhodecode.model.scm import ScmModel
382 from rhodecode.model.scm import ScmModel
389
383
390 owner = self._get_user(owner)
384 owner = self._get_user(owner)
391 fork_of = self._get_repo(fork_of)
385 fork_of = self._get_repo(fork_of)
392 repo_group = self._get_repo_group(safe_int(repo_group))
386 repo_group = self._get_repo_group(safe_int(repo_group))
393
387
394 try:
388 try:
395 repo_name = safe_unicode(repo_name)
389 repo_name = safe_unicode(repo_name)
396 description = safe_unicode(description)
390 description = safe_unicode(description)
397 # repo name is just a name of repository
391 # repo name is just a name of repository
398 # while repo_name_full is a full qualified name that is combined
392 # while repo_name_full is a full qualified name that is combined
399 # with name and path of group
393 # with name and path of group
400 repo_name_full = repo_name
394 repo_name_full = repo_name
401 repo_name = repo_name.split(Repository.NAME_SEP)[-1]
395 repo_name = repo_name.split(Repository.NAME_SEP)[-1]
402
396
403 new_repo = Repository()
397 new_repo = Repository()
404 new_repo.repo_state = state
398 new_repo.repo_state = state
405 new_repo.enable_statistics = False
399 new_repo.enable_statistics = False
406 new_repo.repo_name = repo_name_full
400 new_repo.repo_name = repo_name_full
407 new_repo.repo_type = repo_type
401 new_repo.repo_type = repo_type
408 new_repo.user = owner
402 new_repo.user = owner
409 new_repo.group = repo_group
403 new_repo.group = repo_group
410 new_repo.description = description or repo_name
404 new_repo.description = description or repo_name
411 new_repo.private = private
405 new_repo.private = private
412 new_repo.clone_uri = clone_uri
406 new_repo.clone_uri = clone_uri
413 new_repo.landing_rev = landing_rev
407 new_repo.landing_rev = landing_rev
414
408
415 new_repo.enable_statistics = enable_statistics
409 new_repo.enable_statistics = enable_statistics
416 new_repo.enable_locking = enable_locking
410 new_repo.enable_locking = enable_locking
417 new_repo.enable_downloads = enable_downloads
411 new_repo.enable_downloads = enable_downloads
418
412
419 if repo_group:
413 if repo_group:
420 new_repo.enable_locking = repo_group.enable_locking
414 new_repo.enable_locking = repo_group.enable_locking
421
415
422 if fork_of:
416 if fork_of:
423 parent_repo = fork_of
417 parent_repo = fork_of
424 new_repo.fork = parent_repo
418 new_repo.fork = parent_repo
425
419
426 events.trigger(events.RepoPreCreateEvent(new_repo))
420 events.trigger(events.RepoPreCreateEvent(new_repo))
427
421
428 self.sa.add(new_repo)
422 self.sa.add(new_repo)
429
423
430 EMPTY_PERM = 'repository.none'
424 EMPTY_PERM = 'repository.none'
431 if fork_of and copy_fork_permissions:
425 if fork_of and copy_fork_permissions:
432 repo = fork_of
426 repo = fork_of
433 user_perms = UserRepoToPerm.query() \
427 user_perms = UserRepoToPerm.query() \
434 .filter(UserRepoToPerm.repository == repo).all()
428 .filter(UserRepoToPerm.repository == repo).all()
435 group_perms = UserGroupRepoToPerm.query() \
429 group_perms = UserGroupRepoToPerm.query() \
436 .filter(UserGroupRepoToPerm.repository == repo).all()
430 .filter(UserGroupRepoToPerm.repository == repo).all()
437
431
438 for perm in user_perms:
432 for perm in user_perms:
439 UserRepoToPerm.create(
433 UserRepoToPerm.create(
440 perm.user, new_repo, perm.permission)
434 perm.user, new_repo, perm.permission)
441
435
442 for perm in group_perms:
436 for perm in group_perms:
443 UserGroupRepoToPerm.create(
437 UserGroupRepoToPerm.create(
444 perm.users_group, new_repo, perm.permission)
438 perm.users_group, new_repo, perm.permission)
445 # in case we copy permissions and also set this repo to private
439 # in case we copy permissions and also set this repo to private
446 # override the default user permission to make it a private
440 # override the default user permission to make it a private
447 # repo
441 # repo
448 if private:
442 if private:
449 RepoModel(self.sa).grant_user_permission(
443 RepoModel(self.sa).grant_user_permission(
450 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
444 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
451
445
452 elif repo_group and copy_group_permissions:
446 elif repo_group and copy_group_permissions:
453 user_perms = UserRepoGroupToPerm.query() \
447 user_perms = UserRepoGroupToPerm.query() \
454 .filter(UserRepoGroupToPerm.group == repo_group).all()
448 .filter(UserRepoGroupToPerm.group == repo_group).all()
455
449
456 group_perms = UserGroupRepoGroupToPerm.query() \
450 group_perms = UserGroupRepoGroupToPerm.query() \
457 .filter(UserGroupRepoGroupToPerm.group == repo_group).all()
451 .filter(UserGroupRepoGroupToPerm.group == repo_group).all()
458
452
459 for perm in user_perms:
453 for perm in user_perms:
460 perm_name = perm.permission.permission_name.replace(
454 perm_name = perm.permission.permission_name.replace(
461 'group.', 'repository.')
455 'group.', 'repository.')
462 perm_obj = Permission.get_by_key(perm_name)
456 perm_obj = Permission.get_by_key(perm_name)
463 UserRepoToPerm.create(perm.user, new_repo, perm_obj)
457 UserRepoToPerm.create(perm.user, new_repo, perm_obj)
464
458
465 for perm in group_perms:
459 for perm in group_perms:
466 perm_name = perm.permission.permission_name.replace(
460 perm_name = perm.permission.permission_name.replace(
467 'group.', 'repository.')
461 'group.', 'repository.')
468 perm_obj = Permission.get_by_key(perm_name)
462 perm_obj = Permission.get_by_key(perm_name)
469 UserGroupRepoToPerm.create(
463 UserGroupRepoToPerm.create(
470 perm.users_group, new_repo, perm_obj)
464 perm.users_group, new_repo, perm_obj)
471
465
472 if private:
466 if private:
473 RepoModel(self.sa).grant_user_permission(
467 RepoModel(self.sa).grant_user_permission(
474 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
468 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
475
469
476 else:
470 else:
477 perm_obj = self._create_default_perms(new_repo, private)
471 perm_obj = self._create_default_perms(new_repo, private)
478 self.sa.add(perm_obj)
472 self.sa.add(perm_obj)
479
473
480 # now automatically start following this repository as owner
474 # now automatically start following this repository as owner
481 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
475 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
482 owner.user_id)
476 owner.user_id)
483
477
484 # we need to flush here, in order to check if database won't
478 # we need to flush here, in order to check if database won't
485 # throw any exceptions, create filesystem dirs at the very end
479 # throw any exceptions, create filesystem dirs at the very end
486 self.sa.flush()
480 self.sa.flush()
487 events.trigger(events.RepoCreateEvent(new_repo))
481 events.trigger(events.RepoCreateEvent(new_repo))
488 return new_repo
482 return new_repo
489
483
490 except Exception:
484 except Exception:
491 log.error(traceback.format_exc())
485 log.error(traceback.format_exc())
492 raise
486 raise
493
487
494 def create(self, form_data, cur_user):
488 def create(self, form_data, cur_user):
495 """
489 """
496 Create repository using celery tasks
490 Create repository using celery tasks
497
491
498 :param form_data:
492 :param form_data:
499 :param cur_user:
493 :param cur_user:
500 """
494 """
501 from rhodecode.lib.celerylib import tasks, run_task
495 from rhodecode.lib.celerylib import tasks, run_task
502 return run_task(tasks.create_repo, form_data, cur_user)
496 return run_task(tasks.create_repo, form_data, cur_user)
503
497
504 def update_permissions(self, repo, perm_additions=None, perm_updates=None,
498 def update_permissions(self, repo, perm_additions=None, perm_updates=None,
505 perm_deletions=None, check_perms=True,
499 perm_deletions=None, check_perms=True,
506 cur_user=None):
500 cur_user=None):
507 if not perm_additions:
501 if not perm_additions:
508 perm_additions = []
502 perm_additions = []
509 if not perm_updates:
503 if not perm_updates:
510 perm_updates = []
504 perm_updates = []
511 if not perm_deletions:
505 if not perm_deletions:
512 perm_deletions = []
506 perm_deletions = []
513
507
514 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
508 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
515
509
516 # update permissions
510 # update permissions
517 for member_id, perm, member_type in perm_updates:
511 for member_id, perm, member_type in perm_updates:
518 member_id = int(member_id)
512 member_id = int(member_id)
519 if member_type == 'user':
513 if member_type == 'user':
520 # this updates also current one if found
514 # this updates also current one if found
521 self.grant_user_permission(
515 self.grant_user_permission(
522 repo=repo, user=member_id, perm=perm)
516 repo=repo, user=member_id, perm=perm)
523 else: # set for user group
517 else: # set for user group
524 # check if we have permissions to alter this usergroup
518 # check if we have permissions to alter this usergroup
525 member_name = UserGroup.get(member_id).users_group_name
519 member_name = UserGroup.get(member_id).users_group_name
526 if not check_perms or HasUserGroupPermissionAny(
520 if not check_perms or HasUserGroupPermissionAny(
527 *req_perms)(member_name, user=cur_user):
521 *req_perms)(member_name, user=cur_user):
528 self.grant_user_group_permission(
522 self.grant_user_group_permission(
529 repo=repo, group_name=member_id, perm=perm)
523 repo=repo, group_name=member_id, perm=perm)
530
524
531 # set new permissions
525 # set new permissions
532 for member_id, perm, member_type in perm_additions:
526 for member_id, perm, member_type in perm_additions:
533 member_id = int(member_id)
527 member_id = int(member_id)
534 if member_type == 'user':
528 if member_type == 'user':
535 self.grant_user_permission(
529 self.grant_user_permission(
536 repo=repo, user=member_id, perm=perm)
530 repo=repo, user=member_id, perm=perm)
537 else: # set for user group
531 else: # set for user group
538 # check if we have permissions to alter this usergroup
532 # check if we have permissions to alter this usergroup
539 member_name = UserGroup.get(member_id).users_group_name
533 member_name = UserGroup.get(member_id).users_group_name
540 if not check_perms or HasUserGroupPermissionAny(
534 if not check_perms or HasUserGroupPermissionAny(
541 *req_perms)(member_name, user=cur_user):
535 *req_perms)(member_name, user=cur_user):
542 self.grant_user_group_permission(
536 self.grant_user_group_permission(
543 repo=repo, group_name=member_id, perm=perm)
537 repo=repo, group_name=member_id, perm=perm)
544
538
545 # delete permissions
539 # delete permissions
546 for member_id, perm, member_type in perm_deletions:
540 for member_id, perm, member_type in perm_deletions:
547 member_id = int(member_id)
541 member_id = int(member_id)
548 if member_type == 'user':
542 if member_type == 'user':
549 self.revoke_user_permission(repo=repo, user=member_id)
543 self.revoke_user_permission(repo=repo, user=member_id)
550 else: # set for user group
544 else: # set for user group
551 # check if we have permissions to alter this usergroup
545 # check if we have permissions to alter this usergroup
552 member_name = UserGroup.get(member_id).users_group_name
546 member_name = UserGroup.get(member_id).users_group_name
553 if not check_perms or HasUserGroupPermissionAny(
547 if not check_perms or HasUserGroupPermissionAny(
554 *req_perms)(member_name, user=cur_user):
548 *req_perms)(member_name, user=cur_user):
555 self.revoke_user_group_permission(
549 self.revoke_user_group_permission(
556 repo=repo, group_name=member_id)
550 repo=repo, group_name=member_id)
557
551
558 def create_fork(self, form_data, cur_user):
552 def create_fork(self, form_data, cur_user):
559 """
553 """
560 Simple wrapper into executing celery task for fork creation
554 Simple wrapper into executing celery task for fork creation
561
555
562 :param form_data:
556 :param form_data:
563 :param cur_user:
557 :param cur_user:
564 """
558 """
565 from rhodecode.lib.celerylib import tasks, run_task
559 from rhodecode.lib.celerylib import tasks, run_task
566 return run_task(tasks.create_repo_fork, form_data, cur_user)
560 return run_task(tasks.create_repo_fork, form_data, cur_user)
567
561
568 def delete(self, repo, forks=None, fs_remove=True, cur_user=None):
562 def delete(self, repo, forks=None, fs_remove=True, cur_user=None):
569 """
563 """
570 Delete given repository, forks parameter defines what do do with
564 Delete given repository, forks parameter defines what do do with
571 attached forks. Throws AttachedForksError if deleted repo has attached
565 attached forks. Throws AttachedForksError if deleted repo has attached
572 forks
566 forks
573
567
574 :param repo:
568 :param repo:
575 :param forks: str 'delete' or 'detach'
569 :param forks: str 'delete' or 'detach'
576 :param fs_remove: remove(archive) repo from filesystem
570 :param fs_remove: remove(archive) repo from filesystem
577 """
571 """
578 if not cur_user:
572 if not cur_user:
579 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
573 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
580 repo = self._get_repo(repo)
574 repo = self._get_repo(repo)
581 if repo:
575 if repo:
582 if forks == 'detach':
576 if forks == 'detach':
583 for r in repo.forks:
577 for r in repo.forks:
584 r.fork = None
578 r.fork = None
585 self.sa.add(r)
579 self.sa.add(r)
586 elif forks == 'delete':
580 elif forks == 'delete':
587 for r in repo.forks:
581 for r in repo.forks:
588 self.delete(r, forks='delete')
582 self.delete(r, forks='delete')
589 elif [f for f in repo.forks]:
583 elif [f for f in repo.forks]:
590 raise AttachedForksError()
584 raise AttachedForksError()
591
585
592 old_repo_dict = repo.get_dict()
586 old_repo_dict = repo.get_dict()
593 events.trigger(events.RepoPreDeleteEvent(repo))
587 events.trigger(events.RepoPreDeleteEvent(repo))
594 try:
588 try:
595 self.sa.delete(repo)
589 self.sa.delete(repo)
596 if fs_remove:
590 if fs_remove:
597 self._delete_filesystem_repo(repo)
591 self._delete_filesystem_repo(repo)
598 else:
592 else:
599 log.debug('skipping removal from filesystem')
593 log.debug('skipping removal from filesystem')
600 old_repo_dict.update({
594 old_repo_dict.update({
601 'deleted_by': cur_user,
595 'deleted_by': cur_user,
602 'deleted_on': time.time(),
596 'deleted_on': time.time(),
603 })
597 })
604 log_delete_repository(**old_repo_dict)
598 log_delete_repository(**old_repo_dict)
605 events.trigger(events.RepoDeleteEvent(repo))
599 events.trigger(events.RepoDeleteEvent(repo))
606 except Exception:
600 except Exception:
607 log.error(traceback.format_exc())
601 log.error(traceback.format_exc())
608 raise
602 raise
609
603
610 def grant_user_permission(self, repo, user, perm):
604 def grant_user_permission(self, repo, user, perm):
611 """
605 """
612 Grant permission for user on given repository, or update existing one
606 Grant permission for user on given repository, or update existing one
613 if found
607 if found
614
608
615 :param repo: Instance of Repository, repository_id, or repository name
609 :param repo: Instance of Repository, repository_id, or repository name
616 :param user: Instance of User, user_id or username
610 :param user: Instance of User, user_id or username
617 :param perm: Instance of Permission, or permission_name
611 :param perm: Instance of Permission, or permission_name
618 """
612 """
619 user = self._get_user(user)
613 user = self._get_user(user)
620 repo = self._get_repo(repo)
614 repo = self._get_repo(repo)
621 permission = self._get_perm(perm)
615 permission = self._get_perm(perm)
622
616
623 # check if we have that permission already
617 # check if we have that permission already
624 obj = self.sa.query(UserRepoToPerm) \
618 obj = self.sa.query(UserRepoToPerm) \
625 .filter(UserRepoToPerm.user == user) \
619 .filter(UserRepoToPerm.user == user) \
626 .filter(UserRepoToPerm.repository == repo) \
620 .filter(UserRepoToPerm.repository == repo) \
627 .scalar()
621 .scalar()
628 if obj is None:
622 if obj is None:
629 # create new !
623 # create new !
630 obj = UserRepoToPerm()
624 obj = UserRepoToPerm()
631 obj.repository = repo
625 obj.repository = repo
632 obj.user = user
626 obj.user = user
633 obj.permission = permission
627 obj.permission = permission
634 self.sa.add(obj)
628 self.sa.add(obj)
635 log.debug('Granted perm %s to %s on %s', perm, user, repo)
629 log.debug('Granted perm %s to %s on %s', perm, user, repo)
636 action_logger_generic(
630 action_logger_generic(
637 'granted permission: {} to user: {} on repo: {}'.format(
631 'granted permission: {} to user: {} on repo: {}'.format(
638 perm, user, repo), namespace='security.repo')
632 perm, user, repo), namespace='security.repo')
639 return obj
633 return obj
640
634
641 def revoke_user_permission(self, repo, user):
635 def revoke_user_permission(self, repo, user):
642 """
636 """
643 Revoke permission for user on given repository
637 Revoke permission for user on given repository
644
638
645 :param repo: Instance of Repository, repository_id, or repository name
639 :param repo: Instance of Repository, repository_id, or repository name
646 :param user: Instance of User, user_id or username
640 :param user: Instance of User, user_id or username
647 """
641 """
648
642
649 user = self._get_user(user)
643 user = self._get_user(user)
650 repo = self._get_repo(repo)
644 repo = self._get_repo(repo)
651
645
652 obj = self.sa.query(UserRepoToPerm) \
646 obj = self.sa.query(UserRepoToPerm) \
653 .filter(UserRepoToPerm.repository == repo) \
647 .filter(UserRepoToPerm.repository == repo) \
654 .filter(UserRepoToPerm.user == user) \
648 .filter(UserRepoToPerm.user == user) \
655 .scalar()
649 .scalar()
656 if obj:
650 if obj:
657 self.sa.delete(obj)
651 self.sa.delete(obj)
658 log.debug('Revoked perm on %s on %s', repo, user)
652 log.debug('Revoked perm on %s on %s', repo, user)
659 action_logger_generic(
653 action_logger_generic(
660 'revoked permission from user: {} on repo: {}'.format(
654 'revoked permission from user: {} on repo: {}'.format(
661 user, repo), namespace='security.repo')
655 user, repo), namespace='security.repo')
662
656
663 def grant_user_group_permission(self, repo, group_name, perm):
657 def grant_user_group_permission(self, repo, group_name, perm):
664 """
658 """
665 Grant permission for user group on given repository, or update
659 Grant permission for user group on given repository, or update
666 existing one if found
660 existing one if found
667
661
668 :param repo: Instance of Repository, repository_id, or repository name
662 :param repo: Instance of Repository, repository_id, or repository name
669 :param group_name: Instance of UserGroup, users_group_id,
663 :param group_name: Instance of UserGroup, users_group_id,
670 or user group name
664 or user group name
671 :param perm: Instance of Permission, or permission_name
665 :param perm: Instance of Permission, or permission_name
672 """
666 """
673 repo = self._get_repo(repo)
667 repo = self._get_repo(repo)
674 group_name = self._get_user_group(group_name)
668 group_name = self._get_user_group(group_name)
675 permission = self._get_perm(perm)
669 permission = self._get_perm(perm)
676
670
677 # check if we have that permission already
671 # check if we have that permission already
678 obj = self.sa.query(UserGroupRepoToPerm) \
672 obj = self.sa.query(UserGroupRepoToPerm) \
679 .filter(UserGroupRepoToPerm.users_group == group_name) \
673 .filter(UserGroupRepoToPerm.users_group == group_name) \
680 .filter(UserGroupRepoToPerm.repository == repo) \
674 .filter(UserGroupRepoToPerm.repository == repo) \
681 .scalar()
675 .scalar()
682
676
683 if obj is None:
677 if obj is None:
684 # create new
678 # create new
685 obj = UserGroupRepoToPerm()
679 obj = UserGroupRepoToPerm()
686
680
687 obj.repository = repo
681 obj.repository = repo
688 obj.users_group = group_name
682 obj.users_group = group_name
689 obj.permission = permission
683 obj.permission = permission
690 self.sa.add(obj)
684 self.sa.add(obj)
691 log.debug('Granted perm %s to %s on %s', perm, group_name, repo)
685 log.debug('Granted perm %s to %s on %s', perm, group_name, repo)
692 action_logger_generic(
686 action_logger_generic(
693 'granted permission: {} to usergroup: {} on repo: {}'.format(
687 'granted permission: {} to usergroup: {} on repo: {}'.format(
694 perm, group_name, repo), namespace='security.repo')
688 perm, group_name, repo), namespace='security.repo')
695
689
696 return obj
690 return obj
697
691
698 def revoke_user_group_permission(self, repo, group_name):
692 def revoke_user_group_permission(self, repo, group_name):
699 """
693 """
700 Revoke permission for user group on given repository
694 Revoke permission for user group on given repository
701
695
702 :param repo: Instance of Repository, repository_id, or repository name
696 :param repo: Instance of Repository, repository_id, or repository name
703 :param group_name: Instance of UserGroup, users_group_id,
697 :param group_name: Instance of UserGroup, users_group_id,
704 or user group name
698 or user group name
705 """
699 """
706 repo = self._get_repo(repo)
700 repo = self._get_repo(repo)
707 group_name = self._get_user_group(group_name)
701 group_name = self._get_user_group(group_name)
708
702
709 obj = self.sa.query(UserGroupRepoToPerm) \
703 obj = self.sa.query(UserGroupRepoToPerm) \
710 .filter(UserGroupRepoToPerm.repository == repo) \
704 .filter(UserGroupRepoToPerm.repository == repo) \
711 .filter(UserGroupRepoToPerm.users_group == group_name) \
705 .filter(UserGroupRepoToPerm.users_group == group_name) \
712 .scalar()
706 .scalar()
713 if obj:
707 if obj:
714 self.sa.delete(obj)
708 self.sa.delete(obj)
715 log.debug('Revoked perm to %s on %s', repo, group_name)
709 log.debug('Revoked perm to %s on %s', repo, group_name)
716 action_logger_generic(
710 action_logger_generic(
717 'revoked permission from usergroup: {} on repo: {}'.format(
711 'revoked permission from usergroup: {} on repo: {}'.format(
718 group_name, repo), namespace='security.repo')
712 group_name, repo), namespace='security.repo')
719
713
720 def delete_stats(self, repo_name):
714 def delete_stats(self, repo_name):
721 """
715 """
722 removes stats for given repo
716 removes stats for given repo
723
717
724 :param repo_name:
718 :param repo_name:
725 """
719 """
726 repo = self._get_repo(repo_name)
720 repo = self._get_repo(repo_name)
727 try:
721 try:
728 obj = self.sa.query(Statistics) \
722 obj = self.sa.query(Statistics) \
729 .filter(Statistics.repository == repo).scalar()
723 .filter(Statistics.repository == repo).scalar()
730 if obj:
724 if obj:
731 self.sa.delete(obj)
725 self.sa.delete(obj)
732 except Exception:
726 except Exception:
733 log.error(traceback.format_exc())
727 log.error(traceback.format_exc())
734 raise
728 raise
735
729
736 def add_repo_field(self, repo_name, field_key, field_label, field_value='',
730 def add_repo_field(self, repo_name, field_key, field_label, field_value='',
737 field_type='str', field_desc=''):
731 field_type='str', field_desc=''):
738
732
739 repo = self._get_repo(repo_name)
733 repo = self._get_repo(repo_name)
740
734
741 new_field = RepositoryField()
735 new_field = RepositoryField()
742 new_field.repository = repo
736 new_field.repository = repo
743 new_field.field_key = field_key
737 new_field.field_key = field_key
744 new_field.field_type = field_type # python type
738 new_field.field_type = field_type # python type
745 new_field.field_value = field_value
739 new_field.field_value = field_value
746 new_field.field_desc = field_desc
740 new_field.field_desc = field_desc
747 new_field.field_label = field_label
741 new_field.field_label = field_label
748 self.sa.add(new_field)
742 self.sa.add(new_field)
749 return new_field
743 return new_field
750
744
751 def delete_repo_field(self, repo_name, field_key):
745 def delete_repo_field(self, repo_name, field_key):
752 repo = self._get_repo(repo_name)
746 repo = self._get_repo(repo_name)
753 field = RepositoryField.get_by_key_name(field_key, repo)
747 field = RepositoryField.get_by_key_name(field_key, repo)
754 if field:
748 if field:
755 self.sa.delete(field)
749 self.sa.delete(field)
756
750
757 def _create_filesystem_repo(self, repo_name, repo_type, repo_group,
751 def _create_filesystem_repo(self, repo_name, repo_type, repo_group,
758 clone_uri=None, repo_store_location=None,
752 clone_uri=None, repo_store_location=None,
759 use_global_config=False):
753 use_global_config=False):
760 """
754 """
761 makes repository on filesystem. It's group aware means it'll create
755 makes repository on filesystem. It's group aware means it'll create
762 a repository within a group, and alter the paths accordingly of
756 a repository within a group, and alter the paths accordingly of
763 group location
757 group location
764
758
765 :param repo_name:
759 :param repo_name:
766 :param alias:
760 :param alias:
767 :param parent:
761 :param parent:
768 :param clone_uri:
762 :param clone_uri:
769 :param repo_store_location:
763 :param repo_store_location:
770 """
764 """
771 from rhodecode.lib.utils import is_valid_repo, is_valid_repo_group
765 from rhodecode.lib.utils import is_valid_repo, is_valid_repo_group
772 from rhodecode.model.scm import ScmModel
766 from rhodecode.model.scm import ScmModel
773
767
774 if Repository.NAME_SEP in repo_name:
768 if Repository.NAME_SEP in repo_name:
775 raise ValueError(
769 raise ValueError(
776 'repo_name must not contain groups got `%s`' % repo_name)
770 'repo_name must not contain groups got `%s`' % repo_name)
777
771
778 if isinstance(repo_group, RepoGroup):
772 if isinstance(repo_group, RepoGroup):
779 new_parent_path = os.sep.join(repo_group.full_path_splitted)
773 new_parent_path = os.sep.join(repo_group.full_path_splitted)
780 else:
774 else:
781 new_parent_path = repo_group or ''
775 new_parent_path = repo_group or ''
782
776
783 if repo_store_location:
777 if repo_store_location:
784 _paths = [repo_store_location]
778 _paths = [repo_store_location]
785 else:
779 else:
786 _paths = [self.repos_path, new_parent_path, repo_name]
780 _paths = [self.repos_path, new_parent_path, repo_name]
787 # we need to make it str for mercurial
781 # we need to make it str for mercurial
788 repo_path = os.path.join(*map(lambda x: safe_str(x), _paths))
782 repo_path = os.path.join(*map(lambda x: safe_str(x), _paths))
789
783
790 # check if this path is not a repository
784 # check if this path is not a repository
791 if is_valid_repo(repo_path, self.repos_path):
785 if is_valid_repo(repo_path, self.repos_path):
792 raise Exception('This path %s is a valid repository' % repo_path)
786 raise Exception('This path %s is a valid repository' % repo_path)
793
787
794 # check if this path is a group
788 # check if this path is a group
795 if is_valid_repo_group(repo_path, self.repos_path):
789 if is_valid_repo_group(repo_path, self.repos_path):
796 raise Exception('This path %s is a valid group' % repo_path)
790 raise Exception('This path %s is a valid group' % repo_path)
797
791
798 log.info('creating repo %s in %s from url: `%s`',
792 log.info('creating repo %s in %s from url: `%s`',
799 repo_name, safe_unicode(repo_path),
793 repo_name, safe_unicode(repo_path),
800 obfuscate_url_pw(clone_uri))
794 obfuscate_url_pw(clone_uri))
801
795
802 backend = get_backend(repo_type)
796 backend = get_backend(repo_type)
803
797
804 config_repo = None if use_global_config else repo_name
798 config_repo = None if use_global_config else repo_name
805 if config_repo and new_parent_path:
799 if config_repo and new_parent_path:
806 config_repo = Repository.NAME_SEP.join(
800 config_repo = Repository.NAME_SEP.join(
807 (new_parent_path, config_repo))
801 (new_parent_path, config_repo))
808 config = make_db_config(clear_session=False, repo=config_repo)
802 config = make_db_config(clear_session=False, repo=config_repo)
809 config.set('extensions', 'largefiles', '')
803 config.set('extensions', 'largefiles', '')
810
804
811 # patch and reset hooks section of UI config to not run any
805 # patch and reset hooks section of UI config to not run any
812 # hooks on creating remote repo
806 # hooks on creating remote repo
813 config.clear_section('hooks')
807 config.clear_section('hooks')
814
808
815 # TODO: johbo: Unify this, hardcoded "bare=True" does not look nice
809 # TODO: johbo: Unify this, hardcoded "bare=True" does not look nice
816 if repo_type == 'git':
810 if repo_type == 'git':
817 repo = backend(
811 repo = backend(
818 repo_path, config=config, create=True, src_url=clone_uri,
812 repo_path, config=config, create=True, src_url=clone_uri,
819 bare=True)
813 bare=True)
820 else:
814 else:
821 repo = backend(
815 repo = backend(
822 repo_path, config=config, create=True, src_url=clone_uri)
816 repo_path, config=config, create=True, src_url=clone_uri)
823
817
824 ScmModel().install_hooks(repo, repo_type=repo_type)
818 ScmModel().install_hooks(repo, repo_type=repo_type)
825
819
826 log.debug('Created repo %s with %s backend',
820 log.debug('Created repo %s with %s backend',
827 safe_unicode(repo_name), safe_unicode(repo_type))
821 safe_unicode(repo_name), safe_unicode(repo_type))
828 return repo
822 return repo
829
823
830 def _rename_filesystem_repo(self, old, new):
824 def _rename_filesystem_repo(self, old, new):
831 """
825 """
832 renames repository on filesystem
826 renames repository on filesystem
833
827
834 :param old: old name
828 :param old: old name
835 :param new: new name
829 :param new: new name
836 """
830 """
837 log.info('renaming repo from %s to %s', old, new)
831 log.info('renaming repo from %s to %s', old, new)
838
832
839 old_path = os.path.join(self.repos_path, old)
833 old_path = os.path.join(self.repos_path, old)
840 new_path = os.path.join(self.repos_path, new)
834 new_path = os.path.join(self.repos_path, new)
841 if os.path.isdir(new_path):
835 if os.path.isdir(new_path):
842 raise Exception(
836 raise Exception(
843 'Was trying to rename to already existing dir %s' % new_path
837 'Was trying to rename to already existing dir %s' % new_path
844 )
838 )
845 shutil.move(old_path, new_path)
839 shutil.move(old_path, new_path)
846
840
847 def _delete_filesystem_repo(self, repo):
841 def _delete_filesystem_repo(self, repo):
848 """
842 """
849 removes repo from filesystem, the removal is acctually made by
843 removes repo from filesystem, the removal is acctually made by
850 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
844 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
851 repository is no longer valid for rhodecode, can be undeleted later on
845 repository is no longer valid for rhodecode, can be undeleted later on
852 by reverting the renames on this repository
846 by reverting the renames on this repository
853
847
854 :param repo: repo object
848 :param repo: repo object
855 """
849 """
856 rm_path = os.path.join(self.repos_path, repo.repo_name)
850 rm_path = os.path.join(self.repos_path, repo.repo_name)
857 repo_group = repo.group
851 repo_group = repo.group
858 log.info("Removing repository %s", rm_path)
852 log.info("Removing repository %s", rm_path)
859 # disable hg/git internal that it doesn't get detected as repo
853 # disable hg/git internal that it doesn't get detected as repo
860 alias = repo.repo_type
854 alias = repo.repo_type
861
855
862 config = make_db_config(clear_session=False)
856 config = make_db_config(clear_session=False)
863 config.set('extensions', 'largefiles', '')
857 config.set('extensions', 'largefiles', '')
864 bare = getattr(repo.scm_instance(config=config), 'bare', False)
858 bare = getattr(repo.scm_instance(config=config), 'bare', False)
865
859
866 # skip this for bare git repos
860 # skip this for bare git repos
867 if not bare:
861 if not bare:
868 # disable VCS repo
862 # disable VCS repo
869 vcs_path = os.path.join(rm_path, '.%s' % alias)
863 vcs_path = os.path.join(rm_path, '.%s' % alias)
870 if os.path.exists(vcs_path):
864 if os.path.exists(vcs_path):
871 shutil.move(vcs_path, os.path.join(rm_path, 'rm__.%s' % alias))
865 shutil.move(vcs_path, os.path.join(rm_path, 'rm__.%s' % alias))
872
866
873 _now = datetime.now()
867 _now = datetime.now()
874 _ms = str(_now.microsecond).rjust(6, '0')
868 _ms = str(_now.microsecond).rjust(6, '0')
875 _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
869 _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
876 repo.just_name)
870 repo.just_name)
877 if repo_group:
871 if repo_group:
878 # if repository is in group, prefix the removal path with the group
872 # if repository is in group, prefix the removal path with the group
879 args = repo_group.full_path_splitted + [_d]
873 args = repo_group.full_path_splitted + [_d]
880 _d = os.path.join(*args)
874 _d = os.path.join(*args)
881
875
882 if os.path.isdir(rm_path):
876 if os.path.isdir(rm_path):
883 shutil.move(rm_path, os.path.join(self.repos_path, _d))
877 shutil.move(rm_path, os.path.join(self.repos_path, _d))
884
878
885
879
886 class ReadmeFinder:
880 class ReadmeFinder:
887 """
881 """
888 Utility which knows how to find a readme for a specific commit.
882 Utility which knows how to find a readme for a specific commit.
889
883
890 The main idea is that this is a configurable algorithm. When creating an
884 The main idea is that this is a configurable algorithm. When creating an
891 instance you can define parameters, currently only the `default_renderer`.
885 instance you can define parameters, currently only the `default_renderer`.
892 Based on this configuration the method :meth:`search` behaves slightly
886 Based on this configuration the method :meth:`search` behaves slightly
893 different.
887 different.
894 """
888 """
895
889
896 readme_re = re.compile(r'^readme(\.[^\.]+)?$', re.IGNORECASE)
890 readme_re = re.compile(r'^readme(\.[^\.]+)?$', re.IGNORECASE)
897 path_re = re.compile(r'^docs?', re.IGNORECASE)
891 path_re = re.compile(r'^docs?', re.IGNORECASE)
898
892
899 default_priorities = {
893 default_priorities = {
900 None: 0,
894 None: 0,
901 '.text': 2,
895 '.text': 2,
902 '.txt': 3,
896 '.txt': 3,
903 '.rst': 1,
897 '.rst': 1,
904 '.rest': 2,
898 '.rest': 2,
905 '.md': 1,
899 '.md': 1,
906 '.mkdn': 2,
900 '.mkdn': 2,
907 '.mdown': 3,
901 '.mdown': 3,
908 '.markdown': 4,
902 '.markdown': 4,
909 }
903 }
910
904
911 path_priority = {
905 path_priority = {
912 'doc': 0,
906 'doc': 0,
913 'docs': 1,
907 'docs': 1,
914 }
908 }
915
909
916 FALLBACK_PRIORITY = 99
910 FALLBACK_PRIORITY = 99
917
911
918 RENDERER_TO_EXTENSION = {
912 RENDERER_TO_EXTENSION = {
919 'rst': ['.rst', '.rest'],
913 'rst': ['.rst', '.rest'],
920 'markdown': ['.md', 'mkdn', '.mdown', '.markdown'],
914 'markdown': ['.md', 'mkdn', '.mdown', '.markdown'],
921 }
915 }
922
916
923 def __init__(self, default_renderer=None):
917 def __init__(self, default_renderer=None):
924 self._default_renderer = default_renderer
918 self._default_renderer = default_renderer
925 self._renderer_extensions = self.RENDERER_TO_EXTENSION.get(
919 self._renderer_extensions = self.RENDERER_TO_EXTENSION.get(
926 default_renderer, [])
920 default_renderer, [])
927
921
928 def search(self, commit, path='/'):
922 def search(self, commit, path='/'):
929 """
923 """
930 Find a readme in the given `commit`.
924 Find a readme in the given `commit`.
931 """
925 """
932 nodes = commit.get_nodes(path)
926 nodes = commit.get_nodes(path)
933 matches = self._match_readmes(nodes)
927 matches = self._match_readmes(nodes)
934 matches = self._sort_according_to_priority(matches)
928 matches = self._sort_according_to_priority(matches)
935 if matches:
929 if matches:
936 return matches[0].node
930 return matches[0].node
937
931
938 paths = self._match_paths(nodes)
932 paths = self._match_paths(nodes)
939 paths = self._sort_paths_according_to_priority(paths)
933 paths = self._sort_paths_according_to_priority(paths)
940 for path in paths:
934 for path in paths:
941 match = self.search(commit, path=path)
935 match = self.search(commit, path=path)
942 if match:
936 if match:
943 return match
937 return match
944
938
945 return None
939 return None
946
940
947 def _match_readmes(self, nodes):
941 def _match_readmes(self, nodes):
948 for node in nodes:
942 for node in nodes:
949 if not node.is_file():
943 if not node.is_file():
950 continue
944 continue
951 path = node.path.rsplit('/', 1)[-1]
945 path = node.path.rsplit('/', 1)[-1]
952 match = self.readme_re.match(path)
946 match = self.readme_re.match(path)
953 if match:
947 if match:
954 extension = match.group(1)
948 extension = match.group(1)
955 yield ReadmeMatch(node, match, self._priority(extension))
949 yield ReadmeMatch(node, match, self._priority(extension))
956
950
957 def _match_paths(self, nodes):
951 def _match_paths(self, nodes):
958 for node in nodes:
952 for node in nodes:
959 if not node.is_dir():
953 if not node.is_dir():
960 continue
954 continue
961 match = self.path_re.match(node.path)
955 match = self.path_re.match(node.path)
962 if match:
956 if match:
963 yield node.path
957 yield node.path
964
958
965 def _priority(self, extension):
959 def _priority(self, extension):
966 renderer_priority = (
960 renderer_priority = (
967 0 if extension in self._renderer_extensions else 1)
961 0 if extension in self._renderer_extensions else 1)
968 extension_priority = self.default_priorities.get(
962 extension_priority = self.default_priorities.get(
969 extension, self.FALLBACK_PRIORITY)
963 extension, self.FALLBACK_PRIORITY)
970 return (renderer_priority, extension_priority)
964 return (renderer_priority, extension_priority)
971
965
972 def _sort_according_to_priority(self, matches):
966 def _sort_according_to_priority(self, matches):
973
967
974 def priority_and_path(match):
968 def priority_and_path(match):
975 return (match.priority, match.path)
969 return (match.priority, match.path)
976
970
977 return sorted(matches, key=priority_and_path)
971 return sorted(matches, key=priority_and_path)
978
972
979 def _sort_paths_according_to_priority(self, paths):
973 def _sort_paths_according_to_priority(self, paths):
980
974
981 def priority_and_path(path):
975 def priority_and_path(path):
982 return (self.path_priority.get(path, self.FALLBACK_PRIORITY), path)
976 return (self.path_priority.get(path, self.FALLBACK_PRIORITY), path)
983
977
984 return sorted(paths, key=priority_and_path)
978 return sorted(paths, key=priority_and_path)
985
979
986
980
987 class ReadmeMatch:
981 class ReadmeMatch:
988
982
989 def __init__(self, node, match, priority):
983 def __init__(self, node, match, priority):
990 self.node = node
984 self.node = node
991 self._match = match
985 self._match = match
992 self.priority = priority
986 self.priority = priority
993
987
994 @property
988 @property
995 def path(self):
989 def path(self):
996 return self.node.path
990 return self.node.path
997
991
998 def __repr__(self):
992 def __repr__(self):
999 return '<ReadmeMatch {} priority={}'.format(self.path, self.priority)
993 return '<ReadmeMatch {} priority={}'.format(self.path, self.priority)
@@ -1,116 +1,116 b''
1
1
2 /******************************************************************************
2 /******************************************************************************
3 * *
3 * *
4 * DO NOT CHANGE THIS FILE MANUALLY *
4 * DO NOT CHANGE THIS FILE MANUALLY *
5 * *
5 * *
6 * *
6 * *
7 * This file is automatically generated when the app starts up with *
7 * This file is automatically generated when the app starts up with *
8 * generate_js_files = true *
8 * generate_js_files = true *
9 * *
9 * *
10 * To add a route here pass jsroute=True to the route definition in the app *
10 * To add a route here pass jsroute=True to the route definition in the app *
11 * *
11 * *
12 ******************************************************************************/
12 ******************************************************************************/
13 function registerRCRoutes() {
13 function registerRCRoutes() {
14 // routes registration
14 // routes registration
15 pyroutes.register('home', '/', []);
15 pyroutes.register('home', '/', []);
16 pyroutes.register('new_repo', '/_admin/create_repository', []);
16 pyroutes.register('new_repo', '/_admin/create_repository', []);
17 pyroutes.register('edit_user', '/_admin/users/%(user_id)s/edit', ['user_id']);
17 pyroutes.register('edit_user', '/_admin/users/%(user_id)s/edit', ['user_id']);
18 pyroutes.register('edit_user_group_members', '/_admin/user_groups/%(user_group_id)s/edit/members', ['user_group_id']);
18 pyroutes.register('edit_user_group_members', '/_admin/user_groups/%(user_group_id)s/edit/members', ['user_group_id']);
19 pyroutes.register('gists', '/_admin/gists', []);
19 pyroutes.register('gists', '/_admin/gists', []);
20 pyroutes.register('new_gist', '/_admin/gists/new', []);
20 pyroutes.register('new_gist', '/_admin/gists/new', []);
21 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
21 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
22 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
22 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
23 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
23 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
24 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
24 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
25 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/default-reviewers', ['repo_name']);
25 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/default-reviewers', ['repo_name']);
26 pyroutes.register('changeset_home', '/%(repo_name)s/changeset/%(revision)s', ['repo_name', 'revision']);
26 pyroutes.register('changeset_home', '/%(repo_name)s/changeset/%(revision)s', ['repo_name', 'revision']);
27 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
28 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
27 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
29 pyroutes.register('changeset_comment', '/%(repo_name)s/changeset/%(revision)s/comment', ['repo_name', 'revision']);
28 pyroutes.register('changeset_comment', '/%(repo_name)s/changeset/%(revision)s/comment', ['repo_name', 'revision']);
30 pyroutes.register('changeset_comment_preview', '/%(repo_name)s/changeset/comment/preview', ['repo_name']);
29 pyroutes.register('changeset_comment_preview', '/%(repo_name)s/changeset/comment/preview', ['repo_name']);
31 pyroutes.register('changeset_comment_delete', '/%(repo_name)s/changeset/comment/%(comment_id)s/delete', ['repo_name', 'comment_id']);
30 pyroutes.register('changeset_comment_delete', '/%(repo_name)s/changeset/comment/%(comment_id)s/delete', ['repo_name', 'comment_id']);
32 pyroutes.register('changeset_info', '/%(repo_name)s/changeset_info/%(revision)s', ['repo_name', 'revision']);
31 pyroutes.register('changeset_info', '/%(repo_name)s/changeset_info/%(revision)s', ['repo_name', 'revision']);
33 pyroutes.register('compare_url', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']);
32 pyroutes.register('compare_url', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']);
34 pyroutes.register('pullrequest_home', '/%(repo_name)s/pull-request/new', ['repo_name']);
33 pyroutes.register('pullrequest_home', '/%(repo_name)s/pull-request/new', ['repo_name']);
35 pyroutes.register('pullrequest', '/%(repo_name)s/pull-request/new', ['repo_name']);
34 pyroutes.register('pullrequest', '/%(repo_name)s/pull-request/new', ['repo_name']);
36 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
35 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
37 pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']);
36 pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']);
38 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
37 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
39 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
38 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
40 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
39 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
41 pyroutes.register('pullrequest_comment', '/%(repo_name)s/pull-request-comment/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
40 pyroutes.register('pullrequest_comment', '/%(repo_name)s/pull-request-comment/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
42 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request-comment/%(comment_id)s/delete', ['repo_name', 'comment_id']);
41 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request-comment/%(comment_id)s/delete', ['repo_name', 'comment_id']);
43 pyroutes.register('changelog_home', '/%(repo_name)s/changelog', ['repo_name']);
42 pyroutes.register('changelog_home', '/%(repo_name)s/changelog', ['repo_name']);
44 pyroutes.register('changelog_file_home', '/%(repo_name)s/changelog/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
43 pyroutes.register('changelog_file_home', '/%(repo_name)s/changelog/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
45 pyroutes.register('changelog_elements', '/%(repo_name)s/changelog_details', ['repo_name']);
44 pyroutes.register('changelog_elements', '/%(repo_name)s/changelog_details', ['repo_name']);
46 pyroutes.register('files_home', '/%(repo_name)s/files/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
45 pyroutes.register('files_home', '/%(repo_name)s/files/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
47 pyroutes.register('files_history_home', '/%(repo_name)s/history/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
46 pyroutes.register('files_history_home', '/%(repo_name)s/history/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
48 pyroutes.register('files_authors_home', '/%(repo_name)s/authors/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
47 pyroutes.register('files_authors_home', '/%(repo_name)s/authors/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
49 pyroutes.register('files_annotate_home', '/%(repo_name)s/annotate/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
48 pyroutes.register('files_annotate_home', '/%(repo_name)s/annotate/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
50 pyroutes.register('files_annotate_previous', '/%(repo_name)s/annotate-previous/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
49 pyroutes.register('files_annotate_previous', '/%(repo_name)s/annotate-previous/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
51 pyroutes.register('files_archive_home', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
50 pyroutes.register('files_archive_home', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
52 pyroutes.register('files_nodelist_home', '/%(repo_name)s/nodelist/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
51 pyroutes.register('files_nodelist_home', '/%(repo_name)s/nodelist/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
53 pyroutes.register('files_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
52 pyroutes.register('files_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
54 pyroutes.register('summary_home_slash', '/%(repo_name)s/', ['repo_name']);
53 pyroutes.register('summary_home_slash', '/%(repo_name)s/', ['repo_name']);
55 pyroutes.register('summary_home', '/%(repo_name)s', ['repo_name']);
54 pyroutes.register('summary_home', '/%(repo_name)s', ['repo_name']);
56 pyroutes.register('favicon', '/favicon.ico', []);
55 pyroutes.register('favicon', '/favicon.ico', []);
57 pyroutes.register('robots', '/robots.txt', []);
56 pyroutes.register('robots', '/robots.txt', []);
58 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
57 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
59 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
58 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
60 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
59 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
61 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
60 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
62 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
61 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
63 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
62 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
64 pyroutes.register('repo_group_integrations_home', '%(repo_group_name)s/settings/integrations', ['repo_group_name']);
63 pyroutes.register('repo_group_integrations_home', '%(repo_group_name)s/settings/integrations', ['repo_group_name']);
65 pyroutes.register('repo_group_integrations_list', '%(repo_group_name)s/settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
64 pyroutes.register('repo_group_integrations_list', '%(repo_group_name)s/settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
66 pyroutes.register('repo_group_integrations_new', '%(repo_group_name)s/settings/integrations/new', ['repo_group_name']);
65 pyroutes.register('repo_group_integrations_new', '%(repo_group_name)s/settings/integrations/new', ['repo_group_name']);
67 pyroutes.register('repo_group_integrations_create', '%(repo_group_name)s/settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
66 pyroutes.register('repo_group_integrations_create', '%(repo_group_name)s/settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
68 pyroutes.register('repo_group_integrations_edit', '%(repo_group_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
67 pyroutes.register('repo_group_integrations_edit', '%(repo_group_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
69 pyroutes.register('repo_integrations_home', '%(repo_name)s/settings/integrations', ['repo_name']);
68 pyroutes.register('repo_integrations_home', '%(repo_name)s/settings/integrations', ['repo_name']);
70 pyroutes.register('repo_integrations_list', '%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
69 pyroutes.register('repo_integrations_list', '%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
71 pyroutes.register('repo_integrations_new', '%(repo_name)s/settings/integrations/new', ['repo_name']);
70 pyroutes.register('repo_integrations_new', '%(repo_name)s/settings/integrations/new', ['repo_name']);
72 pyroutes.register('repo_integrations_create', '%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
71 pyroutes.register('repo_integrations_create', '%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
73 pyroutes.register('repo_integrations_edit', '%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
72 pyroutes.register('repo_integrations_edit', '%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
74 pyroutes.register('ops_ping', '_admin/ops/ping', []);
73 pyroutes.register('ops_ping', '_admin/ops/ping', []);
75 pyroutes.register('admin_settings_open_source', '_admin/settings/open_source', []);
74 pyroutes.register('admin_settings_open_source', '_admin/settings/open_source', []);
76 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '_admin/settings/vcs/svn_generate_cfg', []);
75 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '_admin/settings/vcs/svn_generate_cfg', []);
77 pyroutes.register('admin_settings_system', '_admin/settings/system', []);
76 pyroutes.register('admin_settings_system', '_admin/settings/system', []);
78 pyroutes.register('admin_settings_system_update', '_admin/settings/system/updates', []);
77 pyroutes.register('admin_settings_system_update', '_admin/settings/system/updates', []);
79 pyroutes.register('admin_settings_sessions', '_admin/settings/sessions', []);
78 pyroutes.register('admin_settings_sessions', '_admin/settings/sessions', []);
80 pyroutes.register('admin_settings_sessions_cleanup', '_admin/settings/sessions/cleanup', []);
79 pyroutes.register('admin_settings_sessions_cleanup', '_admin/settings/sessions/cleanup', []);
81 pyroutes.register('users', '_admin/users', []);
80 pyroutes.register('users', '_admin/users', []);
82 pyroutes.register('users_data', '_admin/users_data', []);
81 pyroutes.register('users_data', '_admin/users_data', []);
83 pyroutes.register('edit_user_auth_tokens', '_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
82 pyroutes.register('edit_user_auth_tokens', '_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
84 pyroutes.register('edit_user_auth_tokens_add', '_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
83 pyroutes.register('edit_user_auth_tokens_add', '_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
85 pyroutes.register('edit_user_auth_tokens_delete', '_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
84 pyroutes.register('edit_user_auth_tokens_delete', '_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
86 pyroutes.register('edit_user_groups_management', '_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
85 pyroutes.register('edit_user_groups_management', '_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
87 pyroutes.register('edit_user_groups_management_updates', '_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
86 pyroutes.register('edit_user_groups_management_updates', '_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
88 pyroutes.register('edit_user_audit_logs', '_admin/users/%(user_id)s/edit/audit', ['user_id']);
87 pyroutes.register('edit_user_audit_logs', '_admin/users/%(user_id)s/edit/audit', ['user_id']);
89 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
88 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
90 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
89 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
91 pyroutes.register('channelstream_proxy', '/_channelstream', []);
90 pyroutes.register('channelstream_proxy', '/_channelstream', []);
92 pyroutes.register('login', '/_admin/login', []);
91 pyroutes.register('login', '/_admin/login', []);
93 pyroutes.register('logout', '/_admin/logout', []);
92 pyroutes.register('logout', '/_admin/logout', []);
94 pyroutes.register('register', '/_admin/register', []);
93 pyroutes.register('register', '/_admin/register', []);
95 pyroutes.register('reset_password', '/_admin/password_reset', []);
94 pyroutes.register('reset_password', '/_admin/password_reset', []);
96 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
95 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
97 pyroutes.register('user_autocomplete_data', '/_users', []);
96 pyroutes.register('user_autocomplete_data', '/_users', []);
98 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
97 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
99 pyroutes.register('repo_list_data', '/_repos', []);
98 pyroutes.register('repo_list_data', '/_repos', []);
100 pyroutes.register('goto_switcher_data', '/_goto_data', []);
99 pyroutes.register('goto_switcher_data', '/_goto_data', []);
100 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
101 pyroutes.register('repo_maintenance', '/%(repo_name)s/maintenance', ['repo_name']);
101 pyroutes.register('repo_maintenance', '/%(repo_name)s/maintenance', ['repo_name']);
102 pyroutes.register('repo_maintenance_execute', '/%(repo_name)s/maintenance/execute', ['repo_name']);
102 pyroutes.register('repo_maintenance_execute', '/%(repo_name)s/maintenance/execute', ['repo_name']);
103 pyroutes.register('strip', '/%(repo_name)s/strip', ['repo_name']);
103 pyroutes.register('strip', '/%(repo_name)s/strip', ['repo_name']);
104 pyroutes.register('strip_check', '/%(repo_name)s/strip_check', ['repo_name']);
104 pyroutes.register('strip_check', '/%(repo_name)s/strip_check', ['repo_name']);
105 pyroutes.register('strip_execute', '/%(repo_name)s/strip_execute', ['repo_name']);
105 pyroutes.register('strip_execute', '/%(repo_name)s/strip_execute', ['repo_name']);
106 pyroutes.register('search', '/_admin/search', []);
106 pyroutes.register('search', '/_admin/search', []);
107 pyroutes.register('search_repo', '/%(repo_name)s/search', ['repo_name']);
107 pyroutes.register('search_repo', '/%(repo_name)s/search', ['repo_name']);
108 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
108 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
109 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
109 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
110 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
110 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
111 pyroutes.register('my_account_password_update', '/_admin/my_account/password', []);
111 pyroutes.register('my_account_password_update', '/_admin/my_account/password', []);
112 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
112 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
113 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
113 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
114 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
114 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
115 pyroutes.register('apiv2', '/_admin/api', []);
115 pyroutes.register('apiv2', '/_admin/api', []);
116 }
116 }
@@ -1,69 +1,69 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="base.mako"/>
2 <%inherit file="base.mako"/>
3
3
4 <%def name="breadcrumbs_links()">
4 <%def name="breadcrumbs_links()">
5 %if c.repo:
5 %if c.repo:
6 ${h.link_to('Settings',h.url('edit_repo', repo_name=c.repo.repo_name))}
6 ${h.link_to('Settings',h.route_path('edit_repo', repo_name=c.repo.repo_name))}
7 &raquo;
7 &raquo;
8 ${h.link_to(_('Integrations'),request.route_url(route_name='repo_integrations_home', repo_name=c.repo.repo_name))}
8 ${h.link_to(_('Integrations'),request.route_url(route_name='repo_integrations_home', repo_name=c.repo.repo_name))}
9 &raquo;
9 &raquo;
10 ${h.link_to(current_IntegrationType.display_name,
10 ${h.link_to(current_IntegrationType.display_name,
11 request.route_url(route_name='repo_integrations_list',
11 request.route_url(route_name='repo_integrations_list',
12 repo_name=c.repo.repo_name,
12 repo_name=c.repo.repo_name,
13 integration=current_IntegrationType.key))}
13 integration=current_IntegrationType.key))}
14 %elif c.repo_group:
14 %elif c.repo_group:
15 ${h.link_to(_('Admin'),h.url('admin_home'))}
15 ${h.link_to(_('Admin'),h.url('admin_home'))}
16 &raquo;
16 &raquo;
17 ${h.link_to(_('Repository Groups'),h.url('repo_groups'))}
17 ${h.link_to(_('Repository Groups'),h.url('repo_groups'))}
18 &raquo;
18 &raquo;
19 ${h.link_to(c.repo_group.group_name,h.url('edit_repo_group', group_name=c.repo_group.group_name))}
19 ${h.link_to(c.repo_group.group_name,h.url('edit_repo_group', group_name=c.repo_group.group_name))}
20 &raquo;
20 &raquo;
21 ${h.link_to(_('Integrations'),request.route_url(route_name='repo_group_integrations_home', repo_group_name=c.repo_group.group_name))}
21 ${h.link_to(_('Integrations'),request.route_url(route_name='repo_group_integrations_home', repo_group_name=c.repo_group.group_name))}
22 &raquo;
22 &raquo;
23 ${h.link_to(current_IntegrationType.display_name,
23 ${h.link_to(current_IntegrationType.display_name,
24 request.route_url(route_name='repo_group_integrations_list',
24 request.route_url(route_name='repo_group_integrations_list',
25 repo_group_name=c.repo_group.group_name,
25 repo_group_name=c.repo_group.group_name,
26 integration=current_IntegrationType.key))}
26 integration=current_IntegrationType.key))}
27 %else:
27 %else:
28 ${h.link_to(_('Admin'),h.url('admin_home'))}
28 ${h.link_to(_('Admin'),h.url('admin_home'))}
29 &raquo;
29 &raquo;
30 ${h.link_to(_('Settings'),h.url('admin_settings'))}
30 ${h.link_to(_('Settings'),h.url('admin_settings'))}
31 &raquo;
31 &raquo;
32 ${h.link_to(_('Integrations'),request.route_url(route_name='global_integrations_home'))}
32 ${h.link_to(_('Integrations'),request.route_url(route_name='global_integrations_home'))}
33 &raquo;
33 &raquo;
34 ${h.link_to(current_IntegrationType.display_name,
34 ${h.link_to(current_IntegrationType.display_name,
35 request.route_url(route_name='global_integrations_list',
35 request.route_url(route_name='global_integrations_list',
36 integration=current_IntegrationType.key))}
36 integration=current_IntegrationType.key))}
37 %endif
37 %endif
38
38
39 %if integration:
39 %if integration:
40 &raquo;
40 &raquo;
41 ${integration.name}
41 ${integration.name}
42 %elif current_IntegrationType:
42 %elif current_IntegrationType:
43 &raquo;
43 &raquo;
44 ${current_IntegrationType.display_name}
44 ${current_IntegrationType.display_name}
45 %endif
45 %endif
46 </%def>
46 </%def>
47
47
48 <style>
48 <style>
49 .control-inputs.item-options, .control-inputs.item-settings {
49 .control-inputs.item-options, .control-inputs.item-settings {
50 float: left;
50 float: left;
51 width: 100%;
51 width: 100%;
52 }
52 }
53 </style>
53 </style>
54 <div class="panel panel-default">
54 <div class="panel panel-default">
55 <div class="panel-heading">
55 <div class="panel-heading">
56 <h2 class="panel-title">
56 <h2 class="panel-title">
57 %if integration:
57 %if integration:
58 ${current_IntegrationType.display_name} - ${integration.name}
58 ${current_IntegrationType.display_name} - ${integration.name}
59 %else:
59 %else:
60 ${_('Create New %(integration_type)s Integration') % {
60 ${_('Create New %(integration_type)s Integration') % {
61 'integration_type': current_IntegrationType.display_name
61 'integration_type': current_IntegrationType.display_name
62 }}
62 }}
63 %endif
63 %endif
64 </h2>
64 </h2>
65 </div>
65 </div>
66 <div class="panel-body">
66 <div class="panel-body">
67 ${form.render() | n}
67 ${form.render() | n}
68 </div>
68 </div>
69 </div>
69 </div>
@@ -1,252 +1,252 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="base.mako"/>
2 <%inherit file="base.mako"/>
3
3
4 <%def name="breadcrumbs_links()">
4 <%def name="breadcrumbs_links()">
5 %if c.repo:
5 %if c.repo:
6 ${h.link_to('Settings',h.url('edit_repo', repo_name=c.repo.repo_name))}
6 ${h.link_to('Settings',h.route_path('edit_repo', repo_name=c.repo.repo_name))}
7 %elif c.repo_group:
7 %elif c.repo_group:
8 ${h.link_to(_('Admin'),h.url('admin_home'))}
8 ${h.link_to(_('Admin'),h.url('admin_home'))}
9 &raquo;
9 &raquo;
10 ${h.link_to(_('Repository Groups'),h.url('repo_groups'))}
10 ${h.link_to(_('Repository Groups'),h.url('repo_groups'))}
11 &raquo;
11 &raquo;
12 ${h.link_to(c.repo_group.group_name,h.url('edit_repo_group', group_name=c.repo_group.group_name))}
12 ${h.link_to(c.repo_group.group_name,h.url('edit_repo_group', group_name=c.repo_group.group_name))}
13 %else:
13 %else:
14 ${h.link_to(_('Admin'),h.url('admin_home'))}
14 ${h.link_to(_('Admin'),h.url('admin_home'))}
15 &raquo;
15 &raquo;
16 ${h.link_to(_('Settings'),h.url('admin_settings'))}
16 ${h.link_to(_('Settings'),h.url('admin_settings'))}
17 %endif
17 %endif
18 %if current_IntegrationType:
18 %if current_IntegrationType:
19 &raquo;
19 &raquo;
20 %if c.repo:
20 %if c.repo:
21 ${h.link_to(_('Integrations'),
21 ${h.link_to(_('Integrations'),
22 request.route_url(route_name='repo_integrations_home',
22 request.route_url(route_name='repo_integrations_home',
23 repo_name=c.repo.repo_name))}
23 repo_name=c.repo.repo_name))}
24 %elif c.repo_group:
24 %elif c.repo_group:
25 ${h.link_to(_('Integrations'),
25 ${h.link_to(_('Integrations'),
26 request.route_url(route_name='repo_group_integrations_home',
26 request.route_url(route_name='repo_group_integrations_home',
27 repo_group_name=c.repo_group.group_name))}
27 repo_group_name=c.repo_group.group_name))}
28 %else:
28 %else:
29 ${h.link_to(_('Integrations'),
29 ${h.link_to(_('Integrations'),
30 request.route_url(route_name='global_integrations_home'))}
30 request.route_url(route_name='global_integrations_home'))}
31 %endif
31 %endif
32 &raquo;
32 &raquo;
33 ${current_IntegrationType.display_name}
33 ${current_IntegrationType.display_name}
34 %else:
34 %else:
35 &raquo;
35 &raquo;
36 ${_('Integrations')}
36 ${_('Integrations')}
37 %endif
37 %endif
38 </%def>
38 </%def>
39
39
40 <div class="panel panel-default">
40 <div class="panel panel-default">
41 <div class="panel-heading">
41 <div class="panel-heading">
42 <h3 class="panel-title">
42 <h3 class="panel-title">
43 %if c.repo:
43 %if c.repo:
44 ${_('Current Integrations for Repository: {repo_name}').format(repo_name=c.repo.repo_name)}
44 ${_('Current Integrations for Repository: {repo_name}').format(repo_name=c.repo.repo_name)}
45 %elif c.repo_group:
45 %elif c.repo_group:
46 ${_('Current Integrations for repository group: {repo_group_name}').format(repo_group_name=c.repo_group.group_name)}
46 ${_('Current Integrations for repository group: {repo_group_name}').format(repo_group_name=c.repo_group.group_name)}
47 %else:
47 %else:
48 ${_('Current Integrations')}
48 ${_('Current Integrations')}
49 %endif
49 %endif
50 </h3>
50 </h3>
51 </div>
51 </div>
52 <div class="panel-body">
52 <div class="panel-body">
53 <%
53 <%
54 if c.repo:
54 if c.repo:
55 home_url = request.route_path('repo_integrations_home',
55 home_url = request.route_path('repo_integrations_home',
56 repo_name=c.repo.repo_name)
56 repo_name=c.repo.repo_name)
57 elif c.repo_group:
57 elif c.repo_group:
58 home_url = request.route_path('repo_group_integrations_home',
58 home_url = request.route_path('repo_group_integrations_home',
59 repo_group_name=c.repo_group.group_name)
59 repo_group_name=c.repo_group.group_name)
60 else:
60 else:
61 home_url = request.route_path('global_integrations_home')
61 home_url = request.route_path('global_integrations_home')
62 %>
62 %>
63
63
64 <a href="${home_url}" class="btn ${not current_IntegrationType and 'btn-primary' or ''}">${_('All')}</a>
64 <a href="${home_url}" class="btn ${not current_IntegrationType and 'btn-primary' or ''}">${_('All')}</a>
65
65
66 %for integration_key, IntegrationType in available_integrations.items():
66 %for integration_key, IntegrationType in available_integrations.items():
67 <%
67 <%
68 if c.repo:
68 if c.repo:
69 list_url = request.route_path('repo_integrations_list',
69 list_url = request.route_path('repo_integrations_list',
70 repo_name=c.repo.repo_name,
70 repo_name=c.repo.repo_name,
71 integration=integration_key)
71 integration=integration_key)
72 elif c.repo_group:
72 elif c.repo_group:
73 list_url = request.route_path('repo_group_integrations_list',
73 list_url = request.route_path('repo_group_integrations_list',
74 repo_group_name=c.repo_group.group_name,
74 repo_group_name=c.repo_group.group_name,
75 integration=integration_key)
75 integration=integration_key)
76 else:
76 else:
77 list_url = request.route_path('global_integrations_list',
77 list_url = request.route_path('global_integrations_list',
78 integration=integration_key)
78 integration=integration_key)
79 %>
79 %>
80 <a href="${list_url}"
80 <a href="${list_url}"
81 class="btn ${current_IntegrationType and integration_key == current_IntegrationType.key and 'btn-primary' or ''}">
81 class="btn ${current_IntegrationType and integration_key == current_IntegrationType.key and 'btn-primary' or ''}">
82 ${IntegrationType.display_name}
82 ${IntegrationType.display_name}
83 </a>
83 </a>
84 %endfor
84 %endfor
85
85
86 <%
86 <%
87 if c.repo:
87 if c.repo:
88 create_url = h.route_path('repo_integrations_new', repo_name=c.repo.repo_name)
88 create_url = h.route_path('repo_integrations_new', repo_name=c.repo.repo_name)
89 elif c.repo_group:
89 elif c.repo_group:
90 create_url = h.route_path('repo_group_integrations_new', repo_group_name=c.repo_group.group_name)
90 create_url = h.route_path('repo_group_integrations_new', repo_group_name=c.repo_group.group_name)
91 else:
91 else:
92 create_url = h.route_path('global_integrations_new')
92 create_url = h.route_path('global_integrations_new')
93 %>
93 %>
94 <p class="pull-right">
94 <p class="pull-right">
95 <a href="${create_url}" class="btn btn-small btn-success">${_(u'Create new integration')}</a>
95 <a href="${create_url}" class="btn btn-small btn-success">${_(u'Create new integration')}</a>
96 </p>
96 </p>
97
97
98 <table class="rctable integrations">
98 <table class="rctable integrations">
99 <thead>
99 <thead>
100 <tr>
100 <tr>
101 <th><a href="?sort=enabled:${rev_sort_dir}">${_('Enabled')}</a></th>
101 <th><a href="?sort=enabled:${rev_sort_dir}">${_('Enabled')}</a></th>
102 <th><a href="?sort=name:${rev_sort_dir}">${_('Name')}</a></th>
102 <th><a href="?sort=name:${rev_sort_dir}">${_('Name')}</a></th>
103 <th colspan="2"><a href="?sort=integration_type:${rev_sort_dir}">${_('Type')}</a></th>
103 <th colspan="2"><a href="?sort=integration_type:${rev_sort_dir}">${_('Type')}</a></th>
104 <th><a href="?sort=scope:${rev_sort_dir}">${_('Scope')}</a></th>
104 <th><a href="?sort=scope:${rev_sort_dir}">${_('Scope')}</a></th>
105 <th>${_('Actions')}</th>
105 <th>${_('Actions')}</th>
106 <th></th>
106 <th></th>
107 </tr>
107 </tr>
108 </thead>
108 </thead>
109 <tbody>
109 <tbody>
110 %if not integrations_list:
110 %if not integrations_list:
111 <tr>
111 <tr>
112 <td colspan="7">
112 <td colspan="7">
113 <% integration_type = current_IntegrationType and current_IntegrationType.display_name or '' %>
113 <% integration_type = current_IntegrationType and current_IntegrationType.display_name or '' %>
114 %if c.repo:
114 %if c.repo:
115 ${_('No {type} integrations for repo {repo} exist yet.').format(type=integration_type, repo=c.repo.repo_name)}
115 ${_('No {type} integrations for repo {repo} exist yet.').format(type=integration_type, repo=c.repo.repo_name)}
116 %elif c.repo_group:
116 %elif c.repo_group:
117 ${_('No {type} integrations for repogroup {repogroup} exist yet.').format(type=integration_type, repogroup=c.repo_group.group_name)}
117 ${_('No {type} integrations for repogroup {repogroup} exist yet.').format(type=integration_type, repogroup=c.repo_group.group_name)}
118 %else:
118 %else:
119 ${_('No {type} integrations exist yet.').format(type=integration_type)}
119 ${_('No {type} integrations exist yet.').format(type=integration_type)}
120 %endif
120 %endif
121
121
122 %if current_IntegrationType:
122 %if current_IntegrationType:
123 <%
123 <%
124 if c.repo:
124 if c.repo:
125 create_url = h.route_path('repo_integrations_create', repo_name=c.repo.repo_name, integration=current_IntegrationType.key)
125 create_url = h.route_path('repo_integrations_create', repo_name=c.repo.repo_name, integration=current_IntegrationType.key)
126 elif c.repo_group:
126 elif c.repo_group:
127 create_url = h.route_path('repo_group_integrations_create', repo_group_name=c.repo_group.group_name, integration=current_IntegrationType.key)
127 create_url = h.route_path('repo_group_integrations_create', repo_group_name=c.repo_group.group_name, integration=current_IntegrationType.key)
128 else:
128 else:
129 create_url = h.route_path('global_integrations_create', integration=current_IntegrationType.key)
129 create_url = h.route_path('global_integrations_create', integration=current_IntegrationType.key)
130 %>
130 %>
131 %endif
131 %endif
132
132
133 <a href="${create_url}">${_(u'Create one')}</a>
133 <a href="${create_url}">${_(u'Create one')}</a>
134 </td>
134 </td>
135 </tr>
135 </tr>
136 %endif
136 %endif
137 %for IntegrationType, integration in integrations_list:
137 %for IntegrationType, integration in integrations_list:
138 <tr id="integration_${integration.integration_id}">
138 <tr id="integration_${integration.integration_id}">
139 <td class="td-enabled">
139 <td class="td-enabled">
140 %if integration.enabled:
140 %if integration.enabled:
141 <div class="flag_status approved pull-left"></div>
141 <div class="flag_status approved pull-left"></div>
142 %else:
142 %else:
143 <div class="flag_status rejected pull-left"></div>
143 <div class="flag_status rejected pull-left"></div>
144 %endif
144 %endif
145 </td>
145 </td>
146 <td class="td-description">
146 <td class="td-description">
147 ${integration.name}
147 ${integration.name}
148 </td>
148 </td>
149 <td class="td-icon">
149 <td class="td-icon">
150 %if integration.integration_type in available_integrations:
150 %if integration.integration_type in available_integrations:
151 <div class="integration-icon">
151 <div class="integration-icon">
152 ${available_integrations[integration.integration_type].icon|n}
152 ${available_integrations[integration.integration_type].icon|n}
153 </div>
153 </div>
154 %else:
154 %else:
155 ?
155 ?
156 %endif
156 %endif
157 </td>
157 </td>
158 <td class="td-type">
158 <td class="td-type">
159 ${integration.integration_type}
159 ${integration.integration_type}
160 </td>
160 </td>
161 <td class="td-scope">
161 <td class="td-scope">
162 %if integration.repo:
162 %if integration.repo:
163 <a href="${h.url('summary_home', repo_name=integration.repo.repo_name)}">
163 <a href="${h.url('summary_home', repo_name=integration.repo.repo_name)}">
164 ${_('repo')}:${integration.repo.repo_name}
164 ${_('repo')}:${integration.repo.repo_name}
165 </a>
165 </a>
166 %elif integration.repo_group:
166 %elif integration.repo_group:
167 <a href="${h.url('repo_group_home', group_name=integration.repo_group.group_name)}">
167 <a href="${h.url('repo_group_home', group_name=integration.repo_group.group_name)}">
168 ${_('repogroup')}:${integration.repo_group.group_name}
168 ${_('repogroup')}:${integration.repo_group.group_name}
169 %if integration.child_repos_only:
169 %if integration.child_repos_only:
170 ${_('child repos only')}
170 ${_('child repos only')}
171 %else:
171 %else:
172 ${_('cascade to all')}
172 ${_('cascade to all')}
173 %endif
173 %endif
174 </a>
174 </a>
175 %else:
175 %else:
176 %if integration.child_repos_only:
176 %if integration.child_repos_only:
177 ${_('top level repos only')}
177 ${_('top level repos only')}
178 %else:
178 %else:
179 ${_('global')}
179 ${_('global')}
180 %endif
180 %endif
181 </td>
181 </td>
182 %endif
182 %endif
183 <td class="td-action">
183 <td class="td-action">
184 %if not IntegrationType:
184 %if not IntegrationType:
185 ${_('unknown integration')}
185 ${_('unknown integration')}
186 %else:
186 %else:
187 <%
187 <%
188 if c.repo:
188 if c.repo:
189 edit_url = request.route_path('repo_integrations_edit',
189 edit_url = request.route_path('repo_integrations_edit',
190 repo_name=c.repo.repo_name,
190 repo_name=c.repo.repo_name,
191 integration=integration.integration_type,
191 integration=integration.integration_type,
192 integration_id=integration.integration_id)
192 integration_id=integration.integration_id)
193 elif c.repo_group:
193 elif c.repo_group:
194 edit_url = request.route_path('repo_group_integrations_edit',
194 edit_url = request.route_path('repo_group_integrations_edit',
195 repo_group_name=c.repo_group.group_name,
195 repo_group_name=c.repo_group.group_name,
196 integration=integration.integration_type,
196 integration=integration.integration_type,
197 integration_id=integration.integration_id)
197 integration_id=integration.integration_id)
198 else:
198 else:
199 edit_url = request.route_path('global_integrations_edit',
199 edit_url = request.route_path('global_integrations_edit',
200 integration=integration.integration_type,
200 integration=integration.integration_type,
201 integration_id=integration.integration_id)
201 integration_id=integration.integration_id)
202 %>
202 %>
203 <div class="grid_edit">
203 <div class="grid_edit">
204 <a href="${edit_url}">${_('Edit')}</a>
204 <a href="${edit_url}">${_('Edit')}</a>
205 </div>
205 </div>
206 <div class="grid_delete">
206 <div class="grid_delete">
207 <a href="${edit_url}"
207 <a href="${edit_url}"
208 class="btn btn-link btn-danger delete_integration_entry"
208 class="btn btn-link btn-danger delete_integration_entry"
209 data-desc="${integration.name}"
209 data-desc="${integration.name}"
210 data-uid="${integration.integration_id}">
210 data-uid="${integration.integration_id}">
211 ${_('Delete')}
211 ${_('Delete')}
212 </a>
212 </a>
213 </div>
213 </div>
214 %endif
214 %endif
215 </td>
215 </td>
216 </tr>
216 </tr>
217 %endfor
217 %endfor
218 <tr id="last-row"></tr>
218 <tr id="last-row"></tr>
219 </tbody>
219 </tbody>
220 </table>
220 </table>
221 <div class="integrations-paginator">
221 <div class="integrations-paginator">
222 <div class="pagination-wh pagination-left">
222 <div class="pagination-wh pagination-left">
223 ${integrations_list.pager('$link_previous ~2~ $link_next')}
223 ${integrations_list.pager('$link_previous ~2~ $link_next')}
224 </div>
224 </div>
225 </div>
225 </div>
226 </div>
226 </div>
227 </div>
227 </div>
228 <script type="text/javascript">
228 <script type="text/javascript">
229 var delete_integration = function(entry) {
229 var delete_integration = function(entry) {
230 if (confirm("Confirm to remove this integration: "+$(entry).data('desc'))) {
230 if (confirm("Confirm to remove this integration: "+$(entry).data('desc'))) {
231 var request = $.ajax({
231 var request = $.ajax({
232 type: "POST",
232 type: "POST",
233 url: $(entry).attr('href'),
233 url: $(entry).attr('href'),
234 data: {
234 data: {
235 'delete': 'delete',
235 'delete': 'delete',
236 'csrf_token': CSRF_TOKEN
236 'csrf_token': CSRF_TOKEN
237 },
237 },
238 success: function(){
238 success: function(){
239 location.reload();
239 location.reload();
240 },
240 },
241 error: function(data, textStatus, errorThrown){
241 error: function(data, textStatus, errorThrown){
242 alert("Error while deleting entry.\nError code {0} ({1}). URL: {2}".format(data.status,data.statusText,$(entry)[0].url));
242 alert("Error while deleting entry.\nError code {0} ({1}). URL: {2}".format(data.status,data.statusText,$(entry)[0].url));
243 }
243 }
244 });
244 });
245 };
245 };
246 };
246 };
247
247
248 $('.delete_integration_entry').on('click', function(e){
248 $('.delete_integration_entry').on('click', function(e){
249 e.preventDefault();
249 e.preventDefault();
250 delete_integration(this);
250 delete_integration(this);
251 });
251 });
252 </script> No newline at end of file
252 </script>
@@ -1,66 +1,66 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="base.mako"/>
2 <%inherit file="base.mako"/>
3 <%namespace name="widgets" file="/widgets.mako"/>
3 <%namespace name="widgets" file="/widgets.mako"/>
4
4
5 <%def name="breadcrumbs_links()">
5 <%def name="breadcrumbs_links()">
6 %if c.repo:
6 %if c.repo:
7 ${h.link_to('Settings',h.url('edit_repo', repo_name=c.repo.repo_name))}
7 ${h.link_to('Settings',h.route_path('edit_repo', repo_name=c.repo.repo_name))}
8 &raquo;
8 &raquo;
9 ${h.link_to(_('Integrations'),request.route_url(route_name='repo_integrations_home', repo_name=c.repo.repo_name))}
9 ${h.link_to(_('Integrations'),request.route_url(route_name='repo_integrations_home', repo_name=c.repo.repo_name))}
10 %elif c.repo_group:
10 %elif c.repo_group:
11 ${h.link_to(_('Admin'),h.url('admin_home'))}
11 ${h.link_to(_('Admin'),h.url('admin_home'))}
12 &raquo;
12 &raquo;
13 ${h.link_to(_('Repository Groups'),h.url('repo_groups'))}
13 ${h.link_to(_('Repository Groups'),h.url('repo_groups'))}
14 &raquo;
14 &raquo;
15 ${h.link_to(c.repo_group.group_name,h.url('edit_repo_group', group_name=c.repo_group.group_name))}
15 ${h.link_to(c.repo_group.group_name,h.url('edit_repo_group', group_name=c.repo_group.group_name))}
16 &raquo;
16 &raquo;
17 ${h.link_to(_('Integrations'),request.route_url(route_name='repo_group_integrations_home', repo_group_name=c.repo_group.group_name))}
17 ${h.link_to(_('Integrations'),request.route_url(route_name='repo_group_integrations_home', repo_group_name=c.repo_group.group_name))}
18 %else:
18 %else:
19 ${h.link_to(_('Admin'),h.url('admin_home'))}
19 ${h.link_to(_('Admin'),h.url('admin_home'))}
20 &raquo;
20 &raquo;
21 ${h.link_to(_('Settings'),h.url('admin_settings'))}
21 ${h.link_to(_('Settings'),h.url('admin_settings'))}
22 &raquo;
22 &raquo;
23 ${h.link_to(_('Integrations'),request.route_url(route_name='global_integrations_home'))}
23 ${h.link_to(_('Integrations'),request.route_url(route_name='global_integrations_home'))}
24 %endif
24 %endif
25 &raquo;
25 &raquo;
26 ${_('Create new integration')}
26 ${_('Create new integration')}
27 </%def>
27 </%def>
28 <%widgets:panel class_='integrations'>
28 <%widgets:panel class_='integrations'>
29 <%def name="title()">
29 <%def name="title()">
30 %if c.repo:
30 %if c.repo:
31 ${_('Create New Integration for repository: {repo_name}').format(repo_name=c.repo.repo_name)}
31 ${_('Create New Integration for repository: {repo_name}').format(repo_name=c.repo.repo_name)}
32 %elif c.repo_group:
32 %elif c.repo_group:
33 ${_('Create New Integration for repository group: {repo_group_name}').format(repo_group_name=c.repo_group.group_name)}
33 ${_('Create New Integration for repository group: {repo_group_name}').format(repo_group_name=c.repo_group.group_name)}
34 %else:
34 %else:
35 ${_('Create New Global Integration')}
35 ${_('Create New Global Integration')}
36 %endif
36 %endif
37 </%def>
37 </%def>
38
38
39 %for integration, IntegrationType in available_integrations.items():
39 %for integration, IntegrationType in available_integrations.items():
40 <%
40 <%
41 if c.repo:
41 if c.repo:
42 create_url = request.route_path('repo_integrations_create',
42 create_url = request.route_path('repo_integrations_create',
43 repo_name=c.repo.repo_name,
43 repo_name=c.repo.repo_name,
44 integration=integration)
44 integration=integration)
45 elif c.repo_group:
45 elif c.repo_group:
46 create_url = request.route_path('repo_group_integrations_create',
46 create_url = request.route_path('repo_group_integrations_create',
47 repo_group_name=c.repo_group.group_name,
47 repo_group_name=c.repo_group.group_name,
48 integration=integration)
48 integration=integration)
49 else:
49 else:
50 create_url = request.route_path('global_integrations_create',
50 create_url = request.route_path('global_integrations_create',
51 integration=integration)
51 integration=integration)
52 %>
52 %>
53 <a href="${create_url}" class="integration-box">
53 <a href="${create_url}" class="integration-box">
54 <%widgets:panel>
54 <%widgets:panel>
55 <h2>
55 <h2>
56 <div class="integration-icon">
56 <div class="integration-icon">
57 ${IntegrationType.icon|n}
57 ${IntegrationType.icon|n}
58 </div>
58 </div>
59 ${IntegrationType.display_name}
59 ${IntegrationType.display_name}
60 </h2>
60 </h2>
61 ${IntegrationType.description or _('No description available')}
61 ${IntegrationType.description or _('No description available')}
62 </%widgets:panel>
62 </%widgets:panel>
63 </a>
63 </a>
64 %endfor
64 %endfor
65 <div style="clear:both"></div>
65 <div style="clear:both"></div>
66 </%widgets:panel>
66 </%widgets:panel>
@@ -1,106 +1,106 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 ##
2 ##
3 ## See also repo_settings.html
3 ## See also repo_settings.html
4 ##
4 ##
5 <%inherit file="/base/base.mako"/>
5 <%inherit file="/base/base.mako"/>
6
6
7 <%def name="title()">
7 <%def name="title()">
8 ${_('%s repository settings') % c.repo_info.repo_name}
8 ${_('%s repository settings') % c.repo_info.repo_name}
9 %if c.rhodecode_name:
9 %if c.rhodecode_name:
10 &middot; ${h.branding(c.rhodecode_name)}
10 &middot; ${h.branding(c.rhodecode_name)}
11 %endif
11 %endif
12 </%def>
12 </%def>
13
13
14 <%def name="breadcrumbs_links()">
14 <%def name="breadcrumbs_links()">
15 ${_('Settings')}
15 ${_('Settings')}
16 </%def>
16 </%def>
17
17
18 <%def name="menu_bar_nav()">
18 <%def name="menu_bar_nav()">
19 ${self.menu_items(active='repositories')}
19 ${self.menu_items(active='repositories')}
20 </%def>
20 </%def>
21
21
22 <%def name="menu_bar_subnav()">
22 <%def name="menu_bar_subnav()">
23 ${self.repo_menu(active='options')}
23 ${self.repo_menu(active='options')}
24 </%def>
24 </%def>
25
25
26 <%def name="main_content()">
26 <%def name="main_content()">
27 <%include file="/admin/repos/repo_edit_${c.active}.mako"/>
27 <%include file="/admin/repos/repo_edit_${c.active}.mako"/>
28 </%def>
28 </%def>
29
29
30
30
31 <%def name="main()">
31 <%def name="main()">
32 <div class="box">
32 <div class="box">
33 <div class="title">
33 <div class="title">
34 ${self.repo_page_title(c.rhodecode_db_repo)}
34 ${self.repo_page_title(c.rhodecode_db_repo)}
35 ${self.breadcrumbs()}
35 ${self.breadcrumbs()}
36 </div>
36 </div>
37
37
38 <div class="sidebar-col-wrapper scw-small">
38 <div class="sidebar-col-wrapper scw-small">
39 ##main
39 ##main
40 <div class="sidebar">
40 <div class="sidebar">
41 <ul class="nav nav-pills nav-stacked">
41 <ul class="nav nav-pills nav-stacked">
42 <li class="${'active' if c.active=='settings' else ''}">
42 <li class="${'active' if c.active=='settings' else ''}">
43 <a href="${h.url('edit_repo', repo_name=c.repo_name)}">${_('Settings')}</a>
43 <a href="${h.route_path('edit_repo', repo_name=c.repo_name)}">${_('Settings')}</a>
44 </li>
44 </li>
45 <li class="${'active' if c.active=='permissions' else ''}">
45 <li class="${'active' if c.active=='permissions' else ''}">
46 <a href="${h.url('edit_repo_perms', repo_name=c.repo_name)}">${_('Permissions')}</a>
46 <a href="${h.url('edit_repo_perms', repo_name=c.repo_name)}">${_('Permissions')}</a>
47 </li>
47 </li>
48 <li class="${'active' if c.active=='advanced' else ''}">
48 <li class="${'active' if c.active=='advanced' else ''}">
49 <a href="${h.url('edit_repo_advanced', repo_name=c.repo_name)}">${_('Advanced')}</a>
49 <a href="${h.url('edit_repo_advanced', repo_name=c.repo_name)}">${_('Advanced')}</a>
50 </li>
50 </li>
51 <li class="${'active' if c.active=='vcs' else ''}">
51 <li class="${'active' if c.active=='vcs' else ''}">
52 <a href="${h.url('repo_vcs_settings', repo_name=c.repo_name)}">${_('VCS')}</a>
52 <a href="${h.url('repo_vcs_settings', repo_name=c.repo_name)}">${_('VCS')}</a>
53 </li>
53 </li>
54 <li class="${'active' if c.active=='fields' else ''}">
54 <li class="${'active' if c.active=='fields' else ''}">
55 <a href="${h.url('edit_repo_fields', repo_name=c.repo_name)}">${_('Extra Fields')}</a>
55 <a href="${h.url('edit_repo_fields', repo_name=c.repo_name)}">${_('Extra Fields')}</a>
56 </li>
56 </li>
57 <li class="${'active' if c.active=='issuetracker' else ''}">
57 <li class="${'active' if c.active=='issuetracker' else ''}">
58 <a href="${h.url('repo_settings_issuetracker', repo_name=c.repo_name)}">${_('Issue Tracker')}</a>
58 <a href="${h.url('repo_settings_issuetracker', repo_name=c.repo_name)}">${_('Issue Tracker')}</a>
59 </li>
59 </li>
60 <li class="${'active' if c.active=='caches' else ''}">
60 <li class="${'active' if c.active=='caches' else ''}">
61 <a href="${h.url('edit_repo_caches', repo_name=c.repo_name)}">${_('Caches')}</a>
61 <a href="${h.url('edit_repo_caches', repo_name=c.repo_name)}">${_('Caches')}</a>
62 </li>
62 </li>
63 %if c.repo_info.repo_type != 'svn':
63 %if c.repo_info.repo_type != 'svn':
64 <li class="${'active' if c.active=='remote' else ''}">
64 <li class="${'active' if c.active=='remote' else ''}">
65 <a href="${h.url('edit_repo_remote', repo_name=c.repo_name)}">${_('Remote')}</a>
65 <a href="${h.url('edit_repo_remote', repo_name=c.repo_name)}">${_('Remote')}</a>
66 </li>
66 </li>
67 %endif
67 %endif
68 <li class="${'active' if c.active=='statistics' else ''}">
68 <li class="${'active' if c.active=='statistics' else ''}">
69 <a href="${h.url('edit_repo_statistics', repo_name=c.repo_name)}">${_('Statistics')}</a>
69 <a href="${h.url('edit_repo_statistics', repo_name=c.repo_name)}">${_('Statistics')}</a>
70 </li>
70 </li>
71 <li class="${'active' if c.active=='integrations' else ''}">
71 <li class="${'active' if c.active=='integrations' else ''}">
72 <a href="${h.route_path('repo_integrations_home', repo_name=c.repo_name)}">${_('Integrations')}</a>
72 <a href="${h.route_path('repo_integrations_home', repo_name=c.repo_name)}">${_('Integrations')}</a>
73 </li>
73 </li>
74 <li class="${'active' if c.active=='maintenance' else ''}">
74 <li class="${'active' if c.active=='maintenance' else ''}">
75 <a href="${h.route_path('repo_maintenance', repo_name=c.repo_name)}">${_('Maintenance')}</a>
75 <a href="${h.route_path('repo_maintenance', repo_name=c.repo_name)}">${_('Maintenance')}</a>
76 </li>
76 </li>
77 <li class="${'active' if c.active=='strip' else ''}">
77 <li class="${'active' if c.active=='strip' else ''}">
78 <a href="${h.route_path('strip', repo_name=c.repo_name)}">${_('Strip')}</a>
78 <a href="${h.route_path('strip', repo_name=c.repo_name)}">${_('Strip')}</a>
79 </li>
79 </li>
80 ## TODO: dan: replace repo navigation with navlist registry like with
80 ## TODO: dan: replace repo navigation with navlist registry like with
81 ## admin menu. First must find way to allow runtime configuration
81 ## admin menu. First must find way to allow runtime configuration
82 ## it to account for the c.repo_info.repo_type != 'svn' call above
82 ## it to account for the c.repo_info.repo_type != 'svn' call above
83 <%
83 <%
84 reviewer_settings = False
84 reviewer_settings = False
85 try:
85 try:
86 import rc_reviewers
86 import rc_reviewers
87 reviewer_settings = True
87 reviewer_settings = True
88 except ImportError:
88 except ImportError:
89 pass
89 pass
90 %>
90 %>
91 %if reviewer_settings:
91 %if reviewer_settings:
92 <li class="${'active' if c.active=='reviewers' else ''}">
92 <li class="${'active' if c.active=='reviewers' else ''}">
93 <a href="${h.route_path('repo_reviewers_home', repo_name=c.repo_name)}">${_('Reviewers')}</a>
93 <a href="${h.route_path('repo_reviewers_home', repo_name=c.repo_name)}">${_('Reviewers')}</a>
94 </li>
94 </li>
95 %endif
95 %endif
96 </ul>
96 </ul>
97 </div>
97 </div>
98
98
99 <div class="main-content-full-width">
99 <div class="main-content-full-width">
100 ${self.main_content()}
100 ${self.main_content()}
101 </div>
101 </div>
102
102
103 </div>
103 </div>
104 </div>
104 </div>
105
105
106 </%def> No newline at end of file
106 </%def>
@@ -1,240 +1,249 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%namespace name="base" file="/base/base.mako"/>
2 <%namespace name="base" file="/base/base.mako"/>
3
3
4 <div class="panel panel-default">
4 <div class="panel panel-default">
5 <div class="panel-heading">
5 <div class="panel-heading">
6 <h3 class="panel-title">${_('Settings for Repository: %s') % c.repo_info.repo_name}</h3>
6 <h3 class="panel-title">${_('Settings for Repository: %s') % c.rhodecode_db_repo.repo_name}</h3>
7 </div>
7 </div>
8 <div class="panel-body">
8 <div class="panel-body">
9 ${h.secure_form(url('repo', repo_name=c.repo_info.repo_name),method='put')}
9 ${h.secure_form(h.route_path('edit_repo', repo_name=c.rhodecode_db_repo.repo_name), method='POST')}
10 <div class="form">
10 <div class="form">
11 <!-- fields -->
11 <!-- fields -->
12 <div class="fields">
12 <div class="fields">
13
13 <div class="field">
14 <div class="field">
14 <div class="label">
15 <div class="label">
15 <label for="repo_name">${_('Name')}:</label>
16 <label for="repo_name">${_('Name')}:</label>
16 </div>
17 </div>
17 <div class="input">
18 <div class="input">
18 ${h.text('repo_name',class_="medium")}
19 ${c.form['repo_name'].render(css_class='medium', oid='repo_name')|n}
19 <p class="help-block">${_('Non-changeable id')}: `_${c.repo_info.repo_id}` <span><a id="show_more_clone_id" href="#">${_('what is that ?')}</a></span></p>
20 ${c.form.render_error(request, c.form['repo_name'])|n}
21
22 <p class="help-block">${_('Non-changeable id')}: `_${c.rhodecode_db_repo.repo_id}` <span><a href="#" onclick="$('#clone_id').toggle();return false">${_('what is that ?')}</a></span></p>
20 <p id="clone_id" style="display:none;">
23 <p id="clone_id" style="display:none;">
21 ${_('URL by id')}: `${c.repo_info.clone_url(with_id=True)}` </br>
24 ${_('URL by id')}: `${c.rhodecode_db_repo.clone_url(with_id=True)}` <br/>
22 ${_('''In case this repository is renamed or moved into another group the repository url changes.
25 ${_('''In case this repository is renamed or moved into another group the repository url changes.
23 Using above url guarantees that this repository will always be accessible under such url.
26 Using above url guarantees that this repository will always be accessible under such url.
24 Useful for CI systems, or any other cases that you need to hardcode the url into 3rd party service.''')}</p>
27 Useful for CI systems, or any other cases that you need to hardcode the url into 3rd party service.''')}</p>
25 </div>
28 </div>
26 </div>
29 </div>
27 % if c.repo_info.repo_type != 'svn':
30
31 <div class="field">
32 <div class="label">
33 <label for="repo_group">${_('Repository group')}:</label>
34 </div>
35 <div class="select">
36 ${c.form['repo_group'].render(css_class='medium', oid='repo_group')|n}
37 ${c.form.render_error(request, c.form['repo_group'])|n}
38
39 % if c.personal_repo_group:
40 <a class="btn" href="#" data-personal-group-name="${c.personal_repo_group.group_name}" data-personal-group-id="${c.personal_repo_group.group_id}" onclick="selectMyGroup(this); return false">
41 ${_('Select my personal group (`%(repo_group_name)s`)') % {'repo_group_name': c.personal_repo_group.group_name}}
42 </a>
43 % endif
44 <p class="help-block">${_('Optional select a group to put this repository into.')}</p>
45 </div>
46 </div>
47
48 % if c.rhodecode_db_repo.repo_type != 'svn':
28 <div class="field">
49 <div class="field">
29 <div class="label">
50 <div class="label">
30 <label for="clone_uri">${_('Remote uri')}:</label>
51 <label for="clone_uri">${_('Remote uri')}:</label>
31 </div>
52 </div>
32 <div class="input">
53 <div class="input">
33 %if c.repo_info.clone_uri:
54 %if c.rhodecode_db_repo.clone_uri:
55 ## display
34 <div id="clone_uri_hidden" class='text-as-placeholder'>
56 <div id="clone_uri_hidden" class='text-as-placeholder'>
35 <span id="clone_uri_hidden_value">${c.repo_info.clone_uri_hidden}</span>
57 <span id="clone_uri_hidden_value">${c.rhodecode_db_repo.clone_uri_hidden}</span>
36 <span class="link" id="edit_clone_uri"><i class="icon-edit"></i>${_('edit')}</span>
58 <span class="link" id="edit_clone_uri"><i class="icon-edit"></i>${_('edit')}</span>
37 </div>
59 </div>
60 ## alter field
38 <div id="alter_clone_uri" style="display: none">
61 <div id="alter_clone_uri" style="display: none">
39 ${h.text('clone_uri',class_="medium", placeholder=_('new value, leave empty to remove'))}
62 ${c.form['repo_clone_uri'].render(css_class='medium', oid='clone_uri', placeholder=_('enter new value, or leave empty to remove'))|n}
40 ${h.hidden('clone_uri_change', 'OLD')}
63 ${c.form.render_error(request, c.form['repo_clone_uri'])|n}
64 ${h.hidden('repo_clone_uri_change', 'OLD')}
65
41 <span class="link" id="cancel_edit_clone_uri">${_('cancel')}</span>
66 <span class="link" id="cancel_edit_clone_uri">${_('cancel')}</span>
42 </div>
67 </div>
43 %else:
68 %else:
44 ## not set yet, display form to set it
69 ## not set yet, display form to set it
45 ${h.text('clone_uri',class_="medium")}
70 ${c.form['repo_clone_uri'].render(css_class='medium', oid='clone_uri')|n}
46 ${h.hidden('clone_uri_change', 'NEW')}
71 ${c.form.render_error(request, c.form['repo_clone_uri'])|n}
72 ${h.hidden('repo_clone_uri_change', 'NEW')}
47 %endif
73 %endif
48 <p id="alter_clone_uri_help_block" class="help-block">${_('http[s] url where from repository was imported, also used for doing remote pulls.')}</p>
74 <p id="alter_clone_uri_help_block" class="help-block">
75 <% pull_link = h.literal(h.link_to('remote sync', h.url('edit_repo_remote', repo_name=c.repo_name))) %>
76 ${_('http[s] url where from repository was imported, this field can used for doing {pull_link}.').format(pull_link=pull_link)|n} <br/>
77 ${_('This field is stored encrypted inside Database, a format of http://user:password@server.com/repo_name can be used and will be hidden from display.')}
78 </p>
49 </div>
79 </div>
50 </div>
80 </div>
51 % else:
81 % else:
52 ${h.hidden('clone_uri', '')}
82 ${h.hidden('repo_clone_uri', '')}
53 % endif
83 % endif
84
54 <div class="field">
85 <div class="field">
55 <div class="label">
86 <div class="label">
56 <label for="repo_group">${_('Repository group')}:</label>
87 <label for="repo_landing_commit_ref">${_('Landing commit')}:</label>
57 </div>
88 </div>
58 <div class="select">
89 <div class="select">
59 ${h.select('repo_group','',c.repo_groups,class_="medium")}
90 ${c.form['repo_landing_commit_ref'].render(css_class='medium', oid='repo_landing_commit_ref')|n}
60 % if c.personal_repo_group:
91 ${c.form.render_error(request, c.form['repo_landing_commit_ref'])|n}
61 <a class="btn" href="#" id="select_my_group" data-personal-group-id="${c.personal_repo_group.group_id}">
92 <p class="help-block">${_('Default commit for files page, downloads, full text search index and readme')}</p>
62 ${_('Select my personal group (%(repo_group_name)s)') % {'repo_group_name': c.personal_repo_group.group_name}}
63 </a>
64 % endif
65 <p class="help-block">${_('Optional select a group to put this repository into.')}</p>
66 </div>
67 </div>
68 <div class="field">
69 <div class="label">
70 <label for="repo_landing_rev">${_('Landing commit')}:</label>
71 </div>
72 <div class="select">
73 ${h.select('repo_landing_rev','',c.landing_revs,class_="medium")}
74 <p class="help-block">${_('Default commit for files page, downloads, whoosh and readme')}</p>
75 </div>
93 </div>
76 </div>
94 </div>
77
95
78 <div class="field badged-field">
96 <div class="field badged-field">
79 <div class="label">
97 <div class="label">
80 <label for="user">${_('Owner')}:</label>
98 <label for="repo_owner">${_('Owner')}:</label>
81 </div>
99 </div>
82 <div class="input">
100 <div class="input">
83 <div class="badge-input-container">
101 <div class="badge-input-container">
84 <div class="user-badge">
102 <div class="user-badge">
85 ${base.gravatar_with_user(c.repo_info.user.email, show_disabled=not c.repo_info.user.active)}
103 ${base.gravatar_with_user(c.rhodecode_db_repo.user.email, show_disabled=not c.rhodecode_db_repo.user.active)}
86 </div>
104 </div>
87 <div class="badge-input-wrap">
105 <div class="badge-input-wrap">
88 ${h.text('user', class_="medium", autocomplete="off")}
106 ${c.form['repo_owner'].render(css_class='medium', oid='repo_owner')|n}
89 </div>
107 </div>
90 </div>
108 </div>
91 <form:error name="user"/>
109 ${c.form.render_error(request, c.form['repo_owner'])|n}
92 <p class="help-block">${_('Change owner of this repository.')}</p>
110 <p class="help-block">${_('Change owner of this repository.')}</p>
93 </div>
111 </div>
94 </div>
112 </div>
95
113
96 <div class="field">
114 <div class="field">
97 <div class="label label-textarea">
115 <div class="label label-textarea">
98 <label for="repo_description">${_('Description')}:</label>
116 <label for="repo_description">${_('Description')}:</label>
99 </div>
117 </div>
100 <div class="textarea text-area editor">
118 <div class="textarea text-area editor">
101 ${h.textarea('repo_description', )}
119 ${c.form['repo_description'].render(css_class='medium', oid='repo_description')|n}
120 ${c.form.render_error(request, c.form['repo_description'])|n}
102 <p class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</p>
121 <p class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</p>
103 </div>
122 </div>
104 </div>
123 </div>
105
124
106 <div class="field">
125 <div class="field">
107 <div class="label label-checkbox">
126 <div class="label label-checkbox">
108 <label for="repo_private">${_('Private repository')}:</label>
127 <label for="${c.form['repo_private'].oid}">${_('Private repository')}:</label>
109 </div>
128 </div>
110 <div class="checkboxes">
129 <div class="checkboxes">
111 ${h.checkbox('repo_private',value="True")}
130 ${c.form['repo_private'].render(css_class='medium')|n}
131 ${c.form.render_error(request, c.form['repo_private'])|n}
112 <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
132 <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
113 </div>
133 </div>
114 </div>
134 </div>
115 <div class="field">
135 <div class="field">
116 <div class="label label-checkbox">
136 <div class="label label-checkbox">
117 <label for="repo_enable_statistics">${_('Enable statistics')}:</label>
137 <label for="${c.form['repo_enable_statistics'].oid}">${_('Enable statistics')}:</label>
118 </div>
138 </div>
119 <div class="checkboxes">
139 <div class="checkboxes">
120 ${h.checkbox('repo_enable_statistics',value="True")}
140 ${c.form['repo_enable_statistics'].render(css_class='medium')|n}
141 ${c.form.render_error(request, c.form['repo_enable_statistics'])|n}
121 <span class="help-block">${_('Enable statistics window on summary page.')}</span>
142 <span class="help-block">${_('Enable statistics window on summary page.')}</span>
122 </div>
143 </div>
123 </div>
144 </div>
124 <div class="field">
145 <div class="field">
125 <div class="label label-checkbox">
146 <div class="label label-checkbox">
126 <label for="repo_enable_downloads">${_('Enable downloads')}:</label>
147 <label for="${c.form['repo_enable_downloads'].oid}">${_('Enable downloads')}:</label>
127 </div>
148 </div>
128 <div class="checkboxes">
149 <div class="checkboxes">
129 ${h.checkbox('repo_enable_downloads',value="True")}
150 ${c.form['repo_enable_downloads'].render(css_class='medium')|n}
151 ${c.form.render_error(request, c.form['repo_enable_downloads'])|n}
130 <span class="help-block">${_('Enable download menu on summary page.')}</span>
152 <span class="help-block">${_('Enable download menu on summary page.')}</span>
131 </div>
153 </div>
132 </div>
154 </div>
133 <div class="field">
155 <div class="field">
134 <div class="label label-checkbox">
156 <div class="label label-checkbox">
135 <label for="repo_enable_locking">${_('Enable automatic locking')}:</label>
157 <label for="${c.form['repo_enable_locking'].oid}">${_('Enable automatic locking')}:</label>
136 </div>
158 </div>
137 <div class="checkboxes">
159 <div class="checkboxes">
138 ${h.checkbox('repo_enable_locking',value="True")}
160 ${c.form['repo_enable_locking'].render(css_class='medium')|n}
161 ${c.form.render_error(request, c.form['repo_enable_locking'])|n}
139 <span class="help-block">${_('Enable automatic locking on repository. Pulling from this repository creates a lock that can be released by pushing back by the same user')}</span>
162 <span class="help-block">${_('Enable automatic locking on repository. Pulling from this repository creates a lock that can be released by pushing back by the same user')}</span>
140 </div>
163 </div>
141 </div>
164 </div>
142
165
143 %if c.visual.repository_fields:
166 %if c.visual.repository_fields:
144 ## EXTRA FIELDS
167 ## EXTRA FIELDS
145 %for field in c.repo_fields:
168 %for field in c.repo_fields:
146 <div class="field">
169 <div class="field">
147 <div class="label">
170 <div class="label">
148 <label for="${field.field_key_prefixed}">${field.field_label} (${field.field_key}):</label>
171 <label for="${field.field_key_prefixed}">${field.field_label} (${field.field_key}):</label>
149 </div>
172 </div>
150 <div class="input input-medium">
173 <div class="input input-medium">
151 ${h.text(field.field_key_prefixed, field.field_value, class_='medium')}
174 ${h.text(field.field_key_prefixed, field.field_value, class_='medium')}
152 %if field.field_desc:
175 %if field.field_desc:
153 <span class="help-block">${field.field_desc}</span>
176 <span class="help-block">${field.field_desc}</span>
154 %endif
177 %endif
155 </div>
178 </div>
156 </div>
179 </div>
157 %endfor
180 %endfor
158 %endif
181 %endif
159 <div class="buttons">
182 <div class="buttons">
160 ${h.submit('save',_('Save'),class_="btn")}
183 ${h.submit('save',_('Save'),class_="btn")}
161 ${h.reset('reset',_('Reset'),class_="btn")}
184 ${h.reset('reset',_('Reset'),class_="btn")}
162 </div>
185 </div>
163 </div>
186 </div>
164 </div>
187 </div>
165 ${h.end_form()}
188 ${h.end_form()}
166 </div>
189 </div>
167 </div>
190 </div>
168
191
169 <script>
192 <script>
170 $(document).ready(function(){
193 $(document).ready(function(){
171 var select2Options = {
172 'containerCssClass': "drop-menu",
173 'dropdownCssClass': "drop-menu-dropdown",
174 'dropdownAutoWidth': true
175 };
176
177 var cloneUrl = function() {
194 var cloneUrl = function() {
178 var alterButton = $('#alter_clone_uri');
195 var alterButton = $('#alter_clone_uri');
179 var editButton = $('#edit_clone_uri');
196 var editButton = $('#edit_clone_uri');
180 var cancelEditButton = $('#cancel_edit_clone_uri');
197 var cancelEditButton = $('#cancel_edit_clone_uri');
181 var hiddenUrl = $('#clone_uri_hidden');
198 var hiddenUrl = $('#clone_uri_hidden');
182 var hiddenUrlValue = $('#clone_uri_hidden_value');
199 var hiddenUrlValue = $('#clone_uri_hidden_value');
183 var input = $('#clone_uri');
200 var input = $('#clone_uri');
184 var helpBlock = $('#alter_clone_uri_help_block');
201 var helpBlock = $('#alter_clone_uri_help_block');
185 var changedFlag = $('#clone_uri_change');
202 var changedFlag = $('#repo_clone_uri_change');
186 var originalText = helpBlock.html();
203 var originalText = helpBlock.html();
187 var obfuscatedUrl = hiddenUrlValue.html();
204 var obfuscatedUrl = hiddenUrlValue.html();
188
205
189 var edit = function(e) {
206 var edit = function(e) {
190 alterButton.show();
207 alterButton.show();
191 editButton.hide();
208 editButton.hide();
192 hiddenUrl.hide();
209 hiddenUrl.hide();
193
210
194 //add the old value next to input for verification
211 //add the old value next to input for verification
195 helpBlock.html("(" + obfuscatedUrl + ")" + "<br\>" + originalText);
212 helpBlock.html("(" + obfuscatedUrl + ")" + "<br\>" + originalText);
196 changedFlag.val('MOD');
213 changedFlag.val('MOD');
197 };
214 };
198
215
199 var cancelEdit = function(e) {
216 var cancelEdit = function(e) {
200 alterButton.hide();
217 alterButton.hide();
201 editButton.show();
218 editButton.show();
202 hiddenUrl.show();
219 hiddenUrl.show();
203
220
204 helpBlock.html(originalText);
221 helpBlock.html(originalText);
205 changedFlag.val('OLD');
222 changedFlag.val('OLD');
206 input.val('');
223 input.val('');
207 };
224 };
208
225
209 var initEvents = function() {
226 var initEvents = function() {
210 editButton.on('click', edit);
227 editButton.on('click', edit);
211 cancelEditButton.on('click', cancelEdit);
228 cancelEditButton.on('click', cancelEdit);
212 };
229 };
213
230
214 var setInitialState = function() {
231 var setInitialState = function() {
215 if (input.hasClass('error')) {
232 if (input.hasClass('error')) {
216 alterButton.show();
233 alterButton.show();
217 editButton.hide();
234 editButton.hide();
218 hiddenUrl.hide();
235 hiddenUrl.hide();
219 }
236 }
220 };
237 };
221
238
222 setInitialState();
239 setInitialState();
223 initEvents();
240 initEvents();
224 }();
241 }();
225
242
226 $('#show_more_clone_id').on('click', function(e){
243 selectMyGroup = function(element) {
227 $('#clone_id').show();
244 $("#repo_group").val($(element).data('personalGroupId')).trigger("change");
228 e.preventDefault();
245 };
229 });
230
246
231 $('#repo_landing_rev').select2(select2Options);
247 UsersAutoComplete('repo_owner', '${c.rhodecode_user.user_id}');
232 $('#repo_group').select2(select2Options);
233
234 UsersAutoComplete('user', '${c.rhodecode_user.user_id}');
235 $('#select_my_group').on('click', function(e){
236 e.preventDefault();
237 $("#repo_group").val($(this).data('personalGroupId')).trigger("change");
238 });
239 });
248 });
240 </script>
249 </script>
@@ -1,601 +1,601 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="root.mako"/>
2 <%inherit file="root.mako"/>
3
3
4 <div class="outerwrapper">
4 <div class="outerwrapper">
5 <!-- HEADER -->
5 <!-- HEADER -->
6 <div class="header">
6 <div class="header">
7 <div id="header-inner" class="wrapper">
7 <div id="header-inner" class="wrapper">
8 <div id="logo">
8 <div id="logo">
9 <div class="logo-wrapper">
9 <div class="logo-wrapper">
10 <a href="${h.url('home')}"><img src="${h.asset('images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
10 <a href="${h.url('home')}"><img src="${h.asset('images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
11 </div>
11 </div>
12 %if c.rhodecode_name:
12 %if c.rhodecode_name:
13 <div class="branding">- ${h.branding(c.rhodecode_name)}</div>
13 <div class="branding">- ${h.branding(c.rhodecode_name)}</div>
14 %endif
14 %endif
15 </div>
15 </div>
16 <!-- MENU BAR NAV -->
16 <!-- MENU BAR NAV -->
17 ${self.menu_bar_nav()}
17 ${self.menu_bar_nav()}
18 <!-- END MENU BAR NAV -->
18 <!-- END MENU BAR NAV -->
19 </div>
19 </div>
20 </div>
20 </div>
21 ${self.menu_bar_subnav()}
21 ${self.menu_bar_subnav()}
22 <!-- END HEADER -->
22 <!-- END HEADER -->
23
23
24 <!-- CONTENT -->
24 <!-- CONTENT -->
25 <div id="content" class="wrapper">
25 <div id="content" class="wrapper">
26
26
27 <rhodecode-toast id="notifications"></rhodecode-toast>
27 <rhodecode-toast id="notifications"></rhodecode-toast>
28
28
29 <div class="main">
29 <div class="main">
30 ${next.main()}
30 ${next.main()}
31 </div>
31 </div>
32 </div>
32 </div>
33 <!-- END CONTENT -->
33 <!-- END CONTENT -->
34
34
35 </div>
35 </div>
36 <!-- FOOTER -->
36 <!-- FOOTER -->
37 <div id="footer">
37 <div id="footer">
38 <div id="footer-inner" class="title wrapper">
38 <div id="footer-inner" class="title wrapper">
39 <div>
39 <div>
40 <p class="footer-link-right">
40 <p class="footer-link-right">
41 % if c.visual.show_version:
41 % if c.visual.show_version:
42 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
42 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
43 % endif
43 % endif
44 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
44 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
45 % if c.visual.rhodecode_support_url:
45 % if c.visual.rhodecode_support_url:
46 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
46 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
47 % endif
47 % endif
48 </p>
48 </p>
49 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
49 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
50 <p class="server-instance" style="display:${sid}">
50 <p class="server-instance" style="display:${sid}">
51 ## display hidden instance ID if specially defined
51 ## display hidden instance ID if specially defined
52 % if c.rhodecode_instanceid:
52 % if c.rhodecode_instanceid:
53 ${_('RhodeCode instance id: %s') % c.rhodecode_instanceid}
53 ${_('RhodeCode instance id: %s') % c.rhodecode_instanceid}
54 % endif
54 % endif
55 </p>
55 </p>
56 </div>
56 </div>
57 </div>
57 </div>
58 </div>
58 </div>
59
59
60 <!-- END FOOTER -->
60 <!-- END FOOTER -->
61
61
62 ### MAKO DEFS ###
62 ### MAKO DEFS ###
63
63
64 <%def name="menu_bar_subnav()">
64 <%def name="menu_bar_subnav()">
65 </%def>
65 </%def>
66
66
67 <%def name="breadcrumbs(class_='breadcrumbs')">
67 <%def name="breadcrumbs(class_='breadcrumbs')">
68 <div class="${class_}">
68 <div class="${class_}">
69 ${self.breadcrumbs_links()}
69 ${self.breadcrumbs_links()}
70 </div>
70 </div>
71 </%def>
71 </%def>
72
72
73 <%def name="admin_menu()">
73 <%def name="admin_menu()">
74 <ul class="admin_menu submenu">
74 <ul class="admin_menu submenu">
75 <li><a href="${h.url('admin_home')}">${_('Admin journal')}</a></li>
75 <li><a href="${h.url('admin_home')}">${_('Admin journal')}</a></li>
76 <li><a href="${h.url('repos')}">${_('Repositories')}</a></li>
76 <li><a href="${h.url('repos')}">${_('Repositories')}</a></li>
77 <li><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
77 <li><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
78 <li><a href="${h.route_path('users')}">${_('Users')}</a></li>
78 <li><a href="${h.route_path('users')}">${_('Users')}</a></li>
79 <li><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
79 <li><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
80 <li><a href="${h.url('admin_permissions_application')}">${_('Permissions')}</a></li>
80 <li><a href="${h.url('admin_permissions_application')}">${_('Permissions')}</a></li>
81 <li><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
81 <li><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
82 <li><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
82 <li><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
83 <li><a href="${h.url('admin_defaults_repositories')}">${_('Defaults')}</a></li>
83 <li><a href="${h.url('admin_defaults_repositories')}">${_('Defaults')}</a></li>
84 <li class="last"><a href="${h.url('admin_settings')}">${_('Settings')}</a></li>
84 <li class="last"><a href="${h.url('admin_settings')}">${_('Settings')}</a></li>
85 </ul>
85 </ul>
86 </%def>
86 </%def>
87
87
88
88
89 <%def name="dt_info_panel(elements)">
89 <%def name="dt_info_panel(elements)">
90 <dl class="dl-horizontal">
90 <dl class="dl-horizontal">
91 %for dt, dd, title, show_items in elements:
91 %for dt, dd, title, show_items in elements:
92 <dt>${dt}:</dt>
92 <dt>${dt}:</dt>
93 <dd title="${title}">
93 <dd title="${title}">
94 %if callable(dd):
94 %if callable(dd):
95 ## allow lazy evaluation of elements
95 ## allow lazy evaluation of elements
96 ${dd()}
96 ${dd()}
97 %else:
97 %else:
98 ${dd}
98 ${dd}
99 %endif
99 %endif
100 %if show_items:
100 %if show_items:
101 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
101 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
102 %endif
102 %endif
103 </dd>
103 </dd>
104
104
105 %if show_items:
105 %if show_items:
106 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
106 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
107 %for item in show_items:
107 %for item in show_items:
108 <dt></dt>
108 <dt></dt>
109 <dd>${item}</dd>
109 <dd>${item}</dd>
110 %endfor
110 %endfor
111 </div>
111 </div>
112 %endif
112 %endif
113
113
114 %endfor
114 %endfor
115 </dl>
115 </dl>
116 </%def>
116 </%def>
117
117
118
118
119 <%def name="gravatar(email, size=16)">
119 <%def name="gravatar(email, size=16)">
120 <%
120 <%
121 if (size > 16):
121 if (size > 16):
122 gravatar_class = 'gravatar gravatar-large'
122 gravatar_class = 'gravatar gravatar-large'
123 else:
123 else:
124 gravatar_class = 'gravatar'
124 gravatar_class = 'gravatar'
125 %>
125 %>
126 <%doc>
126 <%doc>
127 TODO: johbo: For now we serve double size images to make it smooth
127 TODO: johbo: For now we serve double size images to make it smooth
128 for retina. This is how it worked until now. Should be replaced
128 for retina. This is how it worked until now. Should be replaced
129 with a better solution at some point.
129 with a better solution at some point.
130 </%doc>
130 </%doc>
131 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
131 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
132 </%def>
132 </%def>
133
133
134
134
135 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
135 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
136 <% email = h.email_or_none(contact) %>
136 <% email = h.email_or_none(contact) %>
137 <div class="rc-user tooltip" title="${h.author_string(email)}">
137 <div class="rc-user tooltip" title="${h.author_string(email)}">
138 ${self.gravatar(email, size)}
138 ${self.gravatar(email, size)}
139 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
139 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
140 </div>
140 </div>
141 </%def>
141 </%def>
142
142
143
143
144 ## admin menu used for people that have some admin resources
144 ## admin menu used for people that have some admin resources
145 <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)">
145 <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)">
146 <ul class="submenu">
146 <ul class="submenu">
147 %if repositories:
147 %if repositories:
148 <li class="local-admin-repos"><a href="${h.url('repos')}">${_('Repositories')}</a></li>
148 <li class="local-admin-repos"><a href="${h.url('repos')}">${_('Repositories')}</a></li>
149 %endif
149 %endif
150 %if repository_groups:
150 %if repository_groups:
151 <li class="local-admin-repo-groups"><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
151 <li class="local-admin-repo-groups"><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
152 %endif
152 %endif
153 %if user_groups:
153 %if user_groups:
154 <li class="local-admin-user-groups"><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
154 <li class="local-admin-user-groups"><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
155 %endif
155 %endif
156 </ul>
156 </ul>
157 </%def>
157 </%def>
158
158
159 <%def name="repo_page_title(repo_instance)">
159 <%def name="repo_page_title(repo_instance)">
160 <div class="title-content">
160 <div class="title-content">
161 <div class="title-main">
161 <div class="title-main">
162 ## SVN/HG/GIT icons
162 ## SVN/HG/GIT icons
163 %if h.is_hg(repo_instance):
163 %if h.is_hg(repo_instance):
164 <i class="icon-hg"></i>
164 <i class="icon-hg"></i>
165 %endif
165 %endif
166 %if h.is_git(repo_instance):
166 %if h.is_git(repo_instance):
167 <i class="icon-git"></i>
167 <i class="icon-git"></i>
168 %endif
168 %endif
169 %if h.is_svn(repo_instance):
169 %if h.is_svn(repo_instance):
170 <i class="icon-svn"></i>
170 <i class="icon-svn"></i>
171 %endif
171 %endif
172
172
173 ## public/private
173 ## public/private
174 %if repo_instance.private:
174 %if repo_instance.private:
175 <i class="icon-repo-private"></i>
175 <i class="icon-repo-private"></i>
176 %else:
176 %else:
177 <i class="icon-repo-public"></i>
177 <i class="icon-repo-public"></i>
178 %endif
178 %endif
179
179
180 ## repo name with group name
180 ## repo name with group name
181 ${h.breadcrumb_repo_link(c.rhodecode_db_repo)}
181 ${h.breadcrumb_repo_link(c.rhodecode_db_repo)}
182
182
183 </div>
183 </div>
184
184
185 ## FORKED
185 ## FORKED
186 %if repo_instance.fork:
186 %if repo_instance.fork:
187 <p>
187 <p>
188 <i class="icon-code-fork"></i> ${_('Fork of')}
188 <i class="icon-code-fork"></i> ${_('Fork of')}
189 <a href="${h.url('summary_home',repo_name=repo_instance.fork.repo_name)}">${repo_instance.fork.repo_name}</a>
189 <a href="${h.url('summary_home',repo_name=repo_instance.fork.repo_name)}">${repo_instance.fork.repo_name}</a>
190 </p>
190 </p>
191 %endif
191 %endif
192
192
193 ## IMPORTED FROM REMOTE
193 ## IMPORTED FROM REMOTE
194 %if repo_instance.clone_uri:
194 %if repo_instance.clone_uri:
195 <p>
195 <p>
196 <i class="icon-code-fork"></i> ${_('Clone from')}
196 <i class="icon-code-fork"></i> ${_('Clone from')}
197 <a href="${h.url(h.safe_str(h.hide_credentials(repo_instance.clone_uri)))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
197 <a href="${h.url(h.safe_str(h.hide_credentials(repo_instance.clone_uri)))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
198 </p>
198 </p>
199 %endif
199 %endif
200
200
201 ## LOCKING STATUS
201 ## LOCKING STATUS
202 %if repo_instance.locked[0]:
202 %if repo_instance.locked[0]:
203 <p class="locking_locked">
203 <p class="locking_locked">
204 <i class="icon-repo-lock"></i>
204 <i class="icon-repo-lock"></i>
205 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
205 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
206 </p>
206 </p>
207 %elif repo_instance.enable_locking:
207 %elif repo_instance.enable_locking:
208 <p class="locking_unlocked">
208 <p class="locking_unlocked">
209 <i class="icon-repo-unlock"></i>
209 <i class="icon-repo-unlock"></i>
210 ${_('Repository not locked. Pull repository to lock it.')}
210 ${_('Repository not locked. Pull repository to lock it.')}
211 </p>
211 </p>
212 %endif
212 %endif
213
213
214 </div>
214 </div>
215 </%def>
215 </%def>
216
216
217 <%def name="repo_menu(active=None)">
217 <%def name="repo_menu(active=None)">
218 <%
218 <%
219 def is_active(selected):
219 def is_active(selected):
220 if selected == active:
220 if selected == active:
221 return "active"
221 return "active"
222 %>
222 %>
223
223
224 <!--- CONTEXT BAR -->
224 <!--- CONTEXT BAR -->
225 <div id="context-bar">
225 <div id="context-bar">
226 <div class="wrapper">
226 <div class="wrapper">
227 <ul id="context-pages" class="horizontal-list navigation">
227 <ul id="context-pages" class="horizontal-list navigation">
228 <li class="${is_active('summary')}"><a class="menulink" href="${h.url('summary_home', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
228 <li class="${is_active('summary')}"><a class="menulink" href="${h.url('summary_home', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
229 <li class="${is_active('changelog')}"><a class="menulink" href="${h.url('changelog_home', repo_name=c.repo_name)}"><div class="menulabel">${_('Changelog')}</div></a></li>
229 <li class="${is_active('changelog')}"><a class="menulink" href="${h.url('changelog_home', repo_name=c.repo_name)}"><div class="menulabel">${_('Changelog')}</div></a></li>
230 <li class="${is_active('files')}"><a class="menulink" href="${h.url('files_home', repo_name=c.repo_name, revision=c.rhodecode_db_repo.landing_rev[1])}"><div class="menulabel">${_('Files')}</div></a></li>
230 <li class="${is_active('files')}"><a class="menulink" href="${h.url('files_home', repo_name=c.repo_name, revision=c.rhodecode_db_repo.landing_rev[1])}"><div class="menulabel">${_('Files')}</div></a></li>
231 <li class="${is_active('compare')}">
231 <li class="${is_active('compare')}">
232 <a class="menulink" href="${h.url('compare_home',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a>
232 <a class="menulink" href="${h.url('compare_home',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a>
233 </li>
233 </li>
234 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
234 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
235 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
235 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
236 <li class="${is_active('showpullrequest')}">
236 <li class="${is_active('showpullrequest')}">
237 <a class="menulink" href="${h.url('pullrequest_show_all',repo_name=c.repo_name)}" title="${_('Show Pull Requests for %s') % c.repo_name}">
237 <a class="menulink" href="${h.url('pullrequest_show_all',repo_name=c.repo_name)}" title="${_('Show Pull Requests for %s') % c.repo_name}">
238 %if c.repository_pull_requests:
238 %if c.repository_pull_requests:
239 <span class="pr_notifications">${c.repository_pull_requests}</span>
239 <span class="pr_notifications">${c.repository_pull_requests}</span>
240 %endif
240 %endif
241 <div class="menulabel">${_('Pull Requests')}</div>
241 <div class="menulabel">${_('Pull Requests')}</div>
242 </a>
242 </a>
243 </li>
243 </li>
244 %endif
244 %endif
245 <li class="${is_active('options')}">
245 <li class="${is_active('options')}">
246 <a class="menulink" href="#" class="dropdown"><div class="menulabel">${_('Options')} <div class="show_more"></div></div></a>
246 <a class="menulink" href="#" class="dropdown"><div class="menulabel">${_('Options')} <div class="show_more"></div></div></a>
247 <ul class="submenu">
247 <ul class="submenu">
248 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
248 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
249 <li><a href="${h.url('edit_repo',repo_name=c.repo_name)}">${_('Settings')}</a></li>
249 <li><a href="${h.route_path('edit_repo',repo_name=c.repo_name)}">${_('Settings')}</a></li>
250 %endif
250 %endif
251 %if c.rhodecode_db_repo.fork:
251 %if c.rhodecode_db_repo.fork:
252 <li><a href="${h.url('compare_url',repo_name=c.rhodecode_db_repo.fork.repo_name,source_ref_type=c.rhodecode_db_repo.landing_rev[0],source_ref=c.rhodecode_db_repo.landing_rev[1], target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1], merge=1)}">
252 <li><a href="${h.url('compare_url',repo_name=c.rhodecode_db_repo.fork.repo_name,source_ref_type=c.rhodecode_db_repo.landing_rev[0],source_ref=c.rhodecode_db_repo.landing_rev[1], target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1], merge=1)}">
253 ${_('Compare fork')}</a></li>
253 ${_('Compare fork')}</a></li>
254 %endif
254 %endif
255
255
256 <li><a href="${h.route_path('search_repo',repo_name=c.repo_name)}">${_('Search')}</a></li>
256 <li><a href="${h.route_path('search_repo',repo_name=c.repo_name)}">${_('Search')}</a></li>
257
257
258 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
258 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
259 %if c.rhodecode_db_repo.locked[0]:
259 %if c.rhodecode_db_repo.locked[0]:
260 <li><a class="locking_del" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Unlock')}</a></li>
260 <li><a class="locking_del" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Unlock')}</a></li>
261 %else:
261 %else:
262 <li><a class="locking_add" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Lock')}</a></li>
262 <li><a class="locking_add" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Lock')}</a></li>
263 %endif
263 %endif
264 %endif
264 %endif
265 %if c.rhodecode_user.username != h.DEFAULT_USER:
265 %if c.rhodecode_user.username != h.DEFAULT_USER:
266 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
266 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
267 <li><a href="${h.url('repo_fork_home',repo_name=c.repo_name)}">${_('Fork')}</a></li>
267 <li><a href="${h.url('repo_fork_home',repo_name=c.repo_name)}">${_('Fork')}</a></li>
268 <li><a href="${h.url('pullrequest_home',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
268 <li><a href="${h.url('pullrequest_home',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
269 %endif
269 %endif
270 %endif
270 %endif
271 </ul>
271 </ul>
272 </li>
272 </li>
273 </ul>
273 </ul>
274 </div>
274 </div>
275 <div class="clear"></div>
275 <div class="clear"></div>
276 </div>
276 </div>
277 <!--- END CONTEXT BAR -->
277 <!--- END CONTEXT BAR -->
278
278
279 </%def>
279 </%def>
280
280
281 <%def name="usermenu(active=False)">
281 <%def name="usermenu(active=False)">
282 ## USER MENU
282 ## USER MENU
283 <li id="quick_login_li" class="${'active' if active else ''}">
283 <li id="quick_login_li" class="${'active' if active else ''}">
284 <a id="quick_login_link" class="menulink childs">
284 <a id="quick_login_link" class="menulink childs">
285 ${gravatar(c.rhodecode_user.email, 20)}
285 ${gravatar(c.rhodecode_user.email, 20)}
286 <span class="user">
286 <span class="user">
287 %if c.rhodecode_user.username != h.DEFAULT_USER:
287 %if c.rhodecode_user.username != h.DEFAULT_USER:
288 <span class="menu_link_user">${c.rhodecode_user.username}</span><div class="show_more"></div>
288 <span class="menu_link_user">${c.rhodecode_user.username}</span><div class="show_more"></div>
289 %else:
289 %else:
290 <span>${_('Sign in')}</span>
290 <span>${_('Sign in')}</span>
291 %endif
291 %endif
292 </span>
292 </span>
293 </a>
293 </a>
294
294
295 <div class="user-menu submenu">
295 <div class="user-menu submenu">
296 <div id="quick_login">
296 <div id="quick_login">
297 %if c.rhodecode_user.username == h.DEFAULT_USER:
297 %if c.rhodecode_user.username == h.DEFAULT_USER:
298 <h4>${_('Sign in to your account')}</h4>
298 <h4>${_('Sign in to your account')}</h4>
299 ${h.form(h.route_path('login', _query={'came_from': h.url.current()}), needs_csrf_token=False)}
299 ${h.form(h.route_path('login', _query={'came_from': h.url.current()}), needs_csrf_token=False)}
300 <div class="form form-vertical">
300 <div class="form form-vertical">
301 <div class="fields">
301 <div class="fields">
302 <div class="field">
302 <div class="field">
303 <div class="label">
303 <div class="label">
304 <label for="username">${_('Username')}:</label>
304 <label for="username">${_('Username')}:</label>
305 </div>
305 </div>
306 <div class="input">
306 <div class="input">
307 ${h.text('username',class_='focus',tabindex=1)}
307 ${h.text('username',class_='focus',tabindex=1)}
308 </div>
308 </div>
309
309
310 </div>
310 </div>
311 <div class="field">
311 <div class="field">
312 <div class="label">
312 <div class="label">
313 <label for="password">${_('Password')}:</label>
313 <label for="password">${_('Password')}:</label>
314 %if h.HasPermissionAny('hg.password_reset.enabled')():
314 %if h.HasPermissionAny('hg.password_reset.enabled')():
315 <span class="forgot_password">${h.link_to(_('(Forgot password?)'),h.route_path('reset_password'), class_='pwd_reset')}</span>
315 <span class="forgot_password">${h.link_to(_('(Forgot password?)'),h.route_path('reset_password'), class_='pwd_reset')}</span>
316 %endif
316 %endif
317 </div>
317 </div>
318 <div class="input">
318 <div class="input">
319 ${h.password('password',class_='focus',tabindex=2)}
319 ${h.password('password',class_='focus',tabindex=2)}
320 </div>
320 </div>
321 </div>
321 </div>
322 <div class="buttons">
322 <div class="buttons">
323 <div class="register">
323 <div class="register">
324 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
324 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
325 ${h.link_to(_("Don't have an account ?"),h.route_path('register'))}
325 ${h.link_to(_("Don't have an account ?"),h.route_path('register'))}
326 %endif
326 %endif
327 </div>
327 </div>
328 <div class="submit">
328 <div class="submit">
329 ${h.submit('sign_in',_('Sign In'),class_="btn btn-small",tabindex=3)}
329 ${h.submit('sign_in',_('Sign In'),class_="btn btn-small",tabindex=3)}
330 </div>
330 </div>
331 </div>
331 </div>
332 </div>
332 </div>
333 </div>
333 </div>
334 ${h.end_form()}
334 ${h.end_form()}
335 %else:
335 %else:
336 <div class="">
336 <div class="">
337 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
337 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
338 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
338 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
339 <div class="email">${c.rhodecode_user.email}</div>
339 <div class="email">${c.rhodecode_user.email}</div>
340 </div>
340 </div>
341 <div class="">
341 <div class="">
342 <ol class="links">
342 <ol class="links">
343 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
343 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
344 % if c.rhodecode_user.personal_repo_group:
344 % if c.rhodecode_user.personal_repo_group:
345 <li>${h.link_to(_(u'My personal group'), h.url('repo_group_home', group_name=c.rhodecode_user.personal_repo_group.group_name))}</li>
345 <li>${h.link_to(_(u'My personal group'), h.url('repo_group_home', group_name=c.rhodecode_user.personal_repo_group.group_name))}</li>
346 % endif
346 % endif
347 <li class="logout">
347 <li class="logout">
348 ${h.secure_form(h.route_path('logout'))}
348 ${h.secure_form(h.route_path('logout'))}
349 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
349 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
350 ${h.end_form()}
350 ${h.end_form()}
351 </li>
351 </li>
352 </ol>
352 </ol>
353 </div>
353 </div>
354 %endif
354 %endif
355 </div>
355 </div>
356 </div>
356 </div>
357 %if c.rhodecode_user.username != h.DEFAULT_USER:
357 %if c.rhodecode_user.username != h.DEFAULT_USER:
358 <div class="pill_container">
358 <div class="pill_container">
359 % if c.unread_notifications == 0:
359 % if c.unread_notifications == 0:
360 <a class="menu_link_notifications empty" href="${h.url('notifications')}">${c.unread_notifications}</a>
360 <a class="menu_link_notifications empty" href="${h.url('notifications')}">${c.unread_notifications}</a>
361 % else:
361 % else:
362 <a class="menu_link_notifications" href="${h.url('notifications')}">${c.unread_notifications}</a>
362 <a class="menu_link_notifications" href="${h.url('notifications')}">${c.unread_notifications}</a>
363 % endif
363 % endif
364 </div>
364 </div>
365 % endif
365 % endif
366 </li>
366 </li>
367 </%def>
367 </%def>
368
368
369 <%def name="menu_items(active=None)">
369 <%def name="menu_items(active=None)">
370 <%
370 <%
371 def is_active(selected):
371 def is_active(selected):
372 if selected == active:
372 if selected == active:
373 return "active"
373 return "active"
374 return ""
374 return ""
375 %>
375 %>
376 <ul id="quick" class="main_nav navigation horizontal-list">
376 <ul id="quick" class="main_nav navigation horizontal-list">
377 <!-- repo switcher -->
377 <!-- repo switcher -->
378 <li class="${is_active('repositories')} repo_switcher_li has_select2">
378 <li class="${is_active('repositories')} repo_switcher_li has_select2">
379 <input id="repo_switcher" name="repo_switcher" type="hidden">
379 <input id="repo_switcher" name="repo_switcher" type="hidden">
380 </li>
380 </li>
381
381
382 ## ROOT MENU
382 ## ROOT MENU
383 %if c.rhodecode_user.username != h.DEFAULT_USER:
383 %if c.rhodecode_user.username != h.DEFAULT_USER:
384 <li class="${is_active('journal')}">
384 <li class="${is_active('journal')}">
385 <a class="menulink" title="${_('Show activity journal')}" href="${h.url('journal')}">
385 <a class="menulink" title="${_('Show activity journal')}" href="${h.url('journal')}">
386 <div class="menulabel">${_('Journal')}</div>
386 <div class="menulabel">${_('Journal')}</div>
387 </a>
387 </a>
388 </li>
388 </li>
389 %else:
389 %else:
390 <li class="${is_active('journal')}">
390 <li class="${is_active('journal')}">
391 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.url('public_journal')}">
391 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.url('public_journal')}">
392 <div class="menulabel">${_('Public journal')}</div>
392 <div class="menulabel">${_('Public journal')}</div>
393 </a>
393 </a>
394 </li>
394 </li>
395 %endif
395 %endif
396 <li class="${is_active('gists')}">
396 <li class="${is_active('gists')}">
397 <a class="menulink childs" title="${_('Show Gists')}" href="${h.url('gists')}">
397 <a class="menulink childs" title="${_('Show Gists')}" href="${h.url('gists')}">
398 <div class="menulabel">${_('Gists')}</div>
398 <div class="menulabel">${_('Gists')}</div>
399 </a>
399 </a>
400 </li>
400 </li>
401 <li class="${is_active('search')}">
401 <li class="${is_active('search')}">
402 <a class="menulink" title="${_('Search in repositories you have access to')}" href="${h.route_path('search')}">
402 <a class="menulink" title="${_('Search in repositories you have access to')}" href="${h.route_path('search')}">
403 <div class="menulabel">${_('Search')}</div>
403 <div class="menulabel">${_('Search')}</div>
404 </a>
404 </a>
405 </li>
405 </li>
406 % if h.HasPermissionAll('hg.admin')('access admin main page'):
406 % if h.HasPermissionAll('hg.admin')('access admin main page'):
407 <li class="${is_active('admin')}">
407 <li class="${is_active('admin')}">
408 <a class="menulink childs" title="${_('Admin settings')}" href="#" onclick="return false;">
408 <a class="menulink childs" title="${_('Admin settings')}" href="#" onclick="return false;">
409 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
409 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
410 </a>
410 </a>
411 ${admin_menu()}
411 ${admin_menu()}
412 </li>
412 </li>
413 % elif c.rhodecode_user.repositories_admin or c.rhodecode_user.repository_groups_admin or c.rhodecode_user.user_groups_admin:
413 % elif c.rhodecode_user.repositories_admin or c.rhodecode_user.repository_groups_admin or c.rhodecode_user.user_groups_admin:
414 <li class="${is_active('admin')}">
414 <li class="${is_active('admin')}">
415 <a class="menulink childs" title="${_('Delegated Admin settings')}">
415 <a class="menulink childs" title="${_('Delegated Admin settings')}">
416 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
416 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
417 </a>
417 </a>
418 ${admin_menu_simple(c.rhodecode_user.repositories_admin,
418 ${admin_menu_simple(c.rhodecode_user.repositories_admin,
419 c.rhodecode_user.repository_groups_admin,
419 c.rhodecode_user.repository_groups_admin,
420 c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
420 c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
421 </li>
421 </li>
422 % endif
422 % endif
423 % if c.debug_style:
423 % if c.debug_style:
424 <li class="${is_active('debug_style')}">
424 <li class="${is_active('debug_style')}">
425 <a class="menulink" title="${_('Style')}" href="${h.url('debug_style_home')}">
425 <a class="menulink" title="${_('Style')}" href="${h.url('debug_style_home')}">
426 <div class="menulabel">${_('Style')}</div>
426 <div class="menulabel">${_('Style')}</div>
427 </a>
427 </a>
428 </li>
428 </li>
429 % endif
429 % endif
430 ## render extra user menu
430 ## render extra user menu
431 ${usermenu(active=(active=='my_account'))}
431 ${usermenu(active=(active=='my_account'))}
432 </ul>
432 </ul>
433
433
434 <script type="text/javascript">
434 <script type="text/javascript">
435 var visual_show_public_icon = "${c.visual.show_public_icon}" == "True";
435 var visual_show_public_icon = "${c.visual.show_public_icon}" == "True";
436
436
437 /*format the look of items in the list*/
437 /*format the look of items in the list*/
438 var format = function(state, escapeMarkup){
438 var format = function(state, escapeMarkup){
439 if (!state.id){
439 if (!state.id){
440 return state.text; // optgroup
440 return state.text; // optgroup
441 }
441 }
442 var obj_dict = state.obj;
442 var obj_dict = state.obj;
443 var tmpl = '';
443 var tmpl = '';
444
444
445 if(obj_dict && state.type == 'repo'){
445 if(obj_dict && state.type == 'repo'){
446 if(obj_dict['repo_type'] === 'hg'){
446 if(obj_dict['repo_type'] === 'hg'){
447 tmpl += '<i class="icon-hg"></i> ';
447 tmpl += '<i class="icon-hg"></i> ';
448 }
448 }
449 else if(obj_dict['repo_type'] === 'git'){
449 else if(obj_dict['repo_type'] === 'git'){
450 tmpl += '<i class="icon-git"></i> ';
450 tmpl += '<i class="icon-git"></i> ';
451 }
451 }
452 else if(obj_dict['repo_type'] === 'svn'){
452 else if(obj_dict['repo_type'] === 'svn'){
453 tmpl += '<i class="icon-svn"></i> ';
453 tmpl += '<i class="icon-svn"></i> ';
454 }
454 }
455 if(obj_dict['private']){
455 if(obj_dict['private']){
456 tmpl += '<i class="icon-lock" ></i> ';
456 tmpl += '<i class="icon-lock" ></i> ';
457 }
457 }
458 else if(visual_show_public_icon){
458 else if(visual_show_public_icon){
459 tmpl += '<i class="icon-unlock-alt"></i> ';
459 tmpl += '<i class="icon-unlock-alt"></i> ';
460 }
460 }
461 }
461 }
462 if(obj_dict && state.type == 'commit') {
462 if(obj_dict && state.type == 'commit') {
463 tmpl += '<i class="icon-tag"></i>';
463 tmpl += '<i class="icon-tag"></i>';
464 }
464 }
465 if(obj_dict && state.type == 'group'){
465 if(obj_dict && state.type == 'group'){
466 tmpl += '<i class="icon-folder-close"></i> ';
466 tmpl += '<i class="icon-folder-close"></i> ';
467 }
467 }
468 tmpl += escapeMarkup(state.text);
468 tmpl += escapeMarkup(state.text);
469 return tmpl;
469 return tmpl;
470 };
470 };
471
471
472 var formatResult = function(result, container, query, escapeMarkup) {
472 var formatResult = function(result, container, query, escapeMarkup) {
473 return format(result, escapeMarkup);
473 return format(result, escapeMarkup);
474 };
474 };
475
475
476 var formatSelection = function(data, container, escapeMarkup) {
476 var formatSelection = function(data, container, escapeMarkup) {
477 return format(data, escapeMarkup);
477 return format(data, escapeMarkup);
478 };
478 };
479
479
480 $("#repo_switcher").select2({
480 $("#repo_switcher").select2({
481 cachedDataSource: {},
481 cachedDataSource: {},
482 minimumInputLength: 2,
482 minimumInputLength: 2,
483 placeholder: '<div class="menulabel">${_('Go to')} <div class="show_more"></div></div>',
483 placeholder: '<div class="menulabel">${_('Go to')} <div class="show_more"></div></div>',
484 dropdownAutoWidth: true,
484 dropdownAutoWidth: true,
485 formatResult: formatResult,
485 formatResult: formatResult,
486 formatSelection: formatSelection,
486 formatSelection: formatSelection,
487 containerCssClass: "repo-switcher",
487 containerCssClass: "repo-switcher",
488 dropdownCssClass: "repo-switcher-dropdown",
488 dropdownCssClass: "repo-switcher-dropdown",
489 escapeMarkup: function(m){
489 escapeMarkup: function(m){
490 // don't escape our custom placeholder
490 // don't escape our custom placeholder
491 if(m.substr(0,23) == '<div class="menulabel">'){
491 if(m.substr(0,23) == '<div class="menulabel">'){
492 return m;
492 return m;
493 }
493 }
494
494
495 return Select2.util.escapeMarkup(m);
495 return Select2.util.escapeMarkup(m);
496 },
496 },
497 query: $.debounce(250, function(query){
497 query: $.debounce(250, function(query){
498 self = this;
498 self = this;
499 var cacheKey = query.term;
499 var cacheKey = query.term;
500 var cachedData = self.cachedDataSource[cacheKey];
500 var cachedData = self.cachedDataSource[cacheKey];
501
501
502 if (cachedData) {
502 if (cachedData) {
503 query.callback({results: cachedData.results});
503 query.callback({results: cachedData.results});
504 } else {
504 } else {
505 $.ajax({
505 $.ajax({
506 url: pyroutes.url('goto_switcher_data'),
506 url: pyroutes.url('goto_switcher_data'),
507 data: {'query': query.term},
507 data: {'query': query.term},
508 dataType: 'json',
508 dataType: 'json',
509 type: 'GET',
509 type: 'GET',
510 success: function(data) {
510 success: function(data) {
511 self.cachedDataSource[cacheKey] = data;
511 self.cachedDataSource[cacheKey] = data;
512 query.callback({results: data.results});
512 query.callback({results: data.results});
513 },
513 },
514 error: function(data, textStatus, errorThrown) {
514 error: function(data, textStatus, errorThrown) {
515 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
515 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
516 }
516 }
517 })
517 })
518 }
518 }
519 })
519 })
520 });
520 });
521
521
522 $("#repo_switcher").on('select2-selecting', function(e){
522 $("#repo_switcher").on('select2-selecting', function(e){
523 e.preventDefault();
523 e.preventDefault();
524 window.location = e.choice.url;
524 window.location = e.choice.url;
525 });
525 });
526
526
527 </script>
527 </script>
528 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
528 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
529 </%def>
529 </%def>
530
530
531 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
531 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
532 <div class="modal-dialog">
532 <div class="modal-dialog">
533 <div class="modal-content">
533 <div class="modal-content">
534 <div class="modal-header">
534 <div class="modal-header">
535 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
535 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
536 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
536 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
537 </div>
537 </div>
538 <div class="modal-body">
538 <div class="modal-body">
539 <div class="block-left">
539 <div class="block-left">
540 <table class="keyboard-mappings">
540 <table class="keyboard-mappings">
541 <tbody>
541 <tbody>
542 <tr>
542 <tr>
543 <th></th>
543 <th></th>
544 <th>${_('Site-wide shortcuts')}</th>
544 <th>${_('Site-wide shortcuts')}</th>
545 </tr>
545 </tr>
546 <%
546 <%
547 elems = [
547 elems = [
548 ('/', 'Open quick search box'),
548 ('/', 'Open quick search box'),
549 ('g h', 'Goto home page'),
549 ('g h', 'Goto home page'),
550 ('g g', 'Goto my private gists page'),
550 ('g g', 'Goto my private gists page'),
551 ('g G', 'Goto my public gists page'),
551 ('g G', 'Goto my public gists page'),
552 ('n r', 'New repository page'),
552 ('n r', 'New repository page'),
553 ('n g', 'New gist page'),
553 ('n g', 'New gist page'),
554 ]
554 ]
555 %>
555 %>
556 %for key, desc in elems:
556 %for key, desc in elems:
557 <tr>
557 <tr>
558 <td class="keys">
558 <td class="keys">
559 <span class="key tag">${key}</span>
559 <span class="key tag">${key}</span>
560 </td>
560 </td>
561 <td>${desc}</td>
561 <td>${desc}</td>
562 </tr>
562 </tr>
563 %endfor
563 %endfor
564 </tbody>
564 </tbody>
565 </table>
565 </table>
566 </div>
566 </div>
567 <div class="block-left">
567 <div class="block-left">
568 <table class="keyboard-mappings">
568 <table class="keyboard-mappings">
569 <tbody>
569 <tbody>
570 <tr>
570 <tr>
571 <th></th>
571 <th></th>
572 <th>${_('Repositories')}</th>
572 <th>${_('Repositories')}</th>
573 </tr>
573 </tr>
574 <%
574 <%
575 elems = [
575 elems = [
576 ('g s', 'Goto summary page'),
576 ('g s', 'Goto summary page'),
577 ('g c', 'Goto changelog page'),
577 ('g c', 'Goto changelog page'),
578 ('g f', 'Goto files page'),
578 ('g f', 'Goto files page'),
579 ('g F', 'Goto files page with file search activated'),
579 ('g F', 'Goto files page with file search activated'),
580 ('g p', 'Goto pull requests page'),
580 ('g p', 'Goto pull requests page'),
581 ('g o', 'Goto repository settings'),
581 ('g o', 'Goto repository settings'),
582 ('g O', 'Goto repository permissions settings'),
582 ('g O', 'Goto repository permissions settings'),
583 ]
583 ]
584 %>
584 %>
585 %for key, desc in elems:
585 %for key, desc in elems:
586 <tr>
586 <tr>
587 <td class="keys">
587 <td class="keys">
588 <span class="key tag">${key}</span>
588 <span class="key tag">${key}</span>
589 </td>
589 </td>
590 <td>${desc}</td>
590 <td>${desc}</td>
591 </tr>
591 </tr>
592 %endfor
592 %endfor
593 </tbody>
593 </tbody>
594 </table>
594 </table>
595 </div>
595 </div>
596 </div>
596 </div>
597 <div class="modal-footer">
597 <div class="modal-footer">
598 </div>
598 </div>
599 </div><!-- /.modal-content -->
599 </div><!-- /.modal-content -->
600 </div><!-- /.modal-dialog -->
600 </div><!-- /.modal-dialog -->
601 </div><!-- /.modal -->
601 </div><!-- /.modal -->
@@ -1,317 +1,317 b''
1 ## DATA TABLE RE USABLE ELEMENTS
1 ## DATA TABLE RE USABLE ELEMENTS
2 ## usage:
2 ## usage:
3 ## <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
3 ## <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
4 <%namespace name="base" file="/base/base.mako"/>
4 <%namespace name="base" file="/base/base.mako"/>
5
5
6 ## REPOSITORY RENDERERS
6 ## REPOSITORY RENDERERS
7 <%def name="quick_menu(repo_name)">
7 <%def name="quick_menu(repo_name)">
8 <i class="pointer icon-more"></i>
8 <i class="pointer icon-more"></i>
9 <div class="menu_items_container hidden">
9 <div class="menu_items_container hidden">
10 <ul class="menu_items">
10 <ul class="menu_items">
11 <li>
11 <li>
12 <a title="${_('Summary')}" href="${h.url('summary_home',repo_name=repo_name)}">
12 <a title="${_('Summary')}" href="${h.url('summary_home',repo_name=repo_name)}">
13 <span>${_('Summary')}</span>
13 <span>${_('Summary')}</span>
14 </a>
14 </a>
15 </li>
15 </li>
16 <li>
16 <li>
17 <a title="${_('Changelog')}" href="${h.url('changelog_home',repo_name=repo_name)}">
17 <a title="${_('Changelog')}" href="${h.url('changelog_home',repo_name=repo_name)}">
18 <span>${_('Changelog')}</span>
18 <span>${_('Changelog')}</span>
19 </a>
19 </a>
20 </li>
20 </li>
21 <li>
21 <li>
22 <a title="${_('Files')}" href="${h.url('files_home',repo_name=repo_name)}">
22 <a title="${_('Files')}" href="${h.url('files_home',repo_name=repo_name)}">
23 <span>${_('Files')}</span>
23 <span>${_('Files')}</span>
24 </a>
24 </a>
25 </li>
25 </li>
26 <li>
26 <li>
27 <a title="${_('Fork')}" href="${h.url('repo_fork_home',repo_name=repo_name)}">
27 <a title="${_('Fork')}" href="${h.url('repo_fork_home',repo_name=repo_name)}">
28 <span>${_('Fork')}</span>
28 <span>${_('Fork')}</span>
29 </a>
29 </a>
30 </li>
30 </li>
31 </ul>
31 </ul>
32 </div>
32 </div>
33 </%def>
33 </%def>
34
34
35 <%def name="repo_name(name,rtype,rstate,private,fork_of,short_name=False,admin=False)">
35 <%def name="repo_name(name,rtype,rstate,private,fork_of,short_name=False,admin=False)">
36 <%
36 <%
37 def get_name(name,short_name=short_name):
37 def get_name(name,short_name=short_name):
38 if short_name:
38 if short_name:
39 return name.split('/')[-1]
39 return name.split('/')[-1]
40 else:
40 else:
41 return name
41 return name
42 %>
42 %>
43 <div class="${'repo_state_pending' if rstate == 'repo_state_pending' else ''} truncate">
43 <div class="${'repo_state_pending' if rstate == 'repo_state_pending' else ''} truncate">
44 ##NAME
44 ##NAME
45 <a href="${h.url('edit_repo' if admin else 'summary_home',repo_name=name)}">
45 <a href="${h.route_path('edit_repo',repo_name=name) if admin else h.url('summary_home',repo_name=name)}">
46
46
47 ##TYPE OF REPO
47 ##TYPE OF REPO
48 %if h.is_hg(rtype):
48 %if h.is_hg(rtype):
49 <span title="${_('Mercurial repository')}"><i class="icon-hg"></i></span>
49 <span title="${_('Mercurial repository')}"><i class="icon-hg"></i></span>
50 %elif h.is_git(rtype):
50 %elif h.is_git(rtype):
51 <span title="${_('Git repository')}"><i class="icon-git"></i></span>
51 <span title="${_('Git repository')}"><i class="icon-git"></i></span>
52 %elif h.is_svn(rtype):
52 %elif h.is_svn(rtype):
53 <span title="${_('Subversion repository')}"><i class="icon-svn"></i></span>
53 <span title="${_('Subversion repository')}"><i class="icon-svn"></i></span>
54 %endif
54 %endif
55
55
56 ##PRIVATE/PUBLIC
56 ##PRIVATE/PUBLIC
57 %if private and c.visual.show_private_icon:
57 %if private and c.visual.show_private_icon:
58 <i class="icon-lock" title="${_('Private repository')}"></i>
58 <i class="icon-lock" title="${_('Private repository')}"></i>
59 %elif not private and c.visual.show_public_icon:
59 %elif not private and c.visual.show_public_icon:
60 <i class="icon-unlock-alt" title="${_('Public repository')}"></i>
60 <i class="icon-unlock-alt" title="${_('Public repository')}"></i>
61 %else:
61 %else:
62 <span></span>
62 <span></span>
63 %endif
63 %endif
64 ${get_name(name)}
64 ${get_name(name)}
65 </a>
65 </a>
66 %if fork_of:
66 %if fork_of:
67 <a href="${h.url('summary_home',repo_name=fork_of.repo_name)}"><i class="icon-code-fork"></i></a>
67 <a href="${h.url('summary_home',repo_name=fork_of.repo_name)}"><i class="icon-code-fork"></i></a>
68 %endif
68 %endif
69 %if rstate == 'repo_state_pending':
69 %if rstate == 'repo_state_pending':
70 <i class="icon-cogs" title="${_('Repository creating in progress...')}"></i>
70 <i class="icon-cogs" title="${_('Repository creating in progress...')}"></i>
71 %endif
71 %endif
72 </div>
72 </div>
73 </%def>
73 </%def>
74
74
75 <%def name="repo_desc(description)">
75 <%def name="repo_desc(description)">
76 <div class="truncate-wrap">${description}</div>
76 <div class="truncate-wrap">${description}</div>
77 </%def>
77 </%def>
78
78
79 <%def name="last_change(last_change)">
79 <%def name="last_change(last_change)">
80 ${h.age_component(last_change)}
80 ${h.age_component(last_change)}
81 </%def>
81 </%def>
82
82
83 <%def name="revision(name,rev,tip,author,last_msg)">
83 <%def name="revision(name,rev,tip,author,last_msg)">
84 <div>
84 <div>
85 %if rev >= 0:
85 %if rev >= 0:
86 <code><a title="${h.tooltip('%s:\n\n%s' % (author,last_msg))}" class="tooltip" href="${h.url('changeset_home',repo_name=name,revision=tip)}">${'r%s:%s' % (rev,h.short_id(tip))}</a></code>
86 <code><a title="${h.tooltip('%s:\n\n%s' % (author,last_msg))}" class="tooltip" href="${h.url('changeset_home',repo_name=name,revision=tip)}">${'r%s:%s' % (rev,h.short_id(tip))}</a></code>
87 %else:
87 %else:
88 ${_('No commits yet')}
88 ${_('No commits yet')}
89 %endif
89 %endif
90 </div>
90 </div>
91 </%def>
91 </%def>
92
92
93 <%def name="rss(name)">
93 <%def name="rss(name)">
94 %if c.rhodecode_user.username != h.DEFAULT_USER:
94 %if c.rhodecode_user.username != h.DEFAULT_USER:
95 <a title="${_('Subscribe to %s rss feed')% name}" href="${h.url('rss_feed_home',repo_name=name,auth_token=c.rhodecode_user.feed_token)}"><i class="icon-rss-sign"></i></a>
95 <a title="${_('Subscribe to %s rss feed')% name}" href="${h.url('rss_feed_home',repo_name=name,auth_token=c.rhodecode_user.feed_token)}"><i class="icon-rss-sign"></i></a>
96 %else:
96 %else:
97 <a title="${_('Subscribe to %s rss feed')% name}" href="${h.url('rss_feed_home',repo_name=name)}"><i class="icon-rss-sign"></i></a>
97 <a title="${_('Subscribe to %s rss feed')% name}" href="${h.url('rss_feed_home',repo_name=name)}"><i class="icon-rss-sign"></i></a>
98 %endif
98 %endif
99 </%def>
99 </%def>
100
100
101 <%def name="atom(name)">
101 <%def name="atom(name)">
102 %if c.rhodecode_user.username != h.DEFAULT_USER:
102 %if c.rhodecode_user.username != h.DEFAULT_USER:
103 <a title="${_('Subscribe to %s atom feed')% name}" href="${h.url('atom_feed_home',repo_name=name,auth_token=c.rhodecode_user.feed_token)}"><i class="icon-rss-sign"></i></a>
103 <a title="${_('Subscribe to %s atom feed')% name}" href="${h.url('atom_feed_home',repo_name=name,auth_token=c.rhodecode_user.feed_token)}"><i class="icon-rss-sign"></i></a>
104 %else:
104 %else:
105 <a title="${_('Subscribe to %s atom feed')% name}" href="${h.url('atom_feed_home',repo_name=name)}"><i class="icon-rss-sign"></i></a>
105 <a title="${_('Subscribe to %s atom feed')% name}" href="${h.url('atom_feed_home',repo_name=name)}"><i class="icon-rss-sign"></i></a>
106 %endif
106 %endif
107 </%def>
107 </%def>
108
108
109 <%def name="user_gravatar(email, size=16)">
109 <%def name="user_gravatar(email, size=16)">
110 <div class="rc-user tooltip" title="${h.author_string(email)}">
110 <div class="rc-user tooltip" title="${h.author_string(email)}">
111 ${base.gravatar(email, 16)}
111 ${base.gravatar(email, 16)}
112 </div>
112 </div>
113 </%def>
113 </%def>
114
114
115 <%def name="repo_actions(repo_name, super_user=True)">
115 <%def name="repo_actions(repo_name, super_user=True)">
116 <div>
116 <div>
117 <div class="grid_edit">
117 <div class="grid_edit">
118 <a href="${h.url('edit_repo',repo_name=repo_name)}" title="${_('Edit')}">
118 <a href="${h.route_path('edit_repo',repo_name=repo_name)}" title="${_('Edit')}">
119 <i class="icon-pencil"></i>Edit</a>
119 <i class="icon-pencil"></i>Edit</a>
120 </div>
120 </div>
121 <div class="grid_delete">
121 <div class="grid_delete">
122 ${h.secure_form(h.url('repo', repo_name=repo_name),method='delete')}
122 ${h.secure_form(h.url('repo', repo_name=repo_name),method='delete')}
123 ${h.submit('remove_%s' % repo_name,_('Delete'),class_="btn btn-link btn-danger",
123 ${h.submit('remove_%s' % repo_name,_('Delete'),class_="btn btn-link btn-danger",
124 onclick="return confirm('"+_('Confirm to delete this repository: %s') % repo_name+"');")}
124 onclick="return confirm('"+_('Confirm to delete this repository: %s') % repo_name+"');")}
125 ${h.end_form()}
125 ${h.end_form()}
126 </div>
126 </div>
127 </div>
127 </div>
128 </%def>
128 </%def>
129
129
130 <%def name="repo_state(repo_state)">
130 <%def name="repo_state(repo_state)">
131 <div>
131 <div>
132 %if repo_state == 'repo_state_pending':
132 %if repo_state == 'repo_state_pending':
133 <div class="tag tag4">${_('Creating')}</div>
133 <div class="tag tag4">${_('Creating')}</div>
134 %elif repo_state == 'repo_state_created':
134 %elif repo_state == 'repo_state_created':
135 <div class="tag tag1">${_('Created')}</div>
135 <div class="tag tag1">${_('Created')}</div>
136 %else:
136 %else:
137 <div class="tag alert2" title="${repo_state}">invalid</div>
137 <div class="tag alert2" title="${repo_state}">invalid</div>
138 %endif
138 %endif
139 </div>
139 </div>
140 </%def>
140 </%def>
141
141
142
142
143 ## REPO GROUP RENDERERS
143 ## REPO GROUP RENDERERS
144 <%def name="quick_repo_group_menu(repo_group_name)">
144 <%def name="quick_repo_group_menu(repo_group_name)">
145 <i class="pointer icon-more"></i>
145 <i class="pointer icon-more"></i>
146 <div class="menu_items_container hidden">
146 <div class="menu_items_container hidden">
147 <ul class="menu_items">
147 <ul class="menu_items">
148 <li>
148 <li>
149 <a href="${h.url('repo_group_home',group_name=repo_group_name)}">
149 <a href="${h.url('repo_group_home',group_name=repo_group_name)}">
150 <span class="icon">
150 <span class="icon">
151 <i class="icon-file-text"></i>
151 <i class="icon-file-text"></i>
152 </span>
152 </span>
153 <span>${_('Summary')}</span>
153 <span>${_('Summary')}</span>
154 </a>
154 </a>
155 </li>
155 </li>
156
156
157 </ul>
157 </ul>
158 </div>
158 </div>
159 </%def>
159 </%def>
160
160
161 <%def name="repo_group_name(repo_group_name, children_groups=None)">
161 <%def name="repo_group_name(repo_group_name, children_groups=None)">
162 <div>
162 <div>
163 <a href="${h.url('repo_group_home',group_name=repo_group_name)}">
163 <a href="${h.url('repo_group_home',group_name=repo_group_name)}">
164 <i class="icon-folder-close" title="${_('Repository group')}"></i>
164 <i class="icon-folder-close" title="${_('Repository group')}"></i>
165 %if children_groups:
165 %if children_groups:
166 ${h.literal(' &raquo; '.join(children_groups))}
166 ${h.literal(' &raquo; '.join(children_groups))}
167 %else:
167 %else:
168 ${repo_group_name}
168 ${repo_group_name}
169 %endif
169 %endif
170 </a>
170 </a>
171 </div>
171 </div>
172 </%def>
172 </%def>
173
173
174 <%def name="repo_group_desc(description)">
174 <%def name="repo_group_desc(description)">
175 <div class="truncate-wrap">${description}</div>
175 <div class="truncate-wrap">${description}</div>
176 </%def>
176 </%def>
177
177
178 <%def name="repo_group_actions(repo_group_id, repo_group_name, gr_count)">
178 <%def name="repo_group_actions(repo_group_id, repo_group_name, gr_count)">
179 <div class="grid_edit">
179 <div class="grid_edit">
180 <a href="${h.url('edit_repo_group',group_name=repo_group_name)}" title="${_('Edit')}">Edit</a>
180 <a href="${h.url('edit_repo_group',group_name=repo_group_name)}" title="${_('Edit')}">Edit</a>
181 </div>
181 </div>
182 <div class="grid_delete">
182 <div class="grid_delete">
183 ${h.secure_form(h.url('delete_repo_group', group_name=repo_group_name),method='delete')}
183 ${h.secure_form(h.url('delete_repo_group', group_name=repo_group_name),method='delete')}
184 ${h.submit('remove_%s' % repo_group_name,_('Delete'),class_="btn btn-link btn-danger",
184 ${h.submit('remove_%s' % repo_group_name,_('Delete'),class_="btn btn-link btn-danger",
185 onclick="return confirm('"+ungettext('Confirm to delete this group: %s with %s repository','Confirm to delete this group: %s with %s repositories',gr_count) % (repo_group_name, gr_count)+"');")}
185 onclick="return confirm('"+ungettext('Confirm to delete this group: %s with %s repository','Confirm to delete this group: %s with %s repositories',gr_count) % (repo_group_name, gr_count)+"');")}
186 ${h.end_form()}
186 ${h.end_form()}
187 </div>
187 </div>
188 </%def>
188 </%def>
189
189
190
190
191 <%def name="user_actions(user_id, username)">
191 <%def name="user_actions(user_id, username)">
192 <div class="grid_edit">
192 <div class="grid_edit">
193 <a href="${h.url('edit_user',user_id=user_id)}" title="${_('Edit')}">
193 <a href="${h.url('edit_user',user_id=user_id)}" title="${_('Edit')}">
194 <i class="icon-pencil"></i>Edit</a>
194 <i class="icon-pencil"></i>Edit</a>
195 </div>
195 </div>
196 <div class="grid_delete">
196 <div class="grid_delete">
197 ${h.secure_form(h.url('delete_user', user_id=user_id),method='delete')}
197 ${h.secure_form(h.url('delete_user', user_id=user_id),method='delete')}
198 ${h.submit('remove_',_('Delete'),id="remove_user_%s" % user_id, class_="btn btn-link btn-danger",
198 ${h.submit('remove_',_('Delete'),id="remove_user_%s" % user_id, class_="btn btn-link btn-danger",
199 onclick="return confirm('"+_('Confirm to delete this user: %s') % username+"');")}
199 onclick="return confirm('"+_('Confirm to delete this user: %s') % username+"');")}
200 ${h.end_form()}
200 ${h.end_form()}
201 </div>
201 </div>
202 </%def>
202 </%def>
203
203
204 <%def name="user_group_actions(user_group_id, user_group_name)">
204 <%def name="user_group_actions(user_group_id, user_group_name)">
205 <div class="grid_edit">
205 <div class="grid_edit">
206 <a href="${h.url('edit_users_group', user_group_id=user_group_id)}" title="${_('Edit')}">Edit</a>
206 <a href="${h.url('edit_users_group', user_group_id=user_group_id)}" title="${_('Edit')}">Edit</a>
207 </div>
207 </div>
208 <div class="grid_delete">
208 <div class="grid_delete">
209 ${h.secure_form(h.url('delete_users_group', user_group_id=user_group_id),method='delete')}
209 ${h.secure_form(h.url('delete_users_group', user_group_id=user_group_id),method='delete')}
210 ${h.submit('remove_',_('Delete'),id="remove_group_%s" % user_group_id, class_="btn btn-link btn-danger",
210 ${h.submit('remove_',_('Delete'),id="remove_group_%s" % user_group_id, class_="btn btn-link btn-danger",
211 onclick="return confirm('"+_('Confirm to delete this user group: %s') % user_group_name+"');")}
211 onclick="return confirm('"+_('Confirm to delete this user group: %s') % user_group_name+"');")}
212 ${h.end_form()}
212 ${h.end_form()}
213 </div>
213 </div>
214 </%def>
214 </%def>
215
215
216
216
217 <%def name="user_name(user_id, username)">
217 <%def name="user_name(user_id, username)">
218 ${h.link_to(h.person(username, 'username_or_name_or_email'), h.url('edit_user', user_id=user_id))}
218 ${h.link_to(h.person(username, 'username_or_name_or_email'), h.url('edit_user', user_id=user_id))}
219 </%def>
219 </%def>
220
220
221 <%def name="user_profile(username)">
221 <%def name="user_profile(username)">
222 ${base.gravatar_with_user(username, 16)}
222 ${base.gravatar_with_user(username, 16)}
223 </%def>
223 </%def>
224
224
225 <%def name="user_group_name(user_group_id, user_group_name)">
225 <%def name="user_group_name(user_group_id, user_group_name)">
226 <div>
226 <div>
227 <a href="${h.url('edit_users_group', user_group_id=user_group_id)}">
227 <a href="${h.url('edit_users_group', user_group_id=user_group_id)}">
228 <i class="icon-group" title="${_('User group')}"></i> ${user_group_name}</a>
228 <i class="icon-group" title="${_('User group')}"></i> ${user_group_name}</a>
229 </div>
229 </div>
230 </%def>
230 </%def>
231
231
232
232
233 ## GISTS
233 ## GISTS
234
234
235 <%def name="gist_gravatar(full_contact)">
235 <%def name="gist_gravatar(full_contact)">
236 <div class="gist_gravatar">
236 <div class="gist_gravatar">
237 ${base.gravatar(full_contact, 30)}
237 ${base.gravatar(full_contact, 30)}
238 </div>
238 </div>
239 </%def>
239 </%def>
240
240
241 <%def name="gist_access_id(gist_access_id, full_contact)">
241 <%def name="gist_access_id(gist_access_id, full_contact)">
242 <div>
242 <div>
243 <b>
243 <b>
244 <a href="${h.url('gist',gist_id=gist_access_id)}">gist: ${gist_access_id}</a>
244 <a href="${h.url('gist',gist_id=gist_access_id)}">gist: ${gist_access_id}</a>
245 </b>
245 </b>
246 </div>
246 </div>
247 </%def>
247 </%def>
248
248
249 <%def name="gist_author(full_contact, created_on, expires)">
249 <%def name="gist_author(full_contact, created_on, expires)">
250 ${base.gravatar_with_user(full_contact, 16)}
250 ${base.gravatar_with_user(full_contact, 16)}
251 </%def>
251 </%def>
252
252
253
253
254 <%def name="gist_created(created_on)">
254 <%def name="gist_created(created_on)">
255 <div class="created">
255 <div class="created">
256 ${h.age_component(created_on, time_is_local=True)}
256 ${h.age_component(created_on, time_is_local=True)}
257 </div>
257 </div>
258 </%def>
258 </%def>
259
259
260 <%def name="gist_expires(expires)">
260 <%def name="gist_expires(expires)">
261 <div class="created">
261 <div class="created">
262 %if expires == -1:
262 %if expires == -1:
263 ${_('never')}
263 ${_('never')}
264 %else:
264 %else:
265 ${h.age_component(h.time_to_utcdatetime(expires))}
265 ${h.age_component(h.time_to_utcdatetime(expires))}
266 %endif
266 %endif
267 </div>
267 </div>
268 </%def>
268 </%def>
269
269
270 <%def name="gist_type(gist_type)">
270 <%def name="gist_type(gist_type)">
271 %if gist_type != 'public':
271 %if gist_type != 'public':
272 <div class="tag">${_('Private')}</div>
272 <div class="tag">${_('Private')}</div>
273 %endif
273 %endif
274 </%def>
274 </%def>
275
275
276 <%def name="gist_description(gist_description)">
276 <%def name="gist_description(gist_description)">
277 ${gist_description}
277 ${gist_description}
278 </%def>
278 </%def>
279
279
280
280
281 ## PULL REQUESTS GRID RENDERERS
281 ## PULL REQUESTS GRID RENDERERS
282
282
283 <%def name="pullrequest_target_repo(repo_name)">
283 <%def name="pullrequest_target_repo(repo_name)">
284 <div class="truncate">
284 <div class="truncate">
285 ${h.link_to(repo_name,h.url('summary_home',repo_name=repo_name))}
285 ${h.link_to(repo_name,h.url('summary_home',repo_name=repo_name))}
286 </div>
286 </div>
287 </%def>
287 </%def>
288 <%def name="pullrequest_status(status)">
288 <%def name="pullrequest_status(status)">
289 <div class="${'flag_status %s' % status} pull-left"></div>
289 <div class="${'flag_status %s' % status} pull-left"></div>
290 </%def>
290 </%def>
291
291
292 <%def name="pullrequest_title(title, description)">
292 <%def name="pullrequest_title(title, description)">
293 ${title} <br/>
293 ${title} <br/>
294 ${h.shorter(description, 40)}
294 ${h.shorter(description, 40)}
295 </%def>
295 </%def>
296
296
297 <%def name="pullrequest_comments(comments_nr)">
297 <%def name="pullrequest_comments(comments_nr)">
298 <i class="icon-comment"></i> ${comments_nr}
298 <i class="icon-comment"></i> ${comments_nr}
299 </%def>
299 </%def>
300
300
301 <%def name="pullrequest_name(pull_request_id, target_repo_name, short=False)">
301 <%def name="pullrequest_name(pull_request_id, target_repo_name, short=False)">
302 <a href="${h.url('pullrequest_show',repo_name=target_repo_name,pull_request_id=pull_request_id)}">
302 <a href="${h.url('pullrequest_show',repo_name=target_repo_name,pull_request_id=pull_request_id)}">
303 % if short:
303 % if short:
304 #${pull_request_id}
304 #${pull_request_id}
305 % else:
305 % else:
306 ${_('Pull request #%(pr_number)s') % {'pr_number': pull_request_id,}}
306 ${_('Pull request #%(pr_number)s') % {'pr_number': pull_request_id,}}
307 % endif
307 % endif
308 </a>
308 </a>
309 </%def>
309 </%def>
310
310
311 <%def name="pullrequest_updated_on(updated_on)">
311 <%def name="pullrequest_updated_on(updated_on)">
312 ${h.age_component(h.time_to_utcdatetime(updated_on))}
312 ${h.age_component(h.time_to_utcdatetime(updated_on))}
313 </%def>
313 </%def>
314
314
315 <%def name="pullrequest_author(full_contact)">
315 <%def name="pullrequest_author(full_contact)">
316 ${base.gravatar_with_user(full_contact, 16)}
316 ${base.gravatar_with_user(full_contact, 16)}
317 </%def>
317 </%def>
@@ -1,202 +1,202 b''
1 <%def name="refs_counters(branches, closed_branches, tags, bookmarks)">
1 <%def name="refs_counters(branches, closed_branches, tags, bookmarks)">
2 <span class="branchtag tag">
2 <span class="branchtag tag">
3 <a href="${h.url('branches_home',repo_name=c.repo_name)}" class="childs">
3 <a href="${h.url('branches_home',repo_name=c.repo_name)}" class="childs">
4 <i class="icon-branch"></i>${ungettext(
4 <i class="icon-branch"></i>${ungettext(
5 '%(num)s Branch','%(num)s Branches', len(branches)) % {'num': len(branches)}}</a>
5 '%(num)s Branch','%(num)s Branches', len(branches)) % {'num': len(branches)}}</a>
6 </span>
6 </span>
7
7
8 %if closed_branches:
8 %if closed_branches:
9 <span class="branchtag tag">
9 <span class="branchtag tag">
10 <a href="${h.url('branches_home',repo_name=c.repo_name)}" class="childs">
10 <a href="${h.url('branches_home',repo_name=c.repo_name)}" class="childs">
11 <i class="icon-branch"></i>${ungettext(
11 <i class="icon-branch"></i>${ungettext(
12 '%(num)s Closed Branch', '%(num)s Closed Branches', len(closed_branches)) % {'num': len(closed_branches)}}</a>
12 '%(num)s Closed Branch', '%(num)s Closed Branches', len(closed_branches)) % {'num': len(closed_branches)}}</a>
13 </span>
13 </span>
14 %endif
14 %endif
15
15
16 <span class="tagtag tag">
16 <span class="tagtag tag">
17 <a href="${h.url('tags_home',repo_name=c.repo_name)}" class="childs">
17 <a href="${h.url('tags_home',repo_name=c.repo_name)}" class="childs">
18 <i class="icon-tag"></i>${ungettext(
18 <i class="icon-tag"></i>${ungettext(
19 '%(num)s Tag', '%(num)s Tags', len(tags)) % {'num': len(tags)}}</a>
19 '%(num)s Tag', '%(num)s Tags', len(tags)) % {'num': len(tags)}}</a>
20 </span>
20 </span>
21
21
22 %if bookmarks:
22 %if bookmarks:
23 <span class="booktag tag">
23 <span class="booktag tag">
24 <a href="${h.url('bookmarks_home',repo_name=c.repo_name)}" class="childs">
24 <a href="${h.url('bookmarks_home',repo_name=c.repo_name)}" class="childs">
25 <i class="icon-bookmark"></i>${ungettext(
25 <i class="icon-bookmark"></i>${ungettext(
26 '%(num)s Bookmark', '%(num)s Bookmarks', len(bookmarks)) % {'num': len(bookmarks)}}</a>
26 '%(num)s Bookmark', '%(num)s Bookmarks', len(bookmarks)) % {'num': len(bookmarks)}}</a>
27 </span>
27 </span>
28 %endif
28 %endif
29 </%def>
29 </%def>
30
30
31 <%def name="summary_detail(breadcrumbs_links, show_downloads=True)">
31 <%def name="summary_detail(breadcrumbs_links, show_downloads=True)">
32 <% summary = lambda n:{False:'summary-short'}.get(n) %>
32 <% summary = lambda n:{False:'summary-short'}.get(n) %>
33
33
34 <div id="summary-menu-stats" class="summary-detail">
34 <div id="summary-menu-stats" class="summary-detail">
35 <div class="summary-detail-header">
35 <div class="summary-detail-header">
36 <div class="breadcrumbs files_location">
36 <div class="breadcrumbs files_location">
37 <h4>
37 <h4>
38 ${breadcrumbs_links}
38 ${breadcrumbs_links}
39 </h4>
39 </h4>
40 </div>
40 </div>
41 <div id="summary_details_expand" class="btn-collapse" data-toggle="summary-details">
41 <div id="summary_details_expand" class="btn-collapse" data-toggle="summary-details">
42 ${_('Show More')}
42 ${_('Show More')}
43 </div>
43 </div>
44 </div>
44 </div>
45
45
46 <div class="fieldset">
46 <div class="fieldset">
47 %if h.is_svn_without_proxy(c.rhodecode_db_repo):
47 %if h.is_svn_without_proxy(c.rhodecode_db_repo):
48 <div class="left-label disabled">
48 <div class="left-label disabled">
49 ${_('Read-only url')}:
49 ${_('Read-only url')}:
50 </div>
50 </div>
51 <div class="right-content disabled">
51 <div class="right-content disabled">
52 <input type="text" class="input-monospace" id="clone_url" disabled value="${c.clone_repo_url}"/>
52 <input type="text" class="input-monospace" id="clone_url" disabled value="${c.clone_repo_url}"/>
53 <input type="text" class="input-monospace" id="clone_url_id" disabled value="${c.clone_repo_url_id}" style="display: none;"/>
53 <input type="text" class="input-monospace" id="clone_url_id" disabled value="${c.clone_repo_url_id}" style="display: none;"/>
54 <a id="clone_by_name" class="clone" style="display: none;">${_('Show by Name')}</a>
54 <a id="clone_by_name" class="clone" style="display: none;">${_('Show by Name')}</a>
55 <a id="clone_by_id" class="clone">${_('Show by ID')}</a>
55 <a id="clone_by_id" class="clone">${_('Show by ID')}</a>
56 <p class="help-block">${_('SVN Protocol is disabled. To enable it, see the')} <a href="${h.route_url('enterprise_svn_setup')}" target="_blank">${_('documentation here')}</a>.</p>
56 <p class="help-block">${_('SVN Protocol is disabled. To enable it, see the')} <a href="${h.route_url('enterprise_svn_setup')}" target="_blank">${_('documentation here')}</a>.</p>
57 </div>
57 </div>
58 %else:
58 %else:
59 <div class="left-label">
59 <div class="left-label">
60 ${_('Clone url')}:
60 ${_('Clone url')}:
61 </div>
61 </div>
62 <div class="right-content">
62 <div class="right-content">
63 <input type="text" class="input-monospace" id="clone_url" readonly="readonly" value="${c.clone_repo_url}"/>
63 <input type="text" class="input-monospace" id="clone_url" readonly="readonly" value="${c.clone_repo_url}"/>
64 <input type="text" class="input-monospace" id="clone_url_id" readonly="readonly" value="${c.clone_repo_url_id}" style="display: none;"/>
64 <input type="text" class="input-monospace" id="clone_url_id" readonly="readonly" value="${c.clone_repo_url_id}" style="display: none;"/>
65 <a id="clone_by_name" class="clone" style="display: none;">${_('Show by Name')}</a>
65 <a id="clone_by_name" class="clone" style="display: none;">${_('Show by Name')}</a>
66 <a id="clone_by_id" class="clone">${_('Show by ID')}</a>
66 <a id="clone_by_id" class="clone">${_('Show by ID')}</a>
67 </div>
67 </div>
68 %endif
68 %endif
69 </div>
69 </div>
70
70
71 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
71 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
72 <div class="left-label">
72 <div class="left-label">
73 ${_('Description')}:
73 ${_('Description')}:
74 </div>
74 </div>
75 <div class="right-content">
75 <div class="right-content">
76 %if c.visual.stylify_metatags:
76 %if c.visual.stylify_metatags:
77 <div class="input ${summary(c.show_stats)} desc">${h.urlify_text(h.escaped_stylize(c.rhodecode_db_repo.description))}</div>
77 <div class="input ${summary(c.show_stats)} desc">${h.urlify_text(h.escaped_stylize(c.rhodecode_db_repo.description))}</div>
78 %else:
78 %else:
79 <div class="input ${summary(c.show_stats)} desc">${h.urlify_text(h.html_escape(c.rhodecode_db_repo.description))}</div>
79 <div class="input ${summary(c.show_stats)} desc">${h.urlify_text(h.html_escape(c.rhodecode_db_repo.description))}</div>
80 %endif
80 %endif
81 </div>
81 </div>
82 </div>
82 </div>
83
83
84 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
84 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
85 <div class="left-label">
85 <div class="left-label">
86 ${_('Information')}:
86 ${_('Information')}:
87 </div>
87 </div>
88 <div class="right-content">
88 <div class="right-content">
89
89
90 <div class="repo-size">
90 <div class="repo-size">
91 <% commit_rev = c.rhodecode_db_repo.changeset_cache.get('revision') %>
91 <% commit_rev = c.rhodecode_db_repo.changeset_cache.get('revision') %>
92
92
93 ## commits
93 ## commits
94 % if commit_rev == -1:
94 % if commit_rev == -1:
95 ${ungettext('%(num)s Commit', '%(num)s Commits', 0) % {'num': 0}},
95 ${ungettext('%(num)s Commit', '%(num)s Commits', 0) % {'num': 0}},
96 % else:
96 % else:
97 <a href="${h.url('changelog_home', repo_name=c.repo_name)}">
97 <a href="${h.url('changelog_home', repo_name=c.repo_name)}">
98 ${ungettext('%(num)s Commit', '%(num)s Commits', commit_rev) % {'num': commit_rev}}</a>,
98 ${ungettext('%(num)s Commit', '%(num)s Commits', commit_rev) % {'num': commit_rev}}</a>,
99 % endif
99 % endif
100
100
101 ## forks
101 ## forks
102 <a title="${_('Number of Repository Forks')}" href="${h.url('repo_forks_home', repo_name=c.repo_name)}">
102 <a title="${_('Number of Repository Forks')}" href="${h.url('repo_forks_home', repo_name=c.repo_name)}">
103 ${c.repository_forks} ${ungettext('Fork', 'Forks', c.repository_forks)}</a>,
103 ${c.repository_forks} ${ungettext('Fork', 'Forks', c.repository_forks)}</a>,
104
104
105 ## repo size
105 ## repo size
106 % if commit_rev == -1:
106 % if commit_rev == -1:
107 <span class="stats-bullet">0 B</span>
107 <span class="stats-bullet">0 B</span>
108 % else:
108 % else:
109 <span class="stats-bullet" id="repo_size_container">
109 <span class="stats-bullet" id="repo_size_container">
110 ${_('Calculating Repository Size...')}
110 ${_('Calculating Repository Size...')}
111 </span>
111 </span>
112 % endif
112 % endif
113 </div>
113 </div>
114
114
115 <div class="commit-info">
115 <div class="commit-info">
116 <div class="tags">
116 <div class="tags">
117 % if c.rhodecode_repo:
117 % if c.rhodecode_repo:
118 ${refs_counters(
118 ${refs_counters(
119 c.rhodecode_repo.branches,
119 c.rhodecode_repo.branches,
120 c.rhodecode_repo.branches_closed,
120 c.rhodecode_repo.branches_closed,
121 c.rhodecode_repo.tags,
121 c.rhodecode_repo.tags,
122 c.rhodecode_repo.bookmarks)}
122 c.rhodecode_repo.bookmarks)}
123 % else:
123 % else:
124 ## missing requirements can make c.rhodecode_repo None
124 ## missing requirements can make c.rhodecode_repo None
125 ${refs_counters([], [], [], [])}
125 ${refs_counters([], [], [], [])}
126 % endif
126 % endif
127 </div>
127 </div>
128 </div>
128 </div>
129
129
130 </div>
130 </div>
131 </div>
131 </div>
132
132
133 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
133 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
134 <div class="left-label">
134 <div class="left-label">
135 ${_('Statistics')}:
135 ${_('Statistics')}:
136 </div>
136 </div>
137 <div class="right-content">
137 <div class="right-content">
138 <div class="input ${summary(c.show_stats)} statistics">
138 <div class="input ${summary(c.show_stats)} statistics">
139 % if c.show_stats:
139 % if c.show_stats:
140 <div id="lang_stats" class="enabled">
140 <div id="lang_stats" class="enabled">
141 ${_('Calculating Code Statistics...')}
141 ${_('Calculating Code Statistics...')}
142 </div>
142 </div>
143 % else:
143 % else:
144 <span class="disabled">
144 <span class="disabled">
145 ${_('Statistics are disabled for this repository')}
145 ${_('Statistics are disabled for this repository')}
146 </span>
146 </span>
147 % if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
147 % if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
148 , ${h.link_to(_('enable statistics'),h.url('edit_repo',repo_name=c.repo_name, anchor='repo_enable_statistics'))}
148 , ${h.link_to(_('enable statistics'),h.route_path('edit_repo',repo_name=c.repo_name, anchor='repo_enable_statistics'))}
149 % endif
149 % endif
150 % endif
150 % endif
151 </div>
151 </div>
152
152
153 </div>
153 </div>
154 </div>
154 </div>
155
155
156 % if show_downloads:
156 % if show_downloads:
157 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
157 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
158 <div class="left-label">
158 <div class="left-label">
159 ${_('Downloads')}:
159 ${_('Downloads')}:
160 </div>
160 </div>
161 <div class="right-content">
161 <div class="right-content">
162 <div class="input ${summary(c.show_stats)} downloads">
162 <div class="input ${summary(c.show_stats)} downloads">
163 % if c.rhodecode_repo and len(c.rhodecode_repo.revisions) == 0:
163 % if c.rhodecode_repo and len(c.rhodecode_repo.revisions) == 0:
164 <span class="disabled">
164 <span class="disabled">
165 ${_('There are no downloads yet')}
165 ${_('There are no downloads yet')}
166 </span>
166 </span>
167 % elif not c.enable_downloads:
167 % elif not c.enable_downloads:
168 <span class="disabled">
168 <span class="disabled">
169 ${_('Downloads are disabled for this repository')}
169 ${_('Downloads are disabled for this repository')}
170 </span>
170 </span>
171 % if h.HasPermissionAll('hg.admin')('enable downloads on from summary'):
171 % if h.HasPermissionAll('hg.admin')('enable downloads on from summary'):
172 , ${h.link_to(_('enable downloads'),h.url('edit_repo',repo_name=c.repo_name, anchor='repo_enable_downloads'))}
172 , ${h.link_to(_('enable downloads'),h.route_path('edit_repo',repo_name=c.repo_name, anchor='repo_enable_downloads'))}
173 % endif
173 % endif
174 % else:
174 % else:
175 <span class="enabled">
175 <span class="enabled">
176 <a id="archive_link" class="btn btn-small" href="${h.url('files_archive_home',repo_name=c.rhodecode_db_repo.repo_name,fname='tip.zip')}">
176 <a id="archive_link" class="btn btn-small" href="${h.url('files_archive_home',repo_name=c.rhodecode_db_repo.repo_name,fname='tip.zip')}">
177 <i class="icon-archive"></i> tip.zip
177 <i class="icon-archive"></i> tip.zip
178 ## replaced by some JS on select
178 ## replaced by some JS on select
179 </a>
179 </a>
180 </span>
180 </span>
181 ${h.hidden('download_options')}
181 ${h.hidden('download_options')}
182 % endif
182 % endif
183 </div>
183 </div>
184 </div>
184 </div>
185 </div>
185 </div>
186 % endif
186 % endif
187
187
188 </div><!--end summary-detail-->
188 </div><!--end summary-detail-->
189 </%def>
189 </%def>
190
190
191 <%def name="summary_stats(gravatar_function)">
191 <%def name="summary_stats(gravatar_function)">
192 <div class="sidebar-right">
192 <div class="sidebar-right">
193 <div class="summary-detail-header">
193 <div class="summary-detail-header">
194 <h4 class="item">
194 <h4 class="item">
195 ${_('Owner')}
195 ${_('Owner')}
196 </h4>
196 </h4>
197 </div>
197 </div>
198 <div class="sidebar-right-content">
198 <div class="sidebar-right-content">
199 ${gravatar_function(c.rhodecode_db_repo.user.email, 16)}
199 ${gravatar_function(c.rhodecode_db_repo.user.email, 16)}
200 </div>
200 </div>
201 </div><!--end sidebar-right-->
201 </div><!--end sidebar-right-->
202 </%def>
202 </%def>
General Comments 0
You need to be logged in to leave comments. Login now