##// END OF EJS Templates
user-groups: new selector for user group memebers....
marcink -
r1089:4d236b89 default
parent child Browse files
Show More
@@ -1,191 +1,190 b''
1 {
1 {
2 "dirs": {
2 "dirs": {
3 "css": {
3 "css": {
4 "src":"rhodecode/public/css",
4 "src":"rhodecode/public/css",
5 "dest":"rhodecode/public/css"
5 "dest":"rhodecode/public/css"
6 },
6 },
7 "js": {
7 "js": {
8 "src": "rhodecode/public/js/src",
8 "src": "rhodecode/public/js/src",
9 "dest": "rhodecode/public/js",
9 "dest": "rhodecode/public/js",
10 "bower": "bower_components",
10 "bower": "bower_components",
11 "node_modules": "node_modules"
11 "node_modules": "node_modules"
12 }
12 }
13 },
13 },
14 "copy": {
14 "copy": {
15 "main": {
15 "main": {
16 "expand": true,
16 "expand": true,
17 "cwd": "bower_components",
17 "cwd": "bower_components",
18 "src": "webcomponentsjs/webcomponents-lite.js",
18 "src": "webcomponentsjs/webcomponents-lite.js",
19 "dest": "<%= dirs.js.dest %>/vendors"
19 "dest": "<%= dirs.js.dest %>/vendors"
20 }
20 }
21 },
21 },
22 "concat": {
22 "concat": {
23 "polymercss": {
23 "polymercss": {
24 "src": [
24 "src": [
25 "<%= dirs.js.src %>/components/root-styles-prefix.html",
25 "<%= dirs.js.src %>/components/root-styles-prefix.html",
26 "<%= dirs.css.src %>/style-polymer.css",
26 "<%= dirs.css.src %>/style-polymer.css",
27 "<%= dirs.js.src %>/components/root-styles-suffix.html"
27 "<%= dirs.js.src %>/components/root-styles-suffix.html"
28 ],
28 ],
29 "dest": "<%= dirs.js.dest %>/src/components/root-styles.gen.html",
29 "dest": "<%= dirs.js.dest %>/src/components/root-styles.gen.html",
30 "nonull": true
30 "nonull": true
31 },
31 },
32 "dist": {
32 "dist": {
33 "src": [
33 "src": [
34 "<%= dirs.js.src %>/jquery-1.11.1.min.js",
34 "<%= dirs.js.src %>/jquery-1.11.1.min.js",
35 "<%= dirs.js.src %>/logging.js",
35 "<%= dirs.js.src %>/logging.js",
36 "<%= dirs.js.src %>/bootstrap.js",
36 "<%= dirs.js.src %>/bootstrap.js",
37 "<%= dirs.js.src %>/mousetrap.js",
37 "<%= dirs.js.src %>/mousetrap.js",
38 "<%= dirs.js.src %>/moment.js",
38 "<%= dirs.js.src %>/moment.js",
39 "<%= dirs.js.node_modules %>/appenlight-client/appenlight-client.min.js",
39 "<%= dirs.js.node_modules %>/appenlight-client/appenlight-client.min.js",
40 "<%= dirs.js.node_modules %>/favico.js/favico-0.3.10.min.js",
40 "<%= dirs.js.node_modules %>/favico.js/favico-0.3.10.min.js",
41 "<%= dirs.js.src %>/i18n_utils.js",
41 "<%= dirs.js.src %>/i18n_utils.js",
42 "<%= dirs.js.src %>/deform.js",
42 "<%= dirs.js.src %>/deform.js",
43 "<%= dirs.js.src %>/plugins/jquery.pjax.js",
43 "<%= dirs.js.src %>/plugins/jquery.pjax.js",
44 "<%= dirs.js.src %>/plugins/jquery.dataTables.js",
44 "<%= dirs.js.src %>/plugins/jquery.dataTables.js",
45 "<%= dirs.js.src %>/plugins/flavoured_checkbox.js",
45 "<%= dirs.js.src %>/plugins/flavoured_checkbox.js",
46 "<%= dirs.js.src %>/plugins/jquery.auto-grow-input.js",
46 "<%= dirs.js.src %>/plugins/jquery.auto-grow-input.js",
47 "<%= dirs.js.src %>/plugins/jquery.autocomplete.js",
47 "<%= dirs.js.src %>/plugins/jquery.autocomplete.js",
48 "<%= dirs.js.src %>/plugins/jquery.debounce.js",
48 "<%= dirs.js.src %>/plugins/jquery.debounce.js",
49 "<%= dirs.js.src %>/plugins/jquery.mark.js",
49 "<%= dirs.js.src %>/plugins/jquery.mark.js",
50 "<%= dirs.js.src %>/plugins/jquery.timeago.js",
50 "<%= dirs.js.src %>/plugins/jquery.timeago.js",
51 "<%= dirs.js.src %>/plugins/jquery.timeago-extension.js",
51 "<%= dirs.js.src %>/plugins/jquery.timeago-extension.js",
52 "<%= dirs.js.src %>/select2/select2.js",
52 "<%= dirs.js.src %>/select2/select2.js",
53 "<%= dirs.js.src %>/codemirror/codemirror.js",
53 "<%= dirs.js.src %>/codemirror/codemirror.js",
54 "<%= dirs.js.src %>/codemirror/codemirror_loadmode.js",
54 "<%= dirs.js.src %>/codemirror/codemirror_loadmode.js",
55 "<%= dirs.js.src %>/codemirror/codemirror_hint.js",
55 "<%= dirs.js.src %>/codemirror/codemirror_hint.js",
56 "<%= dirs.js.src %>/codemirror/codemirror_overlay.js",
56 "<%= dirs.js.src %>/codemirror/codemirror_overlay.js",
57 "<%= dirs.js.src %>/codemirror/codemirror_placeholder.js",
57 "<%= dirs.js.src %>/codemirror/codemirror_placeholder.js",
58 "<%= dirs.js.dest %>/mode/meta.js",
58 "<%= dirs.js.dest %>/mode/meta.js",
59 "<%= dirs.js.dest %>/mode/meta_ext.js",
59 "<%= dirs.js.dest %>/mode/meta_ext.js",
60 "<%= dirs.js.dest %>/rhodecode/i18n/select2/translations.js",
60 "<%= dirs.js.dest %>/rhodecode/i18n/select2/translations.js",
61 "<%= dirs.js.src %>/rhodecode/utils/array.js",
61 "<%= dirs.js.src %>/rhodecode/utils/array.js",
62 "<%= dirs.js.src %>/rhodecode/utils/string.js",
62 "<%= dirs.js.src %>/rhodecode/utils/string.js",
63 "<%= dirs.js.src %>/rhodecode/utils/pyroutes.js",
63 "<%= dirs.js.src %>/rhodecode/utils/pyroutes.js",
64 "<%= dirs.js.src %>/rhodecode/utils/ajax.js",
64 "<%= dirs.js.src %>/rhodecode/utils/ajax.js",
65 "<%= dirs.js.src %>/rhodecode/utils/autocomplete.js",
65 "<%= dirs.js.src %>/rhodecode/utils/autocomplete.js",
66 "<%= dirs.js.src %>/rhodecode/utils/colorgenerator.js",
66 "<%= dirs.js.src %>/rhodecode/utils/colorgenerator.js",
67 "<%= dirs.js.src %>/rhodecode/utils/ie.js",
67 "<%= dirs.js.src %>/rhodecode/utils/ie.js",
68 "<%= dirs.js.src %>/rhodecode/utils/os.js",
68 "<%= dirs.js.src %>/rhodecode/utils/os.js",
69 "<%= dirs.js.src %>/rhodecode/utils/topics.js",
69 "<%= dirs.js.src %>/rhodecode/utils/topics.js",
70 "<%= dirs.js.src %>/rhodecode/widgets/multiselect.js",
71 "<%= dirs.js.src %>/rhodecode/init.js",
70 "<%= dirs.js.src %>/rhodecode/init.js",
72 "<%= dirs.js.src %>/rhodecode/codemirror.js",
71 "<%= dirs.js.src %>/rhodecode/codemirror.js",
73 "<%= dirs.js.src %>/rhodecode/comments.js",
72 "<%= dirs.js.src %>/rhodecode/comments.js",
74 "<%= dirs.js.src %>/rhodecode/constants.js",
73 "<%= dirs.js.src %>/rhodecode/constants.js",
75 "<%= dirs.js.src %>/rhodecode/files.js",
74 "<%= dirs.js.src %>/rhodecode/files.js",
76 "<%= dirs.js.src %>/rhodecode/followers.js",
75 "<%= dirs.js.src %>/rhodecode/followers.js",
77 "<%= dirs.js.src %>/rhodecode/menus.js",
76 "<%= dirs.js.src %>/rhodecode/menus.js",
78 "<%= dirs.js.src %>/rhodecode/notifications.js",
77 "<%= dirs.js.src %>/rhodecode/notifications.js",
79 "<%= dirs.js.src %>/rhodecode/permissions.js",
78 "<%= dirs.js.src %>/rhodecode/permissions.js",
80 "<%= dirs.js.src %>/rhodecode/pjax.js",
79 "<%= dirs.js.src %>/rhodecode/pjax.js",
81 "<%= dirs.js.src %>/rhodecode/pullrequests.js",
80 "<%= dirs.js.src %>/rhodecode/pullrequests.js",
82 "<%= dirs.js.src %>/rhodecode/settings.js",
81 "<%= dirs.js.src %>/rhodecode/settings.js",
83 "<%= dirs.js.src %>/rhodecode/select2_widgets.js",
82 "<%= dirs.js.src %>/rhodecode/select2_widgets.js",
84 "<%= dirs.js.src %>/rhodecode/tooltips.js",
83 "<%= dirs.js.src %>/rhodecode/tooltips.js",
85 "<%= dirs.js.src %>/rhodecode/users.js",
84 "<%= dirs.js.src %>/rhodecode/users.js",
86 "<%= dirs.js.src %>/rhodecode/appenlight.js",
85 "<%= dirs.js.src %>/rhodecode/appenlight.js",
87 "<%= dirs.js.src %>/rhodecode.js"
86 "<%= dirs.js.src %>/rhodecode.js"
88 ],
87 ],
89 "dest": "<%= dirs.js.dest %>/scripts.js",
88 "dest": "<%= dirs.js.dest %>/scripts.js",
90 "nonull": true
89 "nonull": true
91 }
90 }
92 },
91 },
93 "crisper": {
92 "crisper": {
94 "dist": {
93 "dist": {
95 "options": {
94 "options": {
96 "cleanup": false,
95 "cleanup": false,
97 "onlySplit": true
96 "onlySplit": true
98 },
97 },
99 "src": "<%= dirs.js.dest %>/rhodecode-components.html",
98 "src": "<%= dirs.js.dest %>/rhodecode-components.html",
100 "dest": "<%= dirs.js.dest %>/rhodecode-components.js"
99 "dest": "<%= dirs.js.dest %>/rhodecode-components.js"
101 }
100 }
102 },
101 },
103 "less": {
102 "less": {
104 "development": {
103 "development": {
105 "options": {
104 "options": {
106 "compress": false,
105 "compress": false,
107 "yuicompress": false,
106 "yuicompress": false,
108 "optimization": 0
107 "optimization": 0
109 },
108 },
110 "files": {
109 "files": {
111 "<%= dirs.css.dest %>/style.css": "<%= dirs.css.src %>/main.less",
110 "<%= dirs.css.dest %>/style.css": "<%= dirs.css.src %>/main.less",
112 "<%= dirs.css.dest %>/style-polymer.css": "<%= dirs.css.src %>/polymer.less"
111 "<%= dirs.css.dest %>/style-polymer.css": "<%= dirs.css.src %>/polymer.less"
113 }
112 }
114 },
113 },
115 "production": {
114 "production": {
116 "options": {
115 "options": {
117 "compress": true,
116 "compress": true,
118 "yuicompress": true,
117 "yuicompress": true,
119 "optimization": 2
118 "optimization": 2
120 },
119 },
121 "files": {
120 "files": {
122 "<%= dirs.css.dest %>/style.css": "<%= dirs.css.src %>/main.less",
121 "<%= dirs.css.dest %>/style.css": "<%= dirs.css.src %>/main.less",
123 "<%= dirs.css.dest %>/style-polymer.css": "<%= dirs.css.src %>/polymer.less"
122 "<%= dirs.css.dest %>/style-polymer.css": "<%= dirs.css.src %>/polymer.less"
124 }
123 }
125 },
124 },
126 "components": {
125 "components": {
127 "files": [
126 "files": [
128 {
127 {
129 "cwd": "<%= dirs.js.src %>/components/",
128 "cwd": "<%= dirs.js.src %>/components/",
130 "dest": "<%= dirs.js.src %>/components/",
129 "dest": "<%= dirs.js.src %>/components/",
131 "src": [
130 "src": [
132 "**/*.less"
131 "**/*.less"
133 ],
132 ],
134 "expand": true,
133 "expand": true,
135 "ext": ".css"
134 "ext": ".css"
136 }
135 }
137 ]
136 ]
138 }
137 }
139 },
138 },
140 "watch": {
139 "watch": {
141 "less": {
140 "less": {
142 "files": [
141 "files": [
143 "<%= dirs.css.src %>/**/*.less",
142 "<%= dirs.css.src %>/**/*.less",
144 "<%= dirs.js.src %>/components/**/*.less"
143 "<%= dirs.js.src %>/components/**/*.less"
145 ],
144 ],
146 "tasks": [
145 "tasks": [
147 "less:development",
146 "less:development",
148 "less:components",
147 "less:components",
149 "concat:polymercss",
148 "concat:polymercss",
150 "vulcanize",
149 "vulcanize",
151 "crisper",
150 "crisper",
152 "concat:dist"
151 "concat:dist"
153 ]
152 ]
154 },
153 },
155 "js": {
154 "js": {
156 "files": [
155 "files": [
157 "!<%= dirs.js.src %>/components/root-styles.gen.html",
156 "!<%= dirs.js.src %>/components/root-styles.gen.html",
158 "<%= dirs.js.src %>/**/*.js",
157 "<%= dirs.js.src %>/**/*.js",
159 "<%= dirs.js.src %>/components/**/*.html"
158 "<%= dirs.js.src %>/components/**/*.html"
160 ],
159 ],
161 "tasks": [
160 "tasks": [
162 "less:components",
161 "less:components",
163 "concat:polymercss",
162 "concat:polymercss",
164 "vulcanize",
163 "vulcanize",
165 "crisper",
164 "crisper",
166 "concat:dist"
165 "concat:dist"
167 ]
166 ]
168 }
167 }
169 },
168 },
170 "jshint": {
169 "jshint": {
171 "rhodecode": {
170 "rhodecode": {
172 "src": "<%= dirs.js.src %>/rhodecode/**/*.js",
171 "src": "<%= dirs.js.src %>/rhodecode/**/*.js",
173 "options": {
172 "options": {
174 "jshintrc": ".jshintrc"
173 "jshintrc": ".jshintrc"
175 }
174 }
176 }
175 }
177 },
176 },
178 "vulcanize": {
177 "vulcanize": {
179 "default": {
178 "default": {
180 "options": {
179 "options": {
181 "abspath": "",
180 "abspath": "",
182 "inlineScripts": true,
181 "inlineScripts": true,
183 "inlineCss": true,
182 "inlineCss": true,
184 "stripComments": true
183 "stripComments": true
185 },
184 },
186 "files": {
185 "files": {
187 "<%= dirs.js.dest %>/rhodecode-components.html": "<%= dirs.js.src %>/components/shared-components.html"
186 "<%= dirs.js.dest %>/rhodecode-components.html": "<%= dirs.js.src %>/components/shared-components.html"
188 }
187 }
189 }
188 }
190 }
189 }
191 }
190 }
@@ -1,1167 +1,1167 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Routes configuration
22 Routes configuration
23
23
24 The more specific and detailed routes should be defined first so they
24 The more specific and detailed routes should be defined first so they
25 may take precedent over the more generic routes. For more information
25 may take precedent over the more generic routes. For more information
26 refer to the routes manual at http://routes.groovie.org/docs/
26 refer to the routes manual at http://routes.groovie.org/docs/
27
27
28 IMPORTANT: if you change any routing here, make sure to take a look at lib/base.py
28 IMPORTANT: if you change any routing here, make sure to take a look at lib/base.py
29 and _route_name variable which uses some of stored naming here to do redirects.
29 and _route_name variable which uses some of stored naming here to do redirects.
30 """
30 """
31 import os
31 import os
32 import re
32 import re
33 from routes import Mapper
33 from routes import Mapper
34
34
35 from rhodecode.config import routing_links
35 from rhodecode.config import routing_links
36
36
37 # prefix for non repository related links needs to be prefixed with `/`
37 # prefix for non repository related links needs to be prefixed with `/`
38 ADMIN_PREFIX = '/_admin'
38 ADMIN_PREFIX = '/_admin'
39 STATIC_FILE_PREFIX = '/_static'
39 STATIC_FILE_PREFIX = '/_static'
40
40
41 # Default requirements for URL parts
41 # Default requirements for URL parts
42 URL_NAME_REQUIREMENTS = {
42 URL_NAME_REQUIREMENTS = {
43 # group name can have a slash in them, but they must not end with a slash
43 # group name can have a slash in them, but they must not end with a slash
44 'group_name': r'.*?[^/]',
44 'group_name': r'.*?[^/]',
45 'repo_group_name': r'.*?[^/]',
45 'repo_group_name': r'.*?[^/]',
46 # repo names can have a slash in them, but they must not end with a slash
46 # repo names can have a slash in them, but they must not end with a slash
47 'repo_name': r'.*?[^/]',
47 'repo_name': r'.*?[^/]',
48 # file path eats up everything at the end
48 # file path eats up everything at the end
49 'f_path': r'.*',
49 'f_path': r'.*',
50 # reference types
50 # reference types
51 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
51 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
52 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
52 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
53 }
53 }
54
54
55
55
56 def add_route_requirements(route_path, requirements):
56 def add_route_requirements(route_path, requirements):
57 """
57 """
58 Adds regex requirements to pyramid routes using a mapping dict
58 Adds regex requirements to pyramid routes using a mapping dict
59
59
60 >>> add_route_requirements('/{action}/{id}', {'id': r'\d+'})
60 >>> add_route_requirements('/{action}/{id}', {'id': r'\d+'})
61 '/{action}/{id:\d+}'
61 '/{action}/{id:\d+}'
62
62
63 """
63 """
64 for key, regex in requirements.items():
64 for key, regex in requirements.items():
65 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
65 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
66 return route_path
66 return route_path
67
67
68
68
69 class JSRoutesMapper(Mapper):
69 class JSRoutesMapper(Mapper):
70 """
70 """
71 Wrapper for routes.Mapper to make pyroutes compatible url definitions
71 Wrapper for routes.Mapper to make pyroutes compatible url definitions
72 """
72 """
73 _named_route_regex = re.compile(r'^[a-z-_0-9A-Z]+$')
73 _named_route_regex = re.compile(r'^[a-z-_0-9A-Z]+$')
74 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
74 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
75 def __init__(self, *args, **kw):
75 def __init__(self, *args, **kw):
76 super(JSRoutesMapper, self).__init__(*args, **kw)
76 super(JSRoutesMapper, self).__init__(*args, **kw)
77 self._jsroutes = []
77 self._jsroutes = []
78
78
79 def connect(self, *args, **kw):
79 def connect(self, *args, **kw):
80 """
80 """
81 Wrapper for connect to take an extra argument jsroute=True
81 Wrapper for connect to take an extra argument jsroute=True
82
82
83 :param jsroute: boolean, if True will add the route to the pyroutes list
83 :param jsroute: boolean, if True will add the route to the pyroutes list
84 """
84 """
85 if kw.pop('jsroute', False):
85 if kw.pop('jsroute', False):
86 if not self._named_route_regex.match(args[0]):
86 if not self._named_route_regex.match(args[0]):
87 raise Exception('only named routes can be added to pyroutes')
87 raise Exception('only named routes can be added to pyroutes')
88 self._jsroutes.append(args[0])
88 self._jsroutes.append(args[0])
89
89
90 super(JSRoutesMapper, self).connect(*args, **kw)
90 super(JSRoutesMapper, self).connect(*args, **kw)
91
91
92 def _extract_route_information(self, route):
92 def _extract_route_information(self, route):
93 """
93 """
94 Convert a route into tuple(name, path, args), eg:
94 Convert a route into tuple(name, path, args), eg:
95 ('user_profile', '/profile/%(username)s', ['username'])
95 ('user_profile', '/profile/%(username)s', ['username'])
96 """
96 """
97 routepath = route.routepath
97 routepath = route.routepath
98 def replace(matchobj):
98 def replace(matchobj):
99 if matchobj.group(1):
99 if matchobj.group(1):
100 return "%%(%s)s" % matchobj.group(1).split(':')[0]
100 return "%%(%s)s" % matchobj.group(1).split(':')[0]
101 else:
101 else:
102 return "%%(%s)s" % matchobj.group(2)
102 return "%%(%s)s" % matchobj.group(2)
103
103
104 routepath = self._argument_prog.sub(replace, routepath)
104 routepath = self._argument_prog.sub(replace, routepath)
105 return (
105 return (
106 route.name,
106 route.name,
107 routepath,
107 routepath,
108 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
108 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
109 for arg in self._argument_prog.findall(route.routepath)]
109 for arg in self._argument_prog.findall(route.routepath)]
110 )
110 )
111
111
112 def jsroutes(self):
112 def jsroutes(self):
113 """
113 """
114 Return a list of pyroutes.js compatible routes
114 Return a list of pyroutes.js compatible routes
115 """
115 """
116 for route_name in self._jsroutes:
116 for route_name in self._jsroutes:
117 yield self._extract_route_information(self._routenames[route_name])
117 yield self._extract_route_information(self._routenames[route_name])
118
118
119
119
120 def make_map(config):
120 def make_map(config):
121 """Create, configure and return the routes Mapper"""
121 """Create, configure and return the routes Mapper"""
122 rmap = JSRoutesMapper(directory=config['pylons.paths']['controllers'],
122 rmap = JSRoutesMapper(directory=config['pylons.paths']['controllers'],
123 always_scan=config['debug'])
123 always_scan=config['debug'])
124 rmap.minimization = False
124 rmap.minimization = False
125 rmap.explicit = False
125 rmap.explicit = False
126
126
127 from rhodecode.lib.utils2 import str2bool
127 from rhodecode.lib.utils2 import str2bool
128 from rhodecode.model import repo, repo_group
128 from rhodecode.model import repo, repo_group
129
129
130 def check_repo(environ, match_dict):
130 def check_repo(environ, match_dict):
131 """
131 """
132 check for valid repository for proper 404 handling
132 check for valid repository for proper 404 handling
133
133
134 :param environ:
134 :param environ:
135 :param match_dict:
135 :param match_dict:
136 """
136 """
137 repo_name = match_dict.get('repo_name')
137 repo_name = match_dict.get('repo_name')
138
138
139 if match_dict.get('f_path'):
139 if match_dict.get('f_path'):
140 # fix for multiple initial slashes that causes errors
140 # fix for multiple initial slashes that causes errors
141 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
141 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
142 repo_model = repo.RepoModel()
142 repo_model = repo.RepoModel()
143 by_name_match = repo_model.get_by_repo_name(repo_name)
143 by_name_match = repo_model.get_by_repo_name(repo_name)
144 # if we match quickly from database, short circuit the operation,
144 # if we match quickly from database, short circuit the operation,
145 # and validate repo based on the type.
145 # and validate repo based on the type.
146 if by_name_match:
146 if by_name_match:
147 return True
147 return True
148
148
149 by_id_match = repo_model.get_repo_by_id(repo_name)
149 by_id_match = repo_model.get_repo_by_id(repo_name)
150 if by_id_match:
150 if by_id_match:
151 repo_name = by_id_match.repo_name
151 repo_name = by_id_match.repo_name
152 match_dict['repo_name'] = repo_name
152 match_dict['repo_name'] = repo_name
153 return True
153 return True
154
154
155 return False
155 return False
156
156
157 def check_group(environ, match_dict):
157 def check_group(environ, match_dict):
158 """
158 """
159 check for valid repository group path for proper 404 handling
159 check for valid repository group path for proper 404 handling
160
160
161 :param environ:
161 :param environ:
162 :param match_dict:
162 :param match_dict:
163 """
163 """
164 repo_group_name = match_dict.get('group_name')
164 repo_group_name = match_dict.get('group_name')
165 repo_group_model = repo_group.RepoGroupModel()
165 repo_group_model = repo_group.RepoGroupModel()
166 by_name_match = repo_group_model.get_by_group_name(repo_group_name)
166 by_name_match = repo_group_model.get_by_group_name(repo_group_name)
167 if by_name_match:
167 if by_name_match:
168 return True
168 return True
169
169
170 return False
170 return False
171
171
172 def check_user_group(environ, match_dict):
172 def check_user_group(environ, match_dict):
173 """
173 """
174 check for valid user group for proper 404 handling
174 check for valid user group for proper 404 handling
175
175
176 :param environ:
176 :param environ:
177 :param match_dict:
177 :param match_dict:
178 """
178 """
179 return True
179 return True
180
180
181 def check_int(environ, match_dict):
181 def check_int(environ, match_dict):
182 return match_dict.get('id').isdigit()
182 return match_dict.get('id').isdigit()
183
183
184
184
185 #==========================================================================
185 #==========================================================================
186 # CUSTOM ROUTES HERE
186 # CUSTOM ROUTES HERE
187 #==========================================================================
187 #==========================================================================
188
188
189 # MAIN PAGE
189 # MAIN PAGE
190 rmap.connect('home', '/', controller='home', action='index', jsroute=True)
190 rmap.connect('home', '/', controller='home', action='index', jsroute=True)
191 rmap.connect('goto_switcher_data', '/_goto_data', controller='home',
191 rmap.connect('goto_switcher_data', '/_goto_data', controller='home',
192 action='goto_switcher_data')
192 action='goto_switcher_data')
193 rmap.connect('repo_list_data', '/_repos', controller='home',
193 rmap.connect('repo_list_data', '/_repos', controller='home',
194 action='repo_list_data')
194 action='repo_list_data')
195
195
196 rmap.connect('user_autocomplete_data', '/_users', controller='home',
196 rmap.connect('user_autocomplete_data', '/_users', controller='home',
197 action='user_autocomplete_data', jsroute=True)
197 action='user_autocomplete_data', jsroute=True)
198 rmap.connect('user_group_autocomplete_data', '/_user_groups', controller='home',
198 rmap.connect('user_group_autocomplete_data', '/_user_groups', controller='home',
199 action='user_group_autocomplete_data', jsroute=True)
199 action='user_group_autocomplete_data', jsroute=True)
200
200
201 rmap.connect(
201 rmap.connect(
202 'user_profile', '/_profiles/{username}', controller='users',
202 'user_profile', '/_profiles/{username}', controller='users',
203 action='user_profile')
203 action='user_profile')
204
204
205 # TODO: johbo: Static links, to be replaced by our redirection mechanism
205 # TODO: johbo: Static links, to be replaced by our redirection mechanism
206 rmap.connect('rst_help',
206 rmap.connect('rst_help',
207 'http://docutils.sourceforge.net/docs/user/rst/quickref.html',
207 'http://docutils.sourceforge.net/docs/user/rst/quickref.html',
208 _static=True)
208 _static=True)
209 rmap.connect('markdown_help',
209 rmap.connect('markdown_help',
210 'http://daringfireball.net/projects/markdown/syntax',
210 'http://daringfireball.net/projects/markdown/syntax',
211 _static=True)
211 _static=True)
212 rmap.connect('rhodecode_official', 'https://rhodecode.com', _static=True)
212 rmap.connect('rhodecode_official', 'https://rhodecode.com', _static=True)
213 rmap.connect('rhodecode_support', 'https://rhodecode.com/help/', _static=True)
213 rmap.connect('rhodecode_support', 'https://rhodecode.com/help/', _static=True)
214 rmap.connect('rhodecode_translations', 'https://rhodecode.com/translate/enterprise', _static=True)
214 rmap.connect('rhodecode_translations', 'https://rhodecode.com/translate/enterprise', _static=True)
215 # TODO: anderson - making this a static link since redirect won't play
215 # TODO: anderson - making this a static link since redirect won't play
216 # nice with POST requests
216 # nice with POST requests
217 rmap.connect('enterprise_license_convert_from_old',
217 rmap.connect('enterprise_license_convert_from_old',
218 'https://rhodecode.com/u/license-upgrade',
218 'https://rhodecode.com/u/license-upgrade',
219 _static=True)
219 _static=True)
220
220
221 routing_links.connect_redirection_links(rmap)
221 routing_links.connect_redirection_links(rmap)
222
222
223 rmap.connect('ping', '%s/ping' % (ADMIN_PREFIX,), controller='home', action='ping')
223 rmap.connect('ping', '%s/ping' % (ADMIN_PREFIX,), controller='home', action='ping')
224 rmap.connect('error_test', '%s/error_test' % (ADMIN_PREFIX,), controller='home', action='error_test')
224 rmap.connect('error_test', '%s/error_test' % (ADMIN_PREFIX,), controller='home', action='error_test')
225
225
226 # ADMIN REPOSITORY ROUTES
226 # ADMIN REPOSITORY ROUTES
227 with rmap.submapper(path_prefix=ADMIN_PREFIX,
227 with rmap.submapper(path_prefix=ADMIN_PREFIX,
228 controller='admin/repos') as m:
228 controller='admin/repos') as m:
229 m.connect('repos', '/repos',
229 m.connect('repos', '/repos',
230 action='create', conditions={'method': ['POST']})
230 action='create', conditions={'method': ['POST']})
231 m.connect('repos', '/repos',
231 m.connect('repos', '/repos',
232 action='index', conditions={'method': ['GET']})
232 action='index', conditions={'method': ['GET']})
233 m.connect('new_repo', '/create_repository', jsroute=True,
233 m.connect('new_repo', '/create_repository', jsroute=True,
234 action='create_repository', conditions={'method': ['GET']})
234 action='create_repository', conditions={'method': ['GET']})
235 m.connect('/repos/{repo_name}',
235 m.connect('/repos/{repo_name}',
236 action='update', conditions={'method': ['PUT'],
236 action='update', conditions={'method': ['PUT'],
237 'function': check_repo},
237 'function': check_repo},
238 requirements=URL_NAME_REQUIREMENTS)
238 requirements=URL_NAME_REQUIREMENTS)
239 m.connect('delete_repo', '/repos/{repo_name}',
239 m.connect('delete_repo', '/repos/{repo_name}',
240 action='delete', conditions={'method': ['DELETE']},
240 action='delete', conditions={'method': ['DELETE']},
241 requirements=URL_NAME_REQUIREMENTS)
241 requirements=URL_NAME_REQUIREMENTS)
242 m.connect('repo', '/repos/{repo_name}',
242 m.connect('repo', '/repos/{repo_name}',
243 action='show', conditions={'method': ['GET'],
243 action='show', conditions={'method': ['GET'],
244 'function': check_repo},
244 'function': check_repo},
245 requirements=URL_NAME_REQUIREMENTS)
245 requirements=URL_NAME_REQUIREMENTS)
246
246
247 # ADMIN REPOSITORY GROUPS ROUTES
247 # ADMIN REPOSITORY GROUPS ROUTES
248 with rmap.submapper(path_prefix=ADMIN_PREFIX,
248 with rmap.submapper(path_prefix=ADMIN_PREFIX,
249 controller='admin/repo_groups') as m:
249 controller='admin/repo_groups') as m:
250 m.connect('repo_groups', '/repo_groups',
250 m.connect('repo_groups', '/repo_groups',
251 action='create', conditions={'method': ['POST']})
251 action='create', conditions={'method': ['POST']})
252 m.connect('repo_groups', '/repo_groups',
252 m.connect('repo_groups', '/repo_groups',
253 action='index', conditions={'method': ['GET']})
253 action='index', conditions={'method': ['GET']})
254 m.connect('new_repo_group', '/repo_groups/new',
254 m.connect('new_repo_group', '/repo_groups/new',
255 action='new', conditions={'method': ['GET']})
255 action='new', conditions={'method': ['GET']})
256 m.connect('update_repo_group', '/repo_groups/{group_name}',
256 m.connect('update_repo_group', '/repo_groups/{group_name}',
257 action='update', conditions={'method': ['PUT'],
257 action='update', conditions={'method': ['PUT'],
258 'function': check_group},
258 'function': check_group},
259 requirements=URL_NAME_REQUIREMENTS)
259 requirements=URL_NAME_REQUIREMENTS)
260
260
261 # EXTRAS REPO GROUP ROUTES
261 # EXTRAS REPO GROUP ROUTES
262 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
262 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
263 action='edit',
263 action='edit',
264 conditions={'method': ['GET'], 'function': check_group},
264 conditions={'method': ['GET'], 'function': check_group},
265 requirements=URL_NAME_REQUIREMENTS)
265 requirements=URL_NAME_REQUIREMENTS)
266 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
266 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
267 action='edit',
267 action='edit',
268 conditions={'method': ['PUT'], 'function': check_group},
268 conditions={'method': ['PUT'], 'function': check_group},
269 requirements=URL_NAME_REQUIREMENTS)
269 requirements=URL_NAME_REQUIREMENTS)
270
270
271 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
271 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
272 action='edit_repo_group_advanced',
272 action='edit_repo_group_advanced',
273 conditions={'method': ['GET'], 'function': check_group},
273 conditions={'method': ['GET'], 'function': check_group},
274 requirements=URL_NAME_REQUIREMENTS)
274 requirements=URL_NAME_REQUIREMENTS)
275 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
275 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
276 action='edit_repo_group_advanced',
276 action='edit_repo_group_advanced',
277 conditions={'method': ['PUT'], 'function': check_group},
277 conditions={'method': ['PUT'], 'function': check_group},
278 requirements=URL_NAME_REQUIREMENTS)
278 requirements=URL_NAME_REQUIREMENTS)
279
279
280 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
280 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
281 action='edit_repo_group_perms',
281 action='edit_repo_group_perms',
282 conditions={'method': ['GET'], 'function': check_group},
282 conditions={'method': ['GET'], 'function': check_group},
283 requirements=URL_NAME_REQUIREMENTS)
283 requirements=URL_NAME_REQUIREMENTS)
284 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
284 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
285 action='update_perms',
285 action='update_perms',
286 conditions={'method': ['PUT'], 'function': check_group},
286 conditions={'method': ['PUT'], 'function': check_group},
287 requirements=URL_NAME_REQUIREMENTS)
287 requirements=URL_NAME_REQUIREMENTS)
288
288
289 m.connect('delete_repo_group', '/repo_groups/{group_name}',
289 m.connect('delete_repo_group', '/repo_groups/{group_name}',
290 action='delete', conditions={'method': ['DELETE'],
290 action='delete', conditions={'method': ['DELETE'],
291 'function': check_group},
291 'function': check_group},
292 requirements=URL_NAME_REQUIREMENTS)
292 requirements=URL_NAME_REQUIREMENTS)
293
293
294 # ADMIN USER ROUTES
294 # ADMIN USER ROUTES
295 with rmap.submapper(path_prefix=ADMIN_PREFIX,
295 with rmap.submapper(path_prefix=ADMIN_PREFIX,
296 controller='admin/users') as m:
296 controller='admin/users') as m:
297 m.connect('users', '/users',
297 m.connect('users', '/users',
298 action='create', conditions={'method': ['POST']})
298 action='create', conditions={'method': ['POST']})
299 m.connect('users', '/users',
299 m.connect('users', '/users',
300 action='index', conditions={'method': ['GET']})
300 action='index', conditions={'method': ['GET']})
301 m.connect('new_user', '/users/new',
301 m.connect('new_user', '/users/new',
302 action='new', conditions={'method': ['GET']})
302 action='new', conditions={'method': ['GET']})
303 m.connect('update_user', '/users/{user_id}',
303 m.connect('update_user', '/users/{user_id}',
304 action='update', conditions={'method': ['PUT']})
304 action='update', conditions={'method': ['PUT']})
305 m.connect('delete_user', '/users/{user_id}',
305 m.connect('delete_user', '/users/{user_id}',
306 action='delete', conditions={'method': ['DELETE']})
306 action='delete', conditions={'method': ['DELETE']})
307 m.connect('edit_user', '/users/{user_id}/edit',
307 m.connect('edit_user', '/users/{user_id}/edit',
308 action='edit', conditions={'method': ['GET']})
308 action='edit', conditions={'method': ['GET']}, jsroute=True)
309 m.connect('user', '/users/{user_id}',
309 m.connect('user', '/users/{user_id}',
310 action='show', conditions={'method': ['GET']})
310 action='show', conditions={'method': ['GET']})
311 m.connect('force_password_reset_user', '/users/{user_id}/password_reset',
311 m.connect('force_password_reset_user', '/users/{user_id}/password_reset',
312 action='reset_password', conditions={'method': ['POST']})
312 action='reset_password', conditions={'method': ['POST']})
313 m.connect('create_personal_repo_group', '/users/{user_id}/create_repo_group',
313 m.connect('create_personal_repo_group', '/users/{user_id}/create_repo_group',
314 action='create_personal_repo_group', conditions={'method': ['POST']})
314 action='create_personal_repo_group', conditions={'method': ['POST']})
315
315
316 # EXTRAS USER ROUTES
316 # EXTRAS USER ROUTES
317 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
317 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
318 action='edit_advanced', conditions={'method': ['GET']})
318 action='edit_advanced', conditions={'method': ['GET']})
319 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
319 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
320 action='update_advanced', conditions={'method': ['PUT']})
320 action='update_advanced', conditions={'method': ['PUT']})
321
321
322 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
322 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
323 action='edit_auth_tokens', conditions={'method': ['GET']})
323 action='edit_auth_tokens', conditions={'method': ['GET']})
324 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
324 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
325 action='add_auth_token', conditions={'method': ['PUT']})
325 action='add_auth_token', conditions={'method': ['PUT']})
326 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
326 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
327 action='delete_auth_token', conditions={'method': ['DELETE']})
327 action='delete_auth_token', conditions={'method': ['DELETE']})
328
328
329 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
329 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
330 action='edit_global_perms', conditions={'method': ['GET']})
330 action='edit_global_perms', conditions={'method': ['GET']})
331 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
331 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
332 action='update_global_perms', conditions={'method': ['PUT']})
332 action='update_global_perms', conditions={'method': ['PUT']})
333
333
334 m.connect('edit_user_perms_summary', '/users/{user_id}/edit/permissions_summary',
334 m.connect('edit_user_perms_summary', '/users/{user_id}/edit/permissions_summary',
335 action='edit_perms_summary', conditions={'method': ['GET']})
335 action='edit_perms_summary', conditions={'method': ['GET']})
336
336
337 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
337 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
338 action='edit_emails', conditions={'method': ['GET']})
338 action='edit_emails', conditions={'method': ['GET']})
339 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
339 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
340 action='add_email', conditions={'method': ['PUT']})
340 action='add_email', conditions={'method': ['PUT']})
341 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
341 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
342 action='delete_email', conditions={'method': ['DELETE']})
342 action='delete_email', conditions={'method': ['DELETE']})
343
343
344 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
344 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
345 action='edit_ips', conditions={'method': ['GET']})
345 action='edit_ips', conditions={'method': ['GET']})
346 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
346 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
347 action='add_ip', conditions={'method': ['PUT']})
347 action='add_ip', conditions={'method': ['PUT']})
348 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
348 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
349 action='delete_ip', conditions={'method': ['DELETE']})
349 action='delete_ip', conditions={'method': ['DELETE']})
350
350
351 # ADMIN USER GROUPS REST ROUTES
351 # ADMIN USER GROUPS REST ROUTES
352 with rmap.submapper(path_prefix=ADMIN_PREFIX,
352 with rmap.submapper(path_prefix=ADMIN_PREFIX,
353 controller='admin/user_groups') as m:
353 controller='admin/user_groups') as m:
354 m.connect('users_groups', '/user_groups',
354 m.connect('users_groups', '/user_groups',
355 action='create', conditions={'method': ['POST']})
355 action='create', conditions={'method': ['POST']})
356 m.connect('users_groups', '/user_groups',
356 m.connect('users_groups', '/user_groups',
357 action='index', conditions={'method': ['GET']})
357 action='index', conditions={'method': ['GET']})
358 m.connect('new_users_group', '/user_groups/new',
358 m.connect('new_users_group', '/user_groups/new',
359 action='new', conditions={'method': ['GET']})
359 action='new', conditions={'method': ['GET']})
360 m.connect('update_users_group', '/user_groups/{user_group_id}',
360 m.connect('update_users_group', '/user_groups/{user_group_id}',
361 action='update', conditions={'method': ['PUT']})
361 action='update', conditions={'method': ['PUT']})
362 m.connect('delete_users_group', '/user_groups/{user_group_id}',
362 m.connect('delete_users_group', '/user_groups/{user_group_id}',
363 action='delete', conditions={'method': ['DELETE']})
363 action='delete', conditions={'method': ['DELETE']})
364 m.connect('edit_users_group', '/user_groups/{user_group_id}/edit',
364 m.connect('edit_users_group', '/user_groups/{user_group_id}/edit',
365 action='edit', conditions={'method': ['GET']},
365 action='edit', conditions={'method': ['GET']},
366 function=check_user_group)
366 function=check_user_group)
367
367
368 # EXTRAS USER GROUP ROUTES
368 # EXTRAS USER GROUP ROUTES
369 m.connect('edit_user_group_global_perms',
369 m.connect('edit_user_group_global_perms',
370 '/user_groups/{user_group_id}/edit/global_permissions',
370 '/user_groups/{user_group_id}/edit/global_permissions',
371 action='edit_global_perms', conditions={'method': ['GET']})
371 action='edit_global_perms', conditions={'method': ['GET']})
372 m.connect('edit_user_group_global_perms',
372 m.connect('edit_user_group_global_perms',
373 '/user_groups/{user_group_id}/edit/global_permissions',
373 '/user_groups/{user_group_id}/edit/global_permissions',
374 action='update_global_perms', conditions={'method': ['PUT']})
374 action='update_global_perms', conditions={'method': ['PUT']})
375 m.connect('edit_user_group_perms_summary',
375 m.connect('edit_user_group_perms_summary',
376 '/user_groups/{user_group_id}/edit/permissions_summary',
376 '/user_groups/{user_group_id}/edit/permissions_summary',
377 action='edit_perms_summary', conditions={'method': ['GET']})
377 action='edit_perms_summary', conditions={'method': ['GET']})
378
378
379 m.connect('edit_user_group_perms',
379 m.connect('edit_user_group_perms',
380 '/user_groups/{user_group_id}/edit/permissions',
380 '/user_groups/{user_group_id}/edit/permissions',
381 action='edit_perms', conditions={'method': ['GET']})
381 action='edit_perms', conditions={'method': ['GET']})
382 m.connect('edit_user_group_perms',
382 m.connect('edit_user_group_perms',
383 '/user_groups/{user_group_id}/edit/permissions',
383 '/user_groups/{user_group_id}/edit/permissions',
384 action='update_perms', conditions={'method': ['PUT']})
384 action='update_perms', conditions={'method': ['PUT']})
385
385
386 m.connect('edit_user_group_advanced',
386 m.connect('edit_user_group_advanced',
387 '/user_groups/{user_group_id}/edit/advanced',
387 '/user_groups/{user_group_id}/edit/advanced',
388 action='edit_advanced', conditions={'method': ['GET']})
388 action='edit_advanced', conditions={'method': ['GET']})
389
389
390 m.connect('edit_user_group_members',
390 m.connect('edit_user_group_members',
391 '/user_groups/{user_group_id}/edit/members', jsroute=True,
391 '/user_groups/{user_group_id}/edit/members', jsroute=True,
392 action='edit_members', conditions={'method': ['GET']})
392 action='user_group_members', conditions={'method': ['GET']})
393
393
394 # ADMIN PERMISSIONS ROUTES
394 # ADMIN PERMISSIONS ROUTES
395 with rmap.submapper(path_prefix=ADMIN_PREFIX,
395 with rmap.submapper(path_prefix=ADMIN_PREFIX,
396 controller='admin/permissions') as m:
396 controller='admin/permissions') as m:
397 m.connect('admin_permissions_application', '/permissions/application',
397 m.connect('admin_permissions_application', '/permissions/application',
398 action='permission_application_update', conditions={'method': ['POST']})
398 action='permission_application_update', conditions={'method': ['POST']})
399 m.connect('admin_permissions_application', '/permissions/application',
399 m.connect('admin_permissions_application', '/permissions/application',
400 action='permission_application', conditions={'method': ['GET']})
400 action='permission_application', conditions={'method': ['GET']})
401
401
402 m.connect('admin_permissions_global', '/permissions/global',
402 m.connect('admin_permissions_global', '/permissions/global',
403 action='permission_global_update', conditions={'method': ['POST']})
403 action='permission_global_update', conditions={'method': ['POST']})
404 m.connect('admin_permissions_global', '/permissions/global',
404 m.connect('admin_permissions_global', '/permissions/global',
405 action='permission_global', conditions={'method': ['GET']})
405 action='permission_global', conditions={'method': ['GET']})
406
406
407 m.connect('admin_permissions_object', '/permissions/object',
407 m.connect('admin_permissions_object', '/permissions/object',
408 action='permission_objects_update', conditions={'method': ['POST']})
408 action='permission_objects_update', conditions={'method': ['POST']})
409 m.connect('admin_permissions_object', '/permissions/object',
409 m.connect('admin_permissions_object', '/permissions/object',
410 action='permission_objects', conditions={'method': ['GET']})
410 action='permission_objects', conditions={'method': ['GET']})
411
411
412 m.connect('admin_permissions_ips', '/permissions/ips',
412 m.connect('admin_permissions_ips', '/permissions/ips',
413 action='permission_ips', conditions={'method': ['POST']})
413 action='permission_ips', conditions={'method': ['POST']})
414 m.connect('admin_permissions_ips', '/permissions/ips',
414 m.connect('admin_permissions_ips', '/permissions/ips',
415 action='permission_ips', conditions={'method': ['GET']})
415 action='permission_ips', conditions={'method': ['GET']})
416
416
417 m.connect('admin_permissions_overview', '/permissions/overview',
417 m.connect('admin_permissions_overview', '/permissions/overview',
418 action='permission_perms', conditions={'method': ['GET']})
418 action='permission_perms', conditions={'method': ['GET']})
419
419
420 # ADMIN DEFAULTS REST ROUTES
420 # ADMIN DEFAULTS REST ROUTES
421 with rmap.submapper(path_prefix=ADMIN_PREFIX,
421 with rmap.submapper(path_prefix=ADMIN_PREFIX,
422 controller='admin/defaults') as m:
422 controller='admin/defaults') as m:
423 m.connect('admin_defaults_repositories', '/defaults/repositories',
423 m.connect('admin_defaults_repositories', '/defaults/repositories',
424 action='update_repository_defaults', conditions={'method': ['POST']})
424 action='update_repository_defaults', conditions={'method': ['POST']})
425 m.connect('admin_defaults_repositories', '/defaults/repositories',
425 m.connect('admin_defaults_repositories', '/defaults/repositories',
426 action='index', conditions={'method': ['GET']})
426 action='index', conditions={'method': ['GET']})
427
427
428 # ADMIN DEBUG STYLE ROUTES
428 # ADMIN DEBUG STYLE ROUTES
429 if str2bool(config.get('debug_style')):
429 if str2bool(config.get('debug_style')):
430 with rmap.submapper(path_prefix=ADMIN_PREFIX + '/debug_style',
430 with rmap.submapper(path_prefix=ADMIN_PREFIX + '/debug_style',
431 controller='debug_style') as m:
431 controller='debug_style') as m:
432 m.connect('debug_style_home', '',
432 m.connect('debug_style_home', '',
433 action='index', conditions={'method': ['GET']})
433 action='index', conditions={'method': ['GET']})
434 m.connect('debug_style_template', '/t/{t_path}',
434 m.connect('debug_style_template', '/t/{t_path}',
435 action='template', conditions={'method': ['GET']})
435 action='template', conditions={'method': ['GET']})
436
436
437 # ADMIN SETTINGS ROUTES
437 # ADMIN SETTINGS ROUTES
438 with rmap.submapper(path_prefix=ADMIN_PREFIX,
438 with rmap.submapper(path_prefix=ADMIN_PREFIX,
439 controller='admin/settings') as m:
439 controller='admin/settings') as m:
440
440
441 # default
441 # default
442 m.connect('admin_settings', '/settings',
442 m.connect('admin_settings', '/settings',
443 action='settings_global_update',
443 action='settings_global_update',
444 conditions={'method': ['POST']})
444 conditions={'method': ['POST']})
445 m.connect('admin_settings', '/settings',
445 m.connect('admin_settings', '/settings',
446 action='settings_global', conditions={'method': ['GET']})
446 action='settings_global', conditions={'method': ['GET']})
447
447
448 m.connect('admin_settings_vcs', '/settings/vcs',
448 m.connect('admin_settings_vcs', '/settings/vcs',
449 action='settings_vcs_update',
449 action='settings_vcs_update',
450 conditions={'method': ['POST']})
450 conditions={'method': ['POST']})
451 m.connect('admin_settings_vcs', '/settings/vcs',
451 m.connect('admin_settings_vcs', '/settings/vcs',
452 action='settings_vcs',
452 action='settings_vcs',
453 conditions={'method': ['GET']})
453 conditions={'method': ['GET']})
454 m.connect('admin_settings_vcs', '/settings/vcs',
454 m.connect('admin_settings_vcs', '/settings/vcs',
455 action='delete_svn_pattern',
455 action='delete_svn_pattern',
456 conditions={'method': ['DELETE']})
456 conditions={'method': ['DELETE']})
457
457
458 m.connect('admin_settings_mapping', '/settings/mapping',
458 m.connect('admin_settings_mapping', '/settings/mapping',
459 action='settings_mapping_update',
459 action='settings_mapping_update',
460 conditions={'method': ['POST']})
460 conditions={'method': ['POST']})
461 m.connect('admin_settings_mapping', '/settings/mapping',
461 m.connect('admin_settings_mapping', '/settings/mapping',
462 action='settings_mapping', conditions={'method': ['GET']})
462 action='settings_mapping', conditions={'method': ['GET']})
463
463
464 m.connect('admin_settings_global', '/settings/global',
464 m.connect('admin_settings_global', '/settings/global',
465 action='settings_global_update',
465 action='settings_global_update',
466 conditions={'method': ['POST']})
466 conditions={'method': ['POST']})
467 m.connect('admin_settings_global', '/settings/global',
467 m.connect('admin_settings_global', '/settings/global',
468 action='settings_global', conditions={'method': ['GET']})
468 action='settings_global', conditions={'method': ['GET']})
469
469
470 m.connect('admin_settings_visual', '/settings/visual',
470 m.connect('admin_settings_visual', '/settings/visual',
471 action='settings_visual_update',
471 action='settings_visual_update',
472 conditions={'method': ['POST']})
472 conditions={'method': ['POST']})
473 m.connect('admin_settings_visual', '/settings/visual',
473 m.connect('admin_settings_visual', '/settings/visual',
474 action='settings_visual', conditions={'method': ['GET']})
474 action='settings_visual', conditions={'method': ['GET']})
475
475
476 m.connect('admin_settings_issuetracker',
476 m.connect('admin_settings_issuetracker',
477 '/settings/issue-tracker', action='settings_issuetracker',
477 '/settings/issue-tracker', action='settings_issuetracker',
478 conditions={'method': ['GET']})
478 conditions={'method': ['GET']})
479 m.connect('admin_settings_issuetracker_save',
479 m.connect('admin_settings_issuetracker_save',
480 '/settings/issue-tracker/save',
480 '/settings/issue-tracker/save',
481 action='settings_issuetracker_save',
481 action='settings_issuetracker_save',
482 conditions={'method': ['POST']})
482 conditions={'method': ['POST']})
483 m.connect('admin_issuetracker_test', '/settings/issue-tracker/test',
483 m.connect('admin_issuetracker_test', '/settings/issue-tracker/test',
484 action='settings_issuetracker_test',
484 action='settings_issuetracker_test',
485 conditions={'method': ['POST']})
485 conditions={'method': ['POST']})
486 m.connect('admin_issuetracker_delete',
486 m.connect('admin_issuetracker_delete',
487 '/settings/issue-tracker/delete',
487 '/settings/issue-tracker/delete',
488 action='settings_issuetracker_delete',
488 action='settings_issuetracker_delete',
489 conditions={'method': ['DELETE']})
489 conditions={'method': ['DELETE']})
490
490
491 m.connect('admin_settings_email', '/settings/email',
491 m.connect('admin_settings_email', '/settings/email',
492 action='settings_email_update',
492 action='settings_email_update',
493 conditions={'method': ['POST']})
493 conditions={'method': ['POST']})
494 m.connect('admin_settings_email', '/settings/email',
494 m.connect('admin_settings_email', '/settings/email',
495 action='settings_email', conditions={'method': ['GET']})
495 action='settings_email', conditions={'method': ['GET']})
496
496
497 m.connect('admin_settings_hooks', '/settings/hooks',
497 m.connect('admin_settings_hooks', '/settings/hooks',
498 action='settings_hooks_update',
498 action='settings_hooks_update',
499 conditions={'method': ['POST', 'DELETE']})
499 conditions={'method': ['POST', 'DELETE']})
500 m.connect('admin_settings_hooks', '/settings/hooks',
500 m.connect('admin_settings_hooks', '/settings/hooks',
501 action='settings_hooks', conditions={'method': ['GET']})
501 action='settings_hooks', conditions={'method': ['GET']})
502
502
503 m.connect('admin_settings_search', '/settings/search',
503 m.connect('admin_settings_search', '/settings/search',
504 action='settings_search', conditions={'method': ['GET']})
504 action='settings_search', conditions={'method': ['GET']})
505
505
506 m.connect('admin_settings_system', '/settings/system',
506 m.connect('admin_settings_system', '/settings/system',
507 action='settings_system', conditions={'method': ['GET']})
507 action='settings_system', conditions={'method': ['GET']})
508
508
509 m.connect('admin_settings_system_update', '/settings/system/updates',
509 m.connect('admin_settings_system_update', '/settings/system/updates',
510 action='settings_system_update', conditions={'method': ['GET']})
510 action='settings_system_update', conditions={'method': ['GET']})
511
511
512 m.connect('admin_settings_supervisor', '/settings/supervisor',
512 m.connect('admin_settings_supervisor', '/settings/supervisor',
513 action='settings_supervisor', conditions={'method': ['GET']})
513 action='settings_supervisor', conditions={'method': ['GET']})
514 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
514 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
515 action='settings_supervisor_log', conditions={'method': ['GET']})
515 action='settings_supervisor_log', conditions={'method': ['GET']})
516
516
517 m.connect('admin_settings_labs', '/settings/labs',
517 m.connect('admin_settings_labs', '/settings/labs',
518 action='settings_labs_update',
518 action='settings_labs_update',
519 conditions={'method': ['POST']})
519 conditions={'method': ['POST']})
520 m.connect('admin_settings_labs', '/settings/labs',
520 m.connect('admin_settings_labs', '/settings/labs',
521 action='settings_labs', conditions={'method': ['GET']})
521 action='settings_labs', conditions={'method': ['GET']})
522
522
523 # ADMIN MY ACCOUNT
523 # ADMIN MY ACCOUNT
524 with rmap.submapper(path_prefix=ADMIN_PREFIX,
524 with rmap.submapper(path_prefix=ADMIN_PREFIX,
525 controller='admin/my_account') as m:
525 controller='admin/my_account') as m:
526
526
527 m.connect('my_account', '/my_account',
527 m.connect('my_account', '/my_account',
528 action='my_account', conditions={'method': ['GET']})
528 action='my_account', conditions={'method': ['GET']})
529 m.connect('my_account_edit', '/my_account/edit',
529 m.connect('my_account_edit', '/my_account/edit',
530 action='my_account_edit', conditions={'method': ['GET']})
530 action='my_account_edit', conditions={'method': ['GET']})
531 m.connect('my_account', '/my_account',
531 m.connect('my_account', '/my_account',
532 action='my_account_update', conditions={'method': ['POST']})
532 action='my_account_update', conditions={'method': ['POST']})
533
533
534 m.connect('my_account_password', '/my_account/password',
534 m.connect('my_account_password', '/my_account/password',
535 action='my_account_password', conditions={'method': ['GET', 'POST']})
535 action='my_account_password', conditions={'method': ['GET', 'POST']})
536
536
537 m.connect('my_account_repos', '/my_account/repos',
537 m.connect('my_account_repos', '/my_account/repos',
538 action='my_account_repos', conditions={'method': ['GET']})
538 action='my_account_repos', conditions={'method': ['GET']})
539
539
540 m.connect('my_account_watched', '/my_account/watched',
540 m.connect('my_account_watched', '/my_account/watched',
541 action='my_account_watched', conditions={'method': ['GET']})
541 action='my_account_watched', conditions={'method': ['GET']})
542
542
543 m.connect('my_account_pullrequests', '/my_account/pull_requests',
543 m.connect('my_account_pullrequests', '/my_account/pull_requests',
544 action='my_account_pullrequests', conditions={'method': ['GET']})
544 action='my_account_pullrequests', conditions={'method': ['GET']})
545
545
546 m.connect('my_account_perms', '/my_account/perms',
546 m.connect('my_account_perms', '/my_account/perms',
547 action='my_account_perms', conditions={'method': ['GET']})
547 action='my_account_perms', conditions={'method': ['GET']})
548
548
549 m.connect('my_account_emails', '/my_account/emails',
549 m.connect('my_account_emails', '/my_account/emails',
550 action='my_account_emails', conditions={'method': ['GET']})
550 action='my_account_emails', conditions={'method': ['GET']})
551 m.connect('my_account_emails', '/my_account/emails',
551 m.connect('my_account_emails', '/my_account/emails',
552 action='my_account_emails_add', conditions={'method': ['POST']})
552 action='my_account_emails_add', conditions={'method': ['POST']})
553 m.connect('my_account_emails', '/my_account/emails',
553 m.connect('my_account_emails', '/my_account/emails',
554 action='my_account_emails_delete', conditions={'method': ['DELETE']})
554 action='my_account_emails_delete', conditions={'method': ['DELETE']})
555
555
556 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
556 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
557 action='my_account_auth_tokens', conditions={'method': ['GET']})
557 action='my_account_auth_tokens', conditions={'method': ['GET']})
558 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
558 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
559 action='my_account_auth_tokens_add', conditions={'method': ['POST']})
559 action='my_account_auth_tokens_add', conditions={'method': ['POST']})
560 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
560 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
561 action='my_account_auth_tokens_delete', conditions={'method': ['DELETE']})
561 action='my_account_auth_tokens_delete', conditions={'method': ['DELETE']})
562 m.connect('my_account_notifications', '/my_account/notifications',
562 m.connect('my_account_notifications', '/my_account/notifications',
563 action='my_notifications',
563 action='my_notifications',
564 conditions={'method': ['GET']})
564 conditions={'method': ['GET']})
565 m.connect('my_account_notifications_toggle_visibility',
565 m.connect('my_account_notifications_toggle_visibility',
566 '/my_account/toggle_visibility',
566 '/my_account/toggle_visibility',
567 action='my_notifications_toggle_visibility',
567 action='my_notifications_toggle_visibility',
568 conditions={'method': ['POST']})
568 conditions={'method': ['POST']})
569
569
570 # NOTIFICATION REST ROUTES
570 # NOTIFICATION REST ROUTES
571 with rmap.submapper(path_prefix=ADMIN_PREFIX,
571 with rmap.submapper(path_prefix=ADMIN_PREFIX,
572 controller='admin/notifications') as m:
572 controller='admin/notifications') as m:
573 m.connect('notifications', '/notifications',
573 m.connect('notifications', '/notifications',
574 action='index', conditions={'method': ['GET']})
574 action='index', conditions={'method': ['GET']})
575 m.connect('notifications_mark_all_read', '/notifications/mark_all_read',
575 m.connect('notifications_mark_all_read', '/notifications/mark_all_read',
576 action='mark_all_read', conditions={'method': ['POST']})
576 action='mark_all_read', conditions={'method': ['POST']})
577 m.connect('/notifications/{notification_id}',
577 m.connect('/notifications/{notification_id}',
578 action='update', conditions={'method': ['PUT']})
578 action='update', conditions={'method': ['PUT']})
579 m.connect('/notifications/{notification_id}',
579 m.connect('/notifications/{notification_id}',
580 action='delete', conditions={'method': ['DELETE']})
580 action='delete', conditions={'method': ['DELETE']})
581 m.connect('notification', '/notifications/{notification_id}',
581 m.connect('notification', '/notifications/{notification_id}',
582 action='show', conditions={'method': ['GET']})
582 action='show', conditions={'method': ['GET']})
583
583
584 # ADMIN GIST
584 # ADMIN GIST
585 with rmap.submapper(path_prefix=ADMIN_PREFIX,
585 with rmap.submapper(path_prefix=ADMIN_PREFIX,
586 controller='admin/gists') as m:
586 controller='admin/gists') as m:
587 m.connect('gists', '/gists',
587 m.connect('gists', '/gists',
588 action='create', conditions={'method': ['POST']})
588 action='create', conditions={'method': ['POST']})
589 m.connect('gists', '/gists', jsroute=True,
589 m.connect('gists', '/gists', jsroute=True,
590 action='index', conditions={'method': ['GET']})
590 action='index', conditions={'method': ['GET']})
591 m.connect('new_gist', '/gists/new', jsroute=True,
591 m.connect('new_gist', '/gists/new', jsroute=True,
592 action='new', conditions={'method': ['GET']})
592 action='new', conditions={'method': ['GET']})
593
593
594 m.connect('/gists/{gist_id}',
594 m.connect('/gists/{gist_id}',
595 action='delete', conditions={'method': ['DELETE']})
595 action='delete', conditions={'method': ['DELETE']})
596 m.connect('edit_gist', '/gists/{gist_id}/edit',
596 m.connect('edit_gist', '/gists/{gist_id}/edit',
597 action='edit_form', conditions={'method': ['GET']})
597 action='edit_form', conditions={'method': ['GET']})
598 m.connect('edit_gist', '/gists/{gist_id}/edit',
598 m.connect('edit_gist', '/gists/{gist_id}/edit',
599 action='edit', conditions={'method': ['POST']})
599 action='edit', conditions={'method': ['POST']})
600 m.connect(
600 m.connect(
601 'edit_gist_check_revision', '/gists/{gist_id}/edit/check_revision',
601 'edit_gist_check_revision', '/gists/{gist_id}/edit/check_revision',
602 action='check_revision', conditions={'method': ['GET']})
602 action='check_revision', conditions={'method': ['GET']})
603
603
604 m.connect('gist', '/gists/{gist_id}',
604 m.connect('gist', '/gists/{gist_id}',
605 action='show', conditions={'method': ['GET']})
605 action='show', conditions={'method': ['GET']})
606 m.connect('gist_rev', '/gists/{gist_id}/{revision}',
606 m.connect('gist_rev', '/gists/{gist_id}/{revision}',
607 revision='tip',
607 revision='tip',
608 action='show', conditions={'method': ['GET']})
608 action='show', conditions={'method': ['GET']})
609 m.connect('formatted_gist', '/gists/{gist_id}/{revision}/{format}',
609 m.connect('formatted_gist', '/gists/{gist_id}/{revision}/{format}',
610 revision='tip',
610 revision='tip',
611 action='show', conditions={'method': ['GET']})
611 action='show', conditions={'method': ['GET']})
612 m.connect('formatted_gist_file', '/gists/{gist_id}/{revision}/{format}/{f_path}',
612 m.connect('formatted_gist_file', '/gists/{gist_id}/{revision}/{format}/{f_path}',
613 revision='tip',
613 revision='tip',
614 action='show', conditions={'method': ['GET']},
614 action='show', conditions={'method': ['GET']},
615 requirements=URL_NAME_REQUIREMENTS)
615 requirements=URL_NAME_REQUIREMENTS)
616
616
617 # ADMIN MAIN PAGES
617 # ADMIN MAIN PAGES
618 with rmap.submapper(path_prefix=ADMIN_PREFIX,
618 with rmap.submapper(path_prefix=ADMIN_PREFIX,
619 controller='admin/admin') as m:
619 controller='admin/admin') as m:
620 m.connect('admin_home', '', action='index')
620 m.connect('admin_home', '', action='index')
621 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
621 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
622 action='add_repo')
622 action='add_repo')
623 m.connect(
623 m.connect(
624 'pull_requests_global_0', '/pull_requests/{pull_request_id:[0-9]+}',
624 'pull_requests_global_0', '/pull_requests/{pull_request_id:[0-9]+}',
625 action='pull_requests')
625 action='pull_requests')
626 m.connect(
626 m.connect(
627 'pull_requests_global', '/pull-requests/{pull_request_id:[0-9]+}',
627 'pull_requests_global', '/pull-requests/{pull_request_id:[0-9]+}',
628 action='pull_requests')
628 action='pull_requests')
629
629
630
630
631 # USER JOURNAL
631 # USER JOURNAL
632 rmap.connect('journal', '%s/journal' % (ADMIN_PREFIX,),
632 rmap.connect('journal', '%s/journal' % (ADMIN_PREFIX,),
633 controller='journal', action='index')
633 controller='journal', action='index')
634 rmap.connect('journal_rss', '%s/journal/rss' % (ADMIN_PREFIX,),
634 rmap.connect('journal_rss', '%s/journal/rss' % (ADMIN_PREFIX,),
635 controller='journal', action='journal_rss')
635 controller='journal', action='journal_rss')
636 rmap.connect('journal_atom', '%s/journal/atom' % (ADMIN_PREFIX,),
636 rmap.connect('journal_atom', '%s/journal/atom' % (ADMIN_PREFIX,),
637 controller='journal', action='journal_atom')
637 controller='journal', action='journal_atom')
638
638
639 rmap.connect('public_journal', '%s/public_journal' % (ADMIN_PREFIX,),
639 rmap.connect('public_journal', '%s/public_journal' % (ADMIN_PREFIX,),
640 controller='journal', action='public_journal')
640 controller='journal', action='public_journal')
641
641
642 rmap.connect('public_journal_rss', '%s/public_journal/rss' % (ADMIN_PREFIX,),
642 rmap.connect('public_journal_rss', '%s/public_journal/rss' % (ADMIN_PREFIX,),
643 controller='journal', action='public_journal_rss')
643 controller='journal', action='public_journal_rss')
644
644
645 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % (ADMIN_PREFIX,),
645 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % (ADMIN_PREFIX,),
646 controller='journal', action='public_journal_rss')
646 controller='journal', action='public_journal_rss')
647
647
648 rmap.connect('public_journal_atom',
648 rmap.connect('public_journal_atom',
649 '%s/public_journal/atom' % (ADMIN_PREFIX,), controller='journal',
649 '%s/public_journal/atom' % (ADMIN_PREFIX,), controller='journal',
650 action='public_journal_atom')
650 action='public_journal_atom')
651
651
652 rmap.connect('public_journal_atom_old',
652 rmap.connect('public_journal_atom_old',
653 '%s/public_journal_atom' % (ADMIN_PREFIX,), controller='journal',
653 '%s/public_journal_atom' % (ADMIN_PREFIX,), controller='journal',
654 action='public_journal_atom')
654 action='public_journal_atom')
655
655
656 rmap.connect('toggle_following', '%s/toggle_following' % (ADMIN_PREFIX,),
656 rmap.connect('toggle_following', '%s/toggle_following' % (ADMIN_PREFIX,),
657 controller='journal', action='toggle_following', jsroute=True,
657 controller='journal', action='toggle_following', jsroute=True,
658 conditions={'method': ['POST']})
658 conditions={'method': ['POST']})
659
659
660 # FULL TEXT SEARCH
660 # FULL TEXT SEARCH
661 rmap.connect('search', '%s/search' % (ADMIN_PREFIX,),
661 rmap.connect('search', '%s/search' % (ADMIN_PREFIX,),
662 controller='search')
662 controller='search')
663 rmap.connect('search_repo_home', '/{repo_name}/search',
663 rmap.connect('search_repo_home', '/{repo_name}/search',
664 controller='search',
664 controller='search',
665 action='index',
665 action='index',
666 conditions={'function': check_repo},
666 conditions={'function': check_repo},
667 requirements=URL_NAME_REQUIREMENTS)
667 requirements=URL_NAME_REQUIREMENTS)
668
668
669 # FEEDS
669 # FEEDS
670 rmap.connect('rss_feed_home', '/{repo_name}/feed/rss',
670 rmap.connect('rss_feed_home', '/{repo_name}/feed/rss',
671 controller='feed', action='rss',
671 controller='feed', action='rss',
672 conditions={'function': check_repo},
672 conditions={'function': check_repo},
673 requirements=URL_NAME_REQUIREMENTS)
673 requirements=URL_NAME_REQUIREMENTS)
674
674
675 rmap.connect('atom_feed_home', '/{repo_name}/feed/atom',
675 rmap.connect('atom_feed_home', '/{repo_name}/feed/atom',
676 controller='feed', action='atom',
676 controller='feed', action='atom',
677 conditions={'function': check_repo},
677 conditions={'function': check_repo},
678 requirements=URL_NAME_REQUIREMENTS)
678 requirements=URL_NAME_REQUIREMENTS)
679
679
680 #==========================================================================
680 #==========================================================================
681 # REPOSITORY ROUTES
681 # REPOSITORY ROUTES
682 #==========================================================================
682 #==========================================================================
683
683
684 rmap.connect('repo_creating_home', '/{repo_name}/repo_creating',
684 rmap.connect('repo_creating_home', '/{repo_name}/repo_creating',
685 controller='admin/repos', action='repo_creating',
685 controller='admin/repos', action='repo_creating',
686 requirements=URL_NAME_REQUIREMENTS)
686 requirements=URL_NAME_REQUIREMENTS)
687 rmap.connect('repo_check_home', '/{repo_name}/crepo_check',
687 rmap.connect('repo_check_home', '/{repo_name}/crepo_check',
688 controller='admin/repos', action='repo_check',
688 controller='admin/repos', action='repo_check',
689 requirements=URL_NAME_REQUIREMENTS)
689 requirements=URL_NAME_REQUIREMENTS)
690
690
691 rmap.connect('repo_stats', '/{repo_name}/repo_stats/{commit_id}',
691 rmap.connect('repo_stats', '/{repo_name}/repo_stats/{commit_id}',
692 controller='summary', action='repo_stats',
692 controller='summary', action='repo_stats',
693 conditions={'function': check_repo},
693 conditions={'function': check_repo},
694 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
694 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
695
695
696 rmap.connect('repo_refs_data', '/{repo_name}/refs-data',
696 rmap.connect('repo_refs_data', '/{repo_name}/refs-data',
697 controller='summary', action='repo_refs_data', jsroute=True,
697 controller='summary', action='repo_refs_data', jsroute=True,
698 requirements=URL_NAME_REQUIREMENTS)
698 requirements=URL_NAME_REQUIREMENTS)
699 rmap.connect('repo_refs_changelog_data', '/{repo_name}/refs-data-changelog',
699 rmap.connect('repo_refs_changelog_data', '/{repo_name}/refs-data-changelog',
700 controller='summary', action='repo_refs_changelog_data',
700 controller='summary', action='repo_refs_changelog_data',
701 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
701 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
702 rmap.connect('repo_default_reviewers_data', '/{repo_name}/default-reviewers',
702 rmap.connect('repo_default_reviewers_data', '/{repo_name}/default-reviewers',
703 controller='summary', action='repo_default_reviewers_data',
703 controller='summary', action='repo_default_reviewers_data',
704 jsroute=True, requirements=URL_NAME_REQUIREMENTS)
704 jsroute=True, requirements=URL_NAME_REQUIREMENTS)
705
705
706 rmap.connect('changeset_home', '/{repo_name}/changeset/{revision}',
706 rmap.connect('changeset_home', '/{repo_name}/changeset/{revision}',
707 controller='changeset', revision='tip', jsroute=True,
707 controller='changeset', revision='tip', jsroute=True,
708 conditions={'function': check_repo},
708 conditions={'function': check_repo},
709 requirements=URL_NAME_REQUIREMENTS)
709 requirements=URL_NAME_REQUIREMENTS)
710 rmap.connect('changeset_children', '/{repo_name}/changeset_children/{revision}',
710 rmap.connect('changeset_children', '/{repo_name}/changeset_children/{revision}',
711 controller='changeset', revision='tip', action='changeset_children',
711 controller='changeset', revision='tip', action='changeset_children',
712 conditions={'function': check_repo},
712 conditions={'function': check_repo},
713 requirements=URL_NAME_REQUIREMENTS)
713 requirements=URL_NAME_REQUIREMENTS)
714 rmap.connect('changeset_parents', '/{repo_name}/changeset_parents/{revision}',
714 rmap.connect('changeset_parents', '/{repo_name}/changeset_parents/{revision}',
715 controller='changeset', revision='tip', action='changeset_parents',
715 controller='changeset', revision='tip', action='changeset_parents',
716 conditions={'function': check_repo},
716 conditions={'function': check_repo},
717 requirements=URL_NAME_REQUIREMENTS)
717 requirements=URL_NAME_REQUIREMENTS)
718
718
719 # repo edit options
719 # repo edit options
720 rmap.connect('edit_repo', '/{repo_name}/settings', jsroute=True,
720 rmap.connect('edit_repo', '/{repo_name}/settings', jsroute=True,
721 controller='admin/repos', action='edit',
721 controller='admin/repos', action='edit',
722 conditions={'method': ['GET'], 'function': check_repo},
722 conditions={'method': ['GET'], 'function': check_repo},
723 requirements=URL_NAME_REQUIREMENTS)
723 requirements=URL_NAME_REQUIREMENTS)
724
724
725 rmap.connect('edit_repo_perms', '/{repo_name}/settings/permissions',
725 rmap.connect('edit_repo_perms', '/{repo_name}/settings/permissions',
726 jsroute=True,
726 jsroute=True,
727 controller='admin/repos', action='edit_permissions',
727 controller='admin/repos', action='edit_permissions',
728 conditions={'method': ['GET'], 'function': check_repo},
728 conditions={'method': ['GET'], 'function': check_repo},
729 requirements=URL_NAME_REQUIREMENTS)
729 requirements=URL_NAME_REQUIREMENTS)
730 rmap.connect('edit_repo_perms_update', '/{repo_name}/settings/permissions',
730 rmap.connect('edit_repo_perms_update', '/{repo_name}/settings/permissions',
731 controller='admin/repos', action='edit_permissions_update',
731 controller='admin/repos', action='edit_permissions_update',
732 conditions={'method': ['PUT'], 'function': check_repo},
732 conditions={'method': ['PUT'], 'function': check_repo},
733 requirements=URL_NAME_REQUIREMENTS)
733 requirements=URL_NAME_REQUIREMENTS)
734
734
735 rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields',
735 rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields',
736 controller='admin/repos', action='edit_fields',
736 controller='admin/repos', action='edit_fields',
737 conditions={'method': ['GET'], 'function': check_repo},
737 conditions={'method': ['GET'], 'function': check_repo},
738 requirements=URL_NAME_REQUIREMENTS)
738 requirements=URL_NAME_REQUIREMENTS)
739 rmap.connect('create_repo_fields', '/{repo_name}/settings/fields/new',
739 rmap.connect('create_repo_fields', '/{repo_name}/settings/fields/new',
740 controller='admin/repos', action='create_repo_field',
740 controller='admin/repos', action='create_repo_field',
741 conditions={'method': ['PUT'], 'function': check_repo},
741 conditions={'method': ['PUT'], 'function': check_repo},
742 requirements=URL_NAME_REQUIREMENTS)
742 requirements=URL_NAME_REQUIREMENTS)
743 rmap.connect('delete_repo_fields', '/{repo_name}/settings/fields/{field_id}',
743 rmap.connect('delete_repo_fields', '/{repo_name}/settings/fields/{field_id}',
744 controller='admin/repos', action='delete_repo_field',
744 controller='admin/repos', action='delete_repo_field',
745 conditions={'method': ['DELETE'], 'function': check_repo},
745 conditions={'method': ['DELETE'], 'function': check_repo},
746 requirements=URL_NAME_REQUIREMENTS)
746 requirements=URL_NAME_REQUIREMENTS)
747
747
748 rmap.connect('edit_repo_advanced', '/{repo_name}/settings/advanced',
748 rmap.connect('edit_repo_advanced', '/{repo_name}/settings/advanced',
749 controller='admin/repos', action='edit_advanced',
749 controller='admin/repos', action='edit_advanced',
750 conditions={'method': ['GET'], 'function': check_repo},
750 conditions={'method': ['GET'], 'function': check_repo},
751 requirements=URL_NAME_REQUIREMENTS)
751 requirements=URL_NAME_REQUIREMENTS)
752
752
753 rmap.connect('edit_repo_advanced_locking', '/{repo_name}/settings/advanced/locking',
753 rmap.connect('edit_repo_advanced_locking', '/{repo_name}/settings/advanced/locking',
754 controller='admin/repos', action='edit_advanced_locking',
754 controller='admin/repos', action='edit_advanced_locking',
755 conditions={'method': ['PUT'], 'function': check_repo},
755 conditions={'method': ['PUT'], 'function': check_repo},
756 requirements=URL_NAME_REQUIREMENTS)
756 requirements=URL_NAME_REQUIREMENTS)
757 rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle',
757 rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle',
758 controller='admin/repos', action='toggle_locking',
758 controller='admin/repos', action='toggle_locking',
759 conditions={'method': ['GET'], 'function': check_repo},
759 conditions={'method': ['GET'], 'function': check_repo},
760 requirements=URL_NAME_REQUIREMENTS)
760 requirements=URL_NAME_REQUIREMENTS)
761
761
762 rmap.connect('edit_repo_advanced_journal', '/{repo_name}/settings/advanced/journal',
762 rmap.connect('edit_repo_advanced_journal', '/{repo_name}/settings/advanced/journal',
763 controller='admin/repos', action='edit_advanced_journal',
763 controller='admin/repos', action='edit_advanced_journal',
764 conditions={'method': ['PUT'], 'function': check_repo},
764 conditions={'method': ['PUT'], 'function': check_repo},
765 requirements=URL_NAME_REQUIREMENTS)
765 requirements=URL_NAME_REQUIREMENTS)
766
766
767 rmap.connect('edit_repo_advanced_fork', '/{repo_name}/settings/advanced/fork',
767 rmap.connect('edit_repo_advanced_fork', '/{repo_name}/settings/advanced/fork',
768 controller='admin/repos', action='edit_advanced_fork',
768 controller='admin/repos', action='edit_advanced_fork',
769 conditions={'method': ['PUT'], 'function': check_repo},
769 conditions={'method': ['PUT'], 'function': check_repo},
770 requirements=URL_NAME_REQUIREMENTS)
770 requirements=URL_NAME_REQUIREMENTS)
771
771
772 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
772 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
773 controller='admin/repos', action='edit_caches_form',
773 controller='admin/repos', action='edit_caches_form',
774 conditions={'method': ['GET'], 'function': check_repo},
774 conditions={'method': ['GET'], 'function': check_repo},
775 requirements=URL_NAME_REQUIREMENTS)
775 requirements=URL_NAME_REQUIREMENTS)
776 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
776 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
777 controller='admin/repos', action='edit_caches',
777 controller='admin/repos', action='edit_caches',
778 conditions={'method': ['PUT'], 'function': check_repo},
778 conditions={'method': ['PUT'], 'function': check_repo},
779 requirements=URL_NAME_REQUIREMENTS)
779 requirements=URL_NAME_REQUIREMENTS)
780
780
781 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
781 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
782 controller='admin/repos', action='edit_remote_form',
782 controller='admin/repos', action='edit_remote_form',
783 conditions={'method': ['GET'], 'function': check_repo},
783 conditions={'method': ['GET'], 'function': check_repo},
784 requirements=URL_NAME_REQUIREMENTS)
784 requirements=URL_NAME_REQUIREMENTS)
785 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
785 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
786 controller='admin/repos', action='edit_remote',
786 controller='admin/repos', action='edit_remote',
787 conditions={'method': ['PUT'], 'function': check_repo},
787 conditions={'method': ['PUT'], 'function': check_repo},
788 requirements=URL_NAME_REQUIREMENTS)
788 requirements=URL_NAME_REQUIREMENTS)
789
789
790 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
790 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
791 controller='admin/repos', action='edit_statistics_form',
791 controller='admin/repos', action='edit_statistics_form',
792 conditions={'method': ['GET'], 'function': check_repo},
792 conditions={'method': ['GET'], 'function': check_repo},
793 requirements=URL_NAME_REQUIREMENTS)
793 requirements=URL_NAME_REQUIREMENTS)
794 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
794 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
795 controller='admin/repos', action='edit_statistics',
795 controller='admin/repos', action='edit_statistics',
796 conditions={'method': ['PUT'], 'function': check_repo},
796 conditions={'method': ['PUT'], 'function': check_repo},
797 requirements=URL_NAME_REQUIREMENTS)
797 requirements=URL_NAME_REQUIREMENTS)
798 rmap.connect('repo_settings_issuetracker',
798 rmap.connect('repo_settings_issuetracker',
799 '/{repo_name}/settings/issue-tracker',
799 '/{repo_name}/settings/issue-tracker',
800 controller='admin/repos', action='repo_issuetracker',
800 controller='admin/repos', action='repo_issuetracker',
801 conditions={'method': ['GET'], 'function': check_repo},
801 conditions={'method': ['GET'], 'function': check_repo},
802 requirements=URL_NAME_REQUIREMENTS)
802 requirements=URL_NAME_REQUIREMENTS)
803 rmap.connect('repo_issuetracker_test',
803 rmap.connect('repo_issuetracker_test',
804 '/{repo_name}/settings/issue-tracker/test',
804 '/{repo_name}/settings/issue-tracker/test',
805 controller='admin/repos', action='repo_issuetracker_test',
805 controller='admin/repos', action='repo_issuetracker_test',
806 conditions={'method': ['POST'], 'function': check_repo},
806 conditions={'method': ['POST'], 'function': check_repo},
807 requirements=URL_NAME_REQUIREMENTS)
807 requirements=URL_NAME_REQUIREMENTS)
808 rmap.connect('repo_issuetracker_delete',
808 rmap.connect('repo_issuetracker_delete',
809 '/{repo_name}/settings/issue-tracker/delete',
809 '/{repo_name}/settings/issue-tracker/delete',
810 controller='admin/repos', action='repo_issuetracker_delete',
810 controller='admin/repos', action='repo_issuetracker_delete',
811 conditions={'method': ['DELETE'], 'function': check_repo},
811 conditions={'method': ['DELETE'], 'function': check_repo},
812 requirements=URL_NAME_REQUIREMENTS)
812 requirements=URL_NAME_REQUIREMENTS)
813 rmap.connect('repo_issuetracker_save',
813 rmap.connect('repo_issuetracker_save',
814 '/{repo_name}/settings/issue-tracker/save',
814 '/{repo_name}/settings/issue-tracker/save',
815 controller='admin/repos', action='repo_issuetracker_save',
815 controller='admin/repos', action='repo_issuetracker_save',
816 conditions={'method': ['POST'], 'function': check_repo},
816 conditions={'method': ['POST'], 'function': check_repo},
817 requirements=URL_NAME_REQUIREMENTS)
817 requirements=URL_NAME_REQUIREMENTS)
818 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
818 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
819 controller='admin/repos', action='repo_settings_vcs_update',
819 controller='admin/repos', action='repo_settings_vcs_update',
820 conditions={'method': ['POST'], 'function': check_repo},
820 conditions={'method': ['POST'], 'function': check_repo},
821 requirements=URL_NAME_REQUIREMENTS)
821 requirements=URL_NAME_REQUIREMENTS)
822 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
822 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
823 controller='admin/repos', action='repo_settings_vcs',
823 controller='admin/repos', action='repo_settings_vcs',
824 conditions={'method': ['GET'], 'function': check_repo},
824 conditions={'method': ['GET'], 'function': check_repo},
825 requirements=URL_NAME_REQUIREMENTS)
825 requirements=URL_NAME_REQUIREMENTS)
826 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
826 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
827 controller='admin/repos', action='repo_delete_svn_pattern',
827 controller='admin/repos', action='repo_delete_svn_pattern',
828 conditions={'method': ['DELETE'], 'function': check_repo},
828 conditions={'method': ['DELETE'], 'function': check_repo},
829 requirements=URL_NAME_REQUIREMENTS)
829 requirements=URL_NAME_REQUIREMENTS)
830 rmap.connect('repo_pullrequest_settings', '/{repo_name}/settings/pullrequest',
830 rmap.connect('repo_pullrequest_settings', '/{repo_name}/settings/pullrequest',
831 controller='admin/repos', action='repo_settings_pullrequest',
831 controller='admin/repos', action='repo_settings_pullrequest',
832 conditions={'method': ['GET', 'POST'], 'function': check_repo},
832 conditions={'method': ['GET', 'POST'], 'function': check_repo},
833 requirements=URL_NAME_REQUIREMENTS)
833 requirements=URL_NAME_REQUIREMENTS)
834
834
835 # still working url for backward compat.
835 # still working url for backward compat.
836 rmap.connect('raw_changeset_home_depraced',
836 rmap.connect('raw_changeset_home_depraced',
837 '/{repo_name}/raw-changeset/{revision}',
837 '/{repo_name}/raw-changeset/{revision}',
838 controller='changeset', action='changeset_raw',
838 controller='changeset', action='changeset_raw',
839 revision='tip', conditions={'function': check_repo},
839 revision='tip', conditions={'function': check_repo},
840 requirements=URL_NAME_REQUIREMENTS)
840 requirements=URL_NAME_REQUIREMENTS)
841
841
842 # new URLs
842 # new URLs
843 rmap.connect('changeset_raw_home',
843 rmap.connect('changeset_raw_home',
844 '/{repo_name}/changeset-diff/{revision}',
844 '/{repo_name}/changeset-diff/{revision}',
845 controller='changeset', action='changeset_raw',
845 controller='changeset', action='changeset_raw',
846 revision='tip', conditions={'function': check_repo},
846 revision='tip', conditions={'function': check_repo},
847 requirements=URL_NAME_REQUIREMENTS)
847 requirements=URL_NAME_REQUIREMENTS)
848
848
849 rmap.connect('changeset_patch_home',
849 rmap.connect('changeset_patch_home',
850 '/{repo_name}/changeset-patch/{revision}',
850 '/{repo_name}/changeset-patch/{revision}',
851 controller='changeset', action='changeset_patch',
851 controller='changeset', action='changeset_patch',
852 revision='tip', conditions={'function': check_repo},
852 revision='tip', conditions={'function': check_repo},
853 requirements=URL_NAME_REQUIREMENTS)
853 requirements=URL_NAME_REQUIREMENTS)
854
854
855 rmap.connect('changeset_download_home',
855 rmap.connect('changeset_download_home',
856 '/{repo_name}/changeset-download/{revision}',
856 '/{repo_name}/changeset-download/{revision}',
857 controller='changeset', action='changeset_download',
857 controller='changeset', action='changeset_download',
858 revision='tip', conditions={'function': check_repo},
858 revision='tip', conditions={'function': check_repo},
859 requirements=URL_NAME_REQUIREMENTS)
859 requirements=URL_NAME_REQUIREMENTS)
860
860
861 rmap.connect('changeset_comment',
861 rmap.connect('changeset_comment',
862 '/{repo_name}/changeset/{revision}/comment', jsroute=True,
862 '/{repo_name}/changeset/{revision}/comment', jsroute=True,
863 controller='changeset', revision='tip', action='comment',
863 controller='changeset', revision='tip', action='comment',
864 conditions={'function': check_repo},
864 conditions={'function': check_repo},
865 requirements=URL_NAME_REQUIREMENTS)
865 requirements=URL_NAME_REQUIREMENTS)
866
866
867 rmap.connect('changeset_comment_preview',
867 rmap.connect('changeset_comment_preview',
868 '/{repo_name}/changeset/comment/preview', jsroute=True,
868 '/{repo_name}/changeset/comment/preview', jsroute=True,
869 controller='changeset', action='preview_comment',
869 controller='changeset', action='preview_comment',
870 conditions={'function': check_repo, 'method': ['POST']},
870 conditions={'function': check_repo, 'method': ['POST']},
871 requirements=URL_NAME_REQUIREMENTS)
871 requirements=URL_NAME_REQUIREMENTS)
872
872
873 rmap.connect('changeset_comment_delete',
873 rmap.connect('changeset_comment_delete',
874 '/{repo_name}/changeset/comment/{comment_id}/delete',
874 '/{repo_name}/changeset/comment/{comment_id}/delete',
875 controller='changeset', action='delete_comment',
875 controller='changeset', action='delete_comment',
876 conditions={'function': check_repo, 'method': ['DELETE']},
876 conditions={'function': check_repo, 'method': ['DELETE']},
877 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
877 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
878
878
879 rmap.connect('changeset_info', '/{repo_name}/changeset_info/{revision}',
879 rmap.connect('changeset_info', '/{repo_name}/changeset_info/{revision}',
880 controller='changeset', action='changeset_info',
880 controller='changeset', action='changeset_info',
881 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
881 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
882
882
883 rmap.connect('compare_home',
883 rmap.connect('compare_home',
884 '/{repo_name}/compare',
884 '/{repo_name}/compare',
885 controller='compare', action='index',
885 controller='compare', action='index',
886 conditions={'function': check_repo},
886 conditions={'function': check_repo},
887 requirements=URL_NAME_REQUIREMENTS)
887 requirements=URL_NAME_REQUIREMENTS)
888
888
889 rmap.connect('compare_url',
889 rmap.connect('compare_url',
890 '/{repo_name}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}',
890 '/{repo_name}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}',
891 controller='compare', action='compare',
891 controller='compare', action='compare',
892 conditions={'function': check_repo},
892 conditions={'function': check_repo},
893 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
893 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
894
894
895 rmap.connect('pullrequest_home',
895 rmap.connect('pullrequest_home',
896 '/{repo_name}/pull-request/new', controller='pullrequests',
896 '/{repo_name}/pull-request/new', controller='pullrequests',
897 action='index', conditions={'function': check_repo,
897 action='index', conditions={'function': check_repo,
898 'method': ['GET']},
898 'method': ['GET']},
899 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
899 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
900
900
901 rmap.connect('pullrequest',
901 rmap.connect('pullrequest',
902 '/{repo_name}/pull-request/new', controller='pullrequests',
902 '/{repo_name}/pull-request/new', controller='pullrequests',
903 action='create', conditions={'function': check_repo,
903 action='create', conditions={'function': check_repo,
904 'method': ['POST']},
904 'method': ['POST']},
905 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
905 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
906
906
907 rmap.connect('pullrequest_repo_refs',
907 rmap.connect('pullrequest_repo_refs',
908 '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
908 '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
909 controller='pullrequests',
909 controller='pullrequests',
910 action='get_repo_refs',
910 action='get_repo_refs',
911 conditions={'function': check_repo, 'method': ['GET']},
911 conditions={'function': check_repo, 'method': ['GET']},
912 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
912 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
913
913
914 rmap.connect('pullrequest_repo_destinations',
914 rmap.connect('pullrequest_repo_destinations',
915 '/{repo_name}/pull-request/repo-destinations',
915 '/{repo_name}/pull-request/repo-destinations',
916 controller='pullrequests',
916 controller='pullrequests',
917 action='get_repo_destinations',
917 action='get_repo_destinations',
918 conditions={'function': check_repo, 'method': ['GET']},
918 conditions={'function': check_repo, 'method': ['GET']},
919 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
919 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
920
920
921 rmap.connect('pullrequest_show',
921 rmap.connect('pullrequest_show',
922 '/{repo_name}/pull-request/{pull_request_id}',
922 '/{repo_name}/pull-request/{pull_request_id}',
923 controller='pullrequests',
923 controller='pullrequests',
924 action='show', conditions={'function': check_repo,
924 action='show', conditions={'function': check_repo,
925 'method': ['GET']},
925 'method': ['GET']},
926 requirements=URL_NAME_REQUIREMENTS)
926 requirements=URL_NAME_REQUIREMENTS)
927
927
928 rmap.connect('pullrequest_update',
928 rmap.connect('pullrequest_update',
929 '/{repo_name}/pull-request/{pull_request_id}',
929 '/{repo_name}/pull-request/{pull_request_id}',
930 controller='pullrequests',
930 controller='pullrequests',
931 action='update', conditions={'function': check_repo,
931 action='update', conditions={'function': check_repo,
932 'method': ['PUT']},
932 'method': ['PUT']},
933 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
933 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
934
934
935 rmap.connect('pullrequest_merge',
935 rmap.connect('pullrequest_merge',
936 '/{repo_name}/pull-request/{pull_request_id}',
936 '/{repo_name}/pull-request/{pull_request_id}',
937 controller='pullrequests',
937 controller='pullrequests',
938 action='merge', conditions={'function': check_repo,
938 action='merge', conditions={'function': check_repo,
939 'method': ['POST']},
939 'method': ['POST']},
940 requirements=URL_NAME_REQUIREMENTS)
940 requirements=URL_NAME_REQUIREMENTS)
941
941
942 rmap.connect('pullrequest_delete',
942 rmap.connect('pullrequest_delete',
943 '/{repo_name}/pull-request/{pull_request_id}',
943 '/{repo_name}/pull-request/{pull_request_id}',
944 controller='pullrequests',
944 controller='pullrequests',
945 action='delete', conditions={'function': check_repo,
945 action='delete', conditions={'function': check_repo,
946 'method': ['DELETE']},
946 'method': ['DELETE']},
947 requirements=URL_NAME_REQUIREMENTS)
947 requirements=URL_NAME_REQUIREMENTS)
948
948
949 rmap.connect('pullrequest_show_all',
949 rmap.connect('pullrequest_show_all',
950 '/{repo_name}/pull-request',
950 '/{repo_name}/pull-request',
951 controller='pullrequests',
951 controller='pullrequests',
952 action='show_all', conditions={'function': check_repo,
952 action='show_all', conditions={'function': check_repo,
953 'method': ['GET']},
953 'method': ['GET']},
954 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
954 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
955
955
956 rmap.connect('pullrequest_comment',
956 rmap.connect('pullrequest_comment',
957 '/{repo_name}/pull-request-comment/{pull_request_id}',
957 '/{repo_name}/pull-request-comment/{pull_request_id}',
958 controller='pullrequests',
958 controller='pullrequests',
959 action='comment', conditions={'function': check_repo,
959 action='comment', conditions={'function': check_repo,
960 'method': ['POST']},
960 'method': ['POST']},
961 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
961 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
962
962
963 rmap.connect('pullrequest_comment_delete',
963 rmap.connect('pullrequest_comment_delete',
964 '/{repo_name}/pull-request-comment/{comment_id}/delete',
964 '/{repo_name}/pull-request-comment/{comment_id}/delete',
965 controller='pullrequests', action='delete_comment',
965 controller='pullrequests', action='delete_comment',
966 conditions={'function': check_repo, 'method': ['DELETE']},
966 conditions={'function': check_repo, 'method': ['DELETE']},
967 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
967 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
968
968
969 rmap.connect('summary_home_explicit', '/{repo_name}/summary',
969 rmap.connect('summary_home_explicit', '/{repo_name}/summary',
970 controller='summary', conditions={'function': check_repo},
970 controller='summary', conditions={'function': check_repo},
971 requirements=URL_NAME_REQUIREMENTS)
971 requirements=URL_NAME_REQUIREMENTS)
972
972
973 rmap.connect('branches_home', '/{repo_name}/branches',
973 rmap.connect('branches_home', '/{repo_name}/branches',
974 controller='branches', conditions={'function': check_repo},
974 controller='branches', conditions={'function': check_repo},
975 requirements=URL_NAME_REQUIREMENTS)
975 requirements=URL_NAME_REQUIREMENTS)
976
976
977 rmap.connect('tags_home', '/{repo_name}/tags',
977 rmap.connect('tags_home', '/{repo_name}/tags',
978 controller='tags', conditions={'function': check_repo},
978 controller='tags', conditions={'function': check_repo},
979 requirements=URL_NAME_REQUIREMENTS)
979 requirements=URL_NAME_REQUIREMENTS)
980
980
981 rmap.connect('bookmarks_home', '/{repo_name}/bookmarks',
981 rmap.connect('bookmarks_home', '/{repo_name}/bookmarks',
982 controller='bookmarks', conditions={'function': check_repo},
982 controller='bookmarks', conditions={'function': check_repo},
983 requirements=URL_NAME_REQUIREMENTS)
983 requirements=URL_NAME_REQUIREMENTS)
984
984
985 rmap.connect('changelog_home', '/{repo_name}/changelog', jsroute=True,
985 rmap.connect('changelog_home', '/{repo_name}/changelog', jsroute=True,
986 controller='changelog', conditions={'function': check_repo},
986 controller='changelog', conditions={'function': check_repo},
987 requirements=URL_NAME_REQUIREMENTS)
987 requirements=URL_NAME_REQUIREMENTS)
988
988
989 rmap.connect('changelog_summary_home', '/{repo_name}/changelog_summary',
989 rmap.connect('changelog_summary_home', '/{repo_name}/changelog_summary',
990 controller='changelog', action='changelog_summary',
990 controller='changelog', action='changelog_summary',
991 conditions={'function': check_repo},
991 conditions={'function': check_repo},
992 requirements=URL_NAME_REQUIREMENTS)
992 requirements=URL_NAME_REQUIREMENTS)
993
993
994 rmap.connect('changelog_file_home',
994 rmap.connect('changelog_file_home',
995 '/{repo_name}/changelog/{revision}/{f_path}',
995 '/{repo_name}/changelog/{revision}/{f_path}',
996 controller='changelog', f_path=None,
996 controller='changelog', f_path=None,
997 conditions={'function': check_repo},
997 conditions={'function': check_repo},
998 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
998 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
999
999
1000 rmap.connect('changelog_details', '/{repo_name}/changelog_details/{cs}',
1000 rmap.connect('changelog_details', '/{repo_name}/changelog_details/{cs}',
1001 controller='changelog', action='changelog_details',
1001 controller='changelog', action='changelog_details',
1002 conditions={'function': check_repo},
1002 conditions={'function': check_repo},
1003 requirements=URL_NAME_REQUIREMENTS)
1003 requirements=URL_NAME_REQUIREMENTS)
1004
1004
1005 rmap.connect('files_home', '/{repo_name}/files/{revision}/{f_path}',
1005 rmap.connect('files_home', '/{repo_name}/files/{revision}/{f_path}',
1006 controller='files', revision='tip', f_path='',
1006 controller='files', revision='tip', f_path='',
1007 conditions={'function': check_repo},
1007 conditions={'function': check_repo},
1008 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1008 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1009
1009
1010 rmap.connect('files_home_simple_catchrev',
1010 rmap.connect('files_home_simple_catchrev',
1011 '/{repo_name}/files/{revision}',
1011 '/{repo_name}/files/{revision}',
1012 controller='files', revision='tip', f_path='',
1012 controller='files', revision='tip', f_path='',
1013 conditions={'function': check_repo},
1013 conditions={'function': check_repo},
1014 requirements=URL_NAME_REQUIREMENTS)
1014 requirements=URL_NAME_REQUIREMENTS)
1015
1015
1016 rmap.connect('files_home_simple_catchall',
1016 rmap.connect('files_home_simple_catchall',
1017 '/{repo_name}/files',
1017 '/{repo_name}/files',
1018 controller='files', revision='tip', f_path='',
1018 controller='files', revision='tip', f_path='',
1019 conditions={'function': check_repo},
1019 conditions={'function': check_repo},
1020 requirements=URL_NAME_REQUIREMENTS)
1020 requirements=URL_NAME_REQUIREMENTS)
1021
1021
1022 rmap.connect('files_history_home',
1022 rmap.connect('files_history_home',
1023 '/{repo_name}/history/{revision}/{f_path}',
1023 '/{repo_name}/history/{revision}/{f_path}',
1024 controller='files', action='history', revision='tip', f_path='',
1024 controller='files', action='history', revision='tip', f_path='',
1025 conditions={'function': check_repo},
1025 conditions={'function': check_repo},
1026 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1026 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1027
1027
1028 rmap.connect('files_authors_home',
1028 rmap.connect('files_authors_home',
1029 '/{repo_name}/authors/{revision}/{f_path}',
1029 '/{repo_name}/authors/{revision}/{f_path}',
1030 controller='files', action='authors', revision='tip', f_path='',
1030 controller='files', action='authors', revision='tip', f_path='',
1031 conditions={'function': check_repo},
1031 conditions={'function': check_repo},
1032 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1032 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1033
1033
1034 rmap.connect('files_diff_home', '/{repo_name}/diff/{f_path}',
1034 rmap.connect('files_diff_home', '/{repo_name}/diff/{f_path}',
1035 controller='files', action='diff', f_path='',
1035 controller='files', action='diff', f_path='',
1036 conditions={'function': check_repo},
1036 conditions={'function': check_repo},
1037 requirements=URL_NAME_REQUIREMENTS)
1037 requirements=URL_NAME_REQUIREMENTS)
1038
1038
1039 rmap.connect('files_diff_2way_home',
1039 rmap.connect('files_diff_2way_home',
1040 '/{repo_name}/diff-2way/{f_path}',
1040 '/{repo_name}/diff-2way/{f_path}',
1041 controller='files', action='diff_2way', f_path='',
1041 controller='files', action='diff_2way', f_path='',
1042 conditions={'function': check_repo},
1042 conditions={'function': check_repo},
1043 requirements=URL_NAME_REQUIREMENTS)
1043 requirements=URL_NAME_REQUIREMENTS)
1044
1044
1045 rmap.connect('files_rawfile_home',
1045 rmap.connect('files_rawfile_home',
1046 '/{repo_name}/rawfile/{revision}/{f_path}',
1046 '/{repo_name}/rawfile/{revision}/{f_path}',
1047 controller='files', action='rawfile', revision='tip',
1047 controller='files', action='rawfile', revision='tip',
1048 f_path='', conditions={'function': check_repo},
1048 f_path='', conditions={'function': check_repo},
1049 requirements=URL_NAME_REQUIREMENTS)
1049 requirements=URL_NAME_REQUIREMENTS)
1050
1050
1051 rmap.connect('files_raw_home',
1051 rmap.connect('files_raw_home',
1052 '/{repo_name}/raw/{revision}/{f_path}',
1052 '/{repo_name}/raw/{revision}/{f_path}',
1053 controller='files', action='raw', revision='tip', f_path='',
1053 controller='files', action='raw', revision='tip', f_path='',
1054 conditions={'function': check_repo},
1054 conditions={'function': check_repo},
1055 requirements=URL_NAME_REQUIREMENTS)
1055 requirements=URL_NAME_REQUIREMENTS)
1056
1056
1057 rmap.connect('files_render_home',
1057 rmap.connect('files_render_home',
1058 '/{repo_name}/render/{revision}/{f_path}',
1058 '/{repo_name}/render/{revision}/{f_path}',
1059 controller='files', action='index', revision='tip', f_path='',
1059 controller='files', action='index', revision='tip', f_path='',
1060 rendered=True, conditions={'function': check_repo},
1060 rendered=True, conditions={'function': check_repo},
1061 requirements=URL_NAME_REQUIREMENTS)
1061 requirements=URL_NAME_REQUIREMENTS)
1062
1062
1063 rmap.connect('files_annotate_home',
1063 rmap.connect('files_annotate_home',
1064 '/{repo_name}/annotate/{revision}/{f_path}',
1064 '/{repo_name}/annotate/{revision}/{f_path}',
1065 controller='files', action='index', revision='tip',
1065 controller='files', action='index', revision='tip',
1066 f_path='', annotate=True, conditions={'function': check_repo},
1066 f_path='', annotate=True, conditions={'function': check_repo},
1067 requirements=URL_NAME_REQUIREMENTS)
1067 requirements=URL_NAME_REQUIREMENTS)
1068
1068
1069 rmap.connect('files_edit',
1069 rmap.connect('files_edit',
1070 '/{repo_name}/edit/{revision}/{f_path}',
1070 '/{repo_name}/edit/{revision}/{f_path}',
1071 controller='files', action='edit', revision='tip',
1071 controller='files', action='edit', revision='tip',
1072 f_path='',
1072 f_path='',
1073 conditions={'function': check_repo, 'method': ['POST']},
1073 conditions={'function': check_repo, 'method': ['POST']},
1074 requirements=URL_NAME_REQUIREMENTS)
1074 requirements=URL_NAME_REQUIREMENTS)
1075
1075
1076 rmap.connect('files_edit_home',
1076 rmap.connect('files_edit_home',
1077 '/{repo_name}/edit/{revision}/{f_path}',
1077 '/{repo_name}/edit/{revision}/{f_path}',
1078 controller='files', action='edit_home', revision='tip',
1078 controller='files', action='edit_home', revision='tip',
1079 f_path='', conditions={'function': check_repo},
1079 f_path='', conditions={'function': check_repo},
1080 requirements=URL_NAME_REQUIREMENTS)
1080 requirements=URL_NAME_REQUIREMENTS)
1081
1081
1082 rmap.connect('files_add',
1082 rmap.connect('files_add',
1083 '/{repo_name}/add/{revision}/{f_path}',
1083 '/{repo_name}/add/{revision}/{f_path}',
1084 controller='files', action='add', revision='tip',
1084 controller='files', action='add', revision='tip',
1085 f_path='',
1085 f_path='',
1086 conditions={'function': check_repo, 'method': ['POST']},
1086 conditions={'function': check_repo, 'method': ['POST']},
1087 requirements=URL_NAME_REQUIREMENTS)
1087 requirements=URL_NAME_REQUIREMENTS)
1088
1088
1089 rmap.connect('files_add_home',
1089 rmap.connect('files_add_home',
1090 '/{repo_name}/add/{revision}/{f_path}',
1090 '/{repo_name}/add/{revision}/{f_path}',
1091 controller='files', action='add_home', revision='tip',
1091 controller='files', action='add_home', revision='tip',
1092 f_path='', conditions={'function': check_repo},
1092 f_path='', conditions={'function': check_repo},
1093 requirements=URL_NAME_REQUIREMENTS)
1093 requirements=URL_NAME_REQUIREMENTS)
1094
1094
1095 rmap.connect('files_delete',
1095 rmap.connect('files_delete',
1096 '/{repo_name}/delete/{revision}/{f_path}',
1096 '/{repo_name}/delete/{revision}/{f_path}',
1097 controller='files', action='delete', revision='tip',
1097 controller='files', action='delete', revision='tip',
1098 f_path='',
1098 f_path='',
1099 conditions={'function': check_repo, 'method': ['POST']},
1099 conditions={'function': check_repo, 'method': ['POST']},
1100 requirements=URL_NAME_REQUIREMENTS)
1100 requirements=URL_NAME_REQUIREMENTS)
1101
1101
1102 rmap.connect('files_delete_home',
1102 rmap.connect('files_delete_home',
1103 '/{repo_name}/delete/{revision}/{f_path}',
1103 '/{repo_name}/delete/{revision}/{f_path}',
1104 controller='files', action='delete_home', revision='tip',
1104 controller='files', action='delete_home', revision='tip',
1105 f_path='', conditions={'function': check_repo},
1105 f_path='', conditions={'function': check_repo},
1106 requirements=URL_NAME_REQUIREMENTS)
1106 requirements=URL_NAME_REQUIREMENTS)
1107
1107
1108 rmap.connect('files_archive_home', '/{repo_name}/archive/{fname}',
1108 rmap.connect('files_archive_home', '/{repo_name}/archive/{fname}',
1109 controller='files', action='archivefile',
1109 controller='files', action='archivefile',
1110 conditions={'function': check_repo},
1110 conditions={'function': check_repo},
1111 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1111 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1112
1112
1113 rmap.connect('files_nodelist_home',
1113 rmap.connect('files_nodelist_home',
1114 '/{repo_name}/nodelist/{revision}/{f_path}',
1114 '/{repo_name}/nodelist/{revision}/{f_path}',
1115 controller='files', action='nodelist',
1115 controller='files', action='nodelist',
1116 conditions={'function': check_repo},
1116 conditions={'function': check_repo},
1117 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1117 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1118
1118
1119 rmap.connect('files_nodetree_full',
1119 rmap.connect('files_nodetree_full',
1120 '/{repo_name}/nodetree_full/{commit_id}/{f_path}',
1120 '/{repo_name}/nodetree_full/{commit_id}/{f_path}',
1121 controller='files', action='nodetree_full',
1121 controller='files', action='nodetree_full',
1122 conditions={'function': check_repo},
1122 conditions={'function': check_repo},
1123 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1123 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1124
1124
1125 rmap.connect('repo_fork_create_home', '/{repo_name}/fork',
1125 rmap.connect('repo_fork_create_home', '/{repo_name}/fork',
1126 controller='forks', action='fork_create',
1126 controller='forks', action='fork_create',
1127 conditions={'function': check_repo, 'method': ['POST']},
1127 conditions={'function': check_repo, 'method': ['POST']},
1128 requirements=URL_NAME_REQUIREMENTS)
1128 requirements=URL_NAME_REQUIREMENTS)
1129
1129
1130 rmap.connect('repo_fork_home', '/{repo_name}/fork',
1130 rmap.connect('repo_fork_home', '/{repo_name}/fork',
1131 controller='forks', action='fork',
1131 controller='forks', action='fork',
1132 conditions={'function': check_repo},
1132 conditions={'function': check_repo},
1133 requirements=URL_NAME_REQUIREMENTS)
1133 requirements=URL_NAME_REQUIREMENTS)
1134
1134
1135 rmap.connect('repo_forks_home', '/{repo_name}/forks',
1135 rmap.connect('repo_forks_home', '/{repo_name}/forks',
1136 controller='forks', action='forks',
1136 controller='forks', action='forks',
1137 conditions={'function': check_repo},
1137 conditions={'function': check_repo},
1138 requirements=URL_NAME_REQUIREMENTS)
1138 requirements=URL_NAME_REQUIREMENTS)
1139
1139
1140 rmap.connect('repo_followers_home', '/{repo_name}/followers',
1140 rmap.connect('repo_followers_home', '/{repo_name}/followers',
1141 controller='followers', action='followers',
1141 controller='followers', action='followers',
1142 conditions={'function': check_repo},
1142 conditions={'function': check_repo},
1143 requirements=URL_NAME_REQUIREMENTS)
1143 requirements=URL_NAME_REQUIREMENTS)
1144
1144
1145 # must be here for proper group/repo catching pattern
1145 # must be here for proper group/repo catching pattern
1146 _connect_with_slash(
1146 _connect_with_slash(
1147 rmap, 'repo_group_home', '/{group_name}',
1147 rmap, 'repo_group_home', '/{group_name}',
1148 controller='home', action='index_repo_group',
1148 controller='home', action='index_repo_group',
1149 conditions={'function': check_group},
1149 conditions={'function': check_group},
1150 requirements=URL_NAME_REQUIREMENTS)
1150 requirements=URL_NAME_REQUIREMENTS)
1151
1151
1152 # catch all, at the end
1152 # catch all, at the end
1153 _connect_with_slash(
1153 _connect_with_slash(
1154 rmap, 'summary_home', '/{repo_name}', jsroute=True,
1154 rmap, 'summary_home', '/{repo_name}', jsroute=True,
1155 controller='summary', action='index',
1155 controller='summary', action='index',
1156 conditions={'function': check_repo},
1156 conditions={'function': check_repo},
1157 requirements=URL_NAME_REQUIREMENTS)
1157 requirements=URL_NAME_REQUIREMENTS)
1158
1158
1159 return rmap
1159 return rmap
1160
1160
1161
1161
1162 def _connect_with_slash(mapper, name, path, *args, **kwargs):
1162 def _connect_with_slash(mapper, name, path, *args, **kwargs):
1163 """
1163 """
1164 Connect a route with an optional trailing slash in `path`.
1164 Connect a route with an optional trailing slash in `path`.
1165 """
1165 """
1166 mapper.connect(name + '_slash', path + '/', *args, **kwargs)
1166 mapper.connect(name + '_slash', path + '/', *args, **kwargs)
1167 mapper.connect(name, path, *args, **kwargs)
1167 mapper.connect(name, path, *args, **kwargs)
@@ -1,480 +1,487 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2016 RhodeCode GmbH
3 # Copyright (C) 2011-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 User Groups crud controller for pylons
22 User Groups crud controller for pylons
23 """
23 """
24
24
25 import logging
25 import logging
26 import formencode
26 import formencode
27
27
28 import peppercorn
28 from formencode import htmlfill
29 from formencode import htmlfill
29 from pylons import request, tmpl_context as c, url, config
30 from pylons import request, tmpl_context as c, url, config
30 from pylons.controllers.util import redirect
31 from pylons.controllers.util import redirect
31 from pylons.i18n.translation import _
32 from pylons.i18n.translation import _
32
33
33 from sqlalchemy.orm import joinedload
34 from sqlalchemy.orm import joinedload
34
35
35 from rhodecode.lib import auth
36 from rhodecode.lib import auth
36 from rhodecode.lib import helpers as h
37 from rhodecode.lib import helpers as h
37 from rhodecode.lib.exceptions import UserGroupAssignedException,\
38 from rhodecode.lib.exceptions import UserGroupAssignedException,\
38 RepoGroupAssignmentError
39 RepoGroupAssignmentError
39 from rhodecode.lib.utils import jsonify, action_logger
40 from rhodecode.lib.utils import jsonify, action_logger
40 from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int
41 from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int
41 from rhodecode.lib.auth import (
42 from rhodecode.lib.auth import (
42 LoginRequired, NotAnonymous, HasUserGroupPermissionAnyDecorator,
43 LoginRequired, NotAnonymous, HasUserGroupPermissionAnyDecorator,
43 HasPermissionAnyDecorator)
44 HasPermissionAnyDecorator, XHRRequired)
44 from rhodecode.lib.base import BaseController, render
45 from rhodecode.lib.base import BaseController, render
45 from rhodecode.model.permission import PermissionModel
46 from rhodecode.model.permission import PermissionModel
46 from rhodecode.model.scm import UserGroupList
47 from rhodecode.model.scm import UserGroupList
47 from rhodecode.model.user_group import UserGroupModel
48 from rhodecode.model.user_group import UserGroupModel
48 from rhodecode.model.db import (
49 from rhodecode.model.db import (
49 User, UserGroup, UserGroupRepoToPerm, UserGroupRepoGroupToPerm)
50 User, UserGroup, UserGroupRepoToPerm, UserGroupRepoGroupToPerm)
50 from rhodecode.model.forms import (
51 from rhodecode.model.forms import (
51 UserGroupForm, UserGroupPermsForm, UserIndividualPermissionsForm,
52 UserGroupForm, UserGroupPermsForm, UserIndividualPermissionsForm,
52 UserPermissionsForm)
53 UserPermissionsForm)
53 from rhodecode.model.meta import Session
54 from rhodecode.model.meta import Session
54 from rhodecode.lib.utils import action_logger
55 from rhodecode.lib.utils import action_logger
55 from rhodecode.lib.ext_json import json
56 from rhodecode.lib.ext_json import json
56
57
57 log = logging.getLogger(__name__)
58 log = logging.getLogger(__name__)
58
59
59
60
60 class UserGroupsController(BaseController):
61 class UserGroupsController(BaseController):
61 """REST Controller styled on the Atom Publishing Protocol"""
62 """REST Controller styled on the Atom Publishing Protocol"""
62
63
63 @LoginRequired()
64 @LoginRequired()
64 def __before__(self):
65 def __before__(self):
65 super(UserGroupsController, self).__before__()
66 super(UserGroupsController, self).__before__()
66 c.available_permissions = config['available_permissions']
67 c.available_permissions = config['available_permissions']
67 PermissionModel().set_global_permission_choices(c, translator=_)
68 PermissionModel().set_global_permission_choices(c, translator=_)
68
69
69 def __load_data(self, user_group_id):
70 def __load_data(self, user_group_id):
70 c.group_members_obj = [x.user for x in c.user_group.members]
71 c.group_members_obj = [x.user for x in c.user_group.members]
71 c.group_members_obj.sort(key=lambda u: u.username.lower())
72 c.group_members_obj.sort(key=lambda u: u.username.lower())
72
73 c.group_members = [(x.user_id, x.username) for x in c.group_members_obj]
73 c.group_members = [(x.user_id, x.username) for x in c.group_members_obj]
74
74
75 c.available_members = [(x.user_id, x.username)
76 for x in User.query().all()]
77 c.available_members.sort(key=lambda u: u[1].lower())
78
79 def __load_defaults(self, user_group_id):
75 def __load_defaults(self, user_group_id):
80 """
76 """
81 Load defaults settings for edit, and update
77 Load defaults settings for edit, and update
82
78
83 :param user_group_id:
79 :param user_group_id:
84 """
80 """
85 user_group = UserGroup.get_or_404(user_group_id)
81 user_group = UserGroup.get_or_404(user_group_id)
86 data = user_group.get_dict()
82 data = user_group.get_dict()
87 # fill owner
83 # fill owner
88 if user_group.user:
84 if user_group.user:
89 data.update({'user': user_group.user.username})
85 data.update({'user': user_group.user.username})
90 else:
86 else:
91 replacement_user = User.get_first_super_admin().username
87 replacement_user = User.get_first_super_admin().username
92 data.update({'user': replacement_user})
88 data.update({'user': replacement_user})
93 return data
89 return data
94
90
95 def _revoke_perms_on_yourself(self, form_result):
91 def _revoke_perms_on_yourself(self, form_result):
96 _updates = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
92 _updates = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
97 form_result['perm_updates'])
93 form_result['perm_updates'])
98 _additions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
94 _additions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
99 form_result['perm_additions'])
95 form_result['perm_additions'])
100 _deletions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
96 _deletions = filter(lambda u: c.rhodecode_user.user_id == int(u[0]),
101 form_result['perm_deletions'])
97 form_result['perm_deletions'])
102 admin_perm = 'usergroup.admin'
98 admin_perm = 'usergroup.admin'
103 if _updates and _updates[0][1] != admin_perm or \
99 if _updates and _updates[0][1] != admin_perm or \
104 _additions and _additions[0][1] != admin_perm or \
100 _additions and _additions[0][1] != admin_perm or \
105 _deletions and _deletions[0][1] != admin_perm:
101 _deletions and _deletions[0][1] != admin_perm:
106 return True
102 return True
107 return False
103 return False
108
104
109 # permission check inside
105 # permission check inside
110 @NotAnonymous()
106 @NotAnonymous()
111 def index(self):
107 def index(self):
112 """GET /users_groups: All items in the collection"""
108 """GET /users_groups: All items in the collection"""
113 # url('users_groups')
109 # url('users_groups')
114
110
115 from rhodecode.lib.utils import PartialRenderer
111 from rhodecode.lib.utils import PartialRenderer
116 _render = PartialRenderer('data_table/_dt_elements.html')
112 _render = PartialRenderer('data_table/_dt_elements.html')
117
113
118 def user_group_name(user_group_id, user_group_name):
114 def user_group_name(user_group_id, user_group_name):
119 return _render("user_group_name", user_group_id, user_group_name)
115 return _render("user_group_name", user_group_id, user_group_name)
120
116
121 def user_group_actions(user_group_id, user_group_name):
117 def user_group_actions(user_group_id, user_group_name):
122 return _render("user_group_actions", user_group_id, user_group_name)
118 return _render("user_group_actions", user_group_id, user_group_name)
123
119
124 ## json generate
120 ## json generate
125 group_iter = UserGroupList(UserGroup.query().all(),
121 group_iter = UserGroupList(UserGroup.query().all(),
126 perm_set=['usergroup.admin'])
122 perm_set=['usergroup.admin'])
127
123
128 user_groups_data = []
124 user_groups_data = []
129 for user_gr in group_iter:
125 for user_gr in group_iter:
130 user_groups_data.append({
126 user_groups_data.append({
131 "group_name": user_group_name(
127 "group_name": user_group_name(
132 user_gr.users_group_id, h.escape(user_gr.users_group_name)),
128 user_gr.users_group_id, h.escape(user_gr.users_group_name)),
133 "group_name_raw": user_gr.users_group_name,
129 "group_name_raw": user_gr.users_group_name,
134 "desc": h.escape(user_gr.user_group_description),
130 "desc": h.escape(user_gr.user_group_description),
135 "members": len(user_gr.members),
131 "members": len(user_gr.members),
136 "active": h.bool2icon(user_gr.users_group_active),
132 "active": h.bool2icon(user_gr.users_group_active),
137 "owner": h.escape(h.link_to_user(user_gr.user.username)),
133 "owner": h.escape(h.link_to_user(user_gr.user.username)),
138 "action": user_group_actions(
134 "action": user_group_actions(
139 user_gr.users_group_id, user_gr.users_group_name)
135 user_gr.users_group_id, user_gr.users_group_name)
140 })
136 })
141
137
142 c.data = json.dumps(user_groups_data)
138 c.data = json.dumps(user_groups_data)
143 return render('admin/user_groups/user_groups.html')
139 return render('admin/user_groups/user_groups.html')
144
140
145 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
141 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
146 @auth.CSRFRequired()
142 @auth.CSRFRequired()
147 def create(self):
143 def create(self):
148 """POST /users_groups: Create a new item"""
144 """POST /users_groups: Create a new item"""
149 # url('users_groups')
145 # url('users_groups')
150
146
151 users_group_form = UserGroupForm()()
147 users_group_form = UserGroupForm()()
152 try:
148 try:
153 form_result = users_group_form.to_python(dict(request.POST))
149 form_result = users_group_form.to_python(dict(request.POST))
154 user_group = UserGroupModel().create(
150 user_group = UserGroupModel().create(
155 name=form_result['users_group_name'],
151 name=form_result['users_group_name'],
156 description=form_result['user_group_description'],
152 description=form_result['user_group_description'],
157 owner=c.rhodecode_user.user_id,
153 owner=c.rhodecode_user.user_id,
158 active=form_result['users_group_active'])
154 active=form_result['users_group_active'])
159 Session().flush()
155 Session().flush()
160
156
161 user_group_name = form_result['users_group_name']
157 user_group_name = form_result['users_group_name']
162 action_logger(c.rhodecode_user,
158 action_logger(c.rhodecode_user,
163 'admin_created_users_group:%s' % user_group_name,
159 'admin_created_users_group:%s' % user_group_name,
164 None, self.ip_addr, self.sa)
160 None, self.ip_addr, self.sa)
165 user_group_link = h.link_to(h.escape(user_group_name),
161 user_group_link = h.link_to(h.escape(user_group_name),
166 url('edit_users_group',
162 url('edit_users_group',
167 user_group_id=user_group.users_group_id))
163 user_group_id=user_group.users_group_id))
168 h.flash(h.literal(_('Created user group %(user_group_link)s')
164 h.flash(h.literal(_('Created user group %(user_group_link)s')
169 % {'user_group_link': user_group_link}),
165 % {'user_group_link': user_group_link}),
170 category='success')
166 category='success')
171 Session().commit()
167 Session().commit()
172 except formencode.Invalid as errors:
168 except formencode.Invalid as errors:
173 return htmlfill.render(
169 return htmlfill.render(
174 render('admin/user_groups/user_group_add.html'),
170 render('admin/user_groups/user_group_add.html'),
175 defaults=errors.value,
171 defaults=errors.value,
176 errors=errors.error_dict or {},
172 errors=errors.error_dict or {},
177 prefix_error=False,
173 prefix_error=False,
178 encoding="UTF-8",
174 encoding="UTF-8",
179 force_defaults=False)
175 force_defaults=False)
180 except Exception:
176 except Exception:
181 log.exception("Exception creating user group")
177 log.exception("Exception creating user group")
182 h.flash(_('Error occurred during creation of user group %s') \
178 h.flash(_('Error occurred during creation of user group %s') \
183 % request.POST.get('users_group_name'), category='error')
179 % request.POST.get('users_group_name'), category='error')
184
180
185 return redirect(
181 return redirect(
186 url('edit_users_group', user_group_id=user_group.users_group_id))
182 url('edit_users_group', user_group_id=user_group.users_group_id))
187
183
188 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
184 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
189 def new(self):
185 def new(self):
190 """GET /user_groups/new: Form to create a new item"""
186 """GET /user_groups/new: Form to create a new item"""
191 # url('new_users_group')
187 # url('new_users_group')
192 return render('admin/user_groups/user_group_add.html')
188 return render('admin/user_groups/user_group_add.html')
193
189
194 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
190 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
195 @auth.CSRFRequired()
191 @auth.CSRFRequired()
196 def update(self, user_group_id):
192 def update(self, user_group_id):
197 """PUT /user_groups/user_group_id: Update an existing item"""
193 """PUT /user_groups/user_group_id: Update an existing item"""
198 # Forms posted to this method should contain a hidden field:
194 # Forms posted to this method should contain a hidden field:
199 # <input type="hidden" name="_method" value="PUT" />
195 # <input type="hidden" name="_method" value="PUT" />
200 # Or using helpers:
196 # Or using helpers:
201 # h.form(url('users_group', user_group_id=ID),
197 # h.form(url('users_group', user_group_id=ID),
202 # method='put')
198 # method='put')
203 # url('users_group', user_group_id=ID)
199 # url('users_group', user_group_id=ID)
204
200
205 user_group_id = safe_int(user_group_id)
201 user_group_id = safe_int(user_group_id)
206 c.user_group = UserGroup.get_or_404(user_group_id)
202 c.user_group = UserGroup.get_or_404(user_group_id)
207 c.active = 'settings'
203 c.active = 'settings'
208 self.__load_data(user_group_id)
204 self.__load_data(user_group_id)
209
205
210 available_members = [safe_unicode(x[0]) for x in c.available_members]
211
212 users_group_form = UserGroupForm(
206 users_group_form = UserGroupForm(
213 edit=True, old_data=c.user_group.get_dict(),
207 edit=True, old_data=c.user_group.get_dict(), allow_disabled=True)()
214 available_members=available_members, allow_disabled=True)()
215
208
216 try:
209 try:
217 form_result = users_group_form.to_python(request.POST)
210 form_result = users_group_form.to_python(request.POST)
211 pstruct = peppercorn.parse(request.POST.items())
212 form_result['users_group_members'] = pstruct['user_group_members']
213
218 UserGroupModel().update(c.user_group, form_result)
214 UserGroupModel().update(c.user_group, form_result)
219 gr = form_result['users_group_name']
215 updated_user_group = form_result['users_group_name']
220 action_logger(c.rhodecode_user,
216 action_logger(c.rhodecode_user,
221 'admin_updated_users_group:%s' % gr,
217 'admin_updated_users_group:%s' % updated_user_group,
222 None, self.ip_addr, self.sa)
218 None, self.ip_addr, self.sa)
223 h.flash(_('Updated user group %s') % gr, category='success')
219 h.flash(_('Updated user group %s') % updated_user_group,
220 category='success')
224 Session().commit()
221 Session().commit()
225 except formencode.Invalid as errors:
222 except formencode.Invalid as errors:
226 defaults = errors.value
223 defaults = errors.value
227 e = errors.error_dict or {}
224 e = errors.error_dict or {}
228
225
229 return htmlfill.render(
226 return htmlfill.render(
230 render('admin/user_groups/user_group_edit.html'),
227 render('admin/user_groups/user_group_edit.html'),
231 defaults=defaults,
228 defaults=defaults,
232 errors=e,
229 errors=e,
233 prefix_error=False,
230 prefix_error=False,
234 encoding="UTF-8",
231 encoding="UTF-8",
235 force_defaults=False)
232 force_defaults=False)
236 except Exception:
233 except Exception:
237 log.exception("Exception during update of user group")
234 log.exception("Exception during update of user group")
238 h.flash(_('Error occurred during update of user group %s')
235 h.flash(_('Error occurred during update of user group %s')
239 % request.POST.get('users_group_name'), category='error')
236 % request.POST.get('users_group_name'), category='error')
240
237
241 return redirect(url('edit_users_group', user_group_id=user_group_id))
238 return redirect(url('edit_users_group', user_group_id=user_group_id))
242
239
243 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
240 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
244 @auth.CSRFRequired()
241 @auth.CSRFRequired()
245 def delete(self, user_group_id):
242 def delete(self, user_group_id):
246 """DELETE /user_groups/user_group_id: Delete an existing item"""
243 """DELETE /user_groups/user_group_id: Delete an existing item"""
247 # Forms posted to this method should contain a hidden field:
244 # Forms posted to this method should contain a hidden field:
248 # <input type="hidden" name="_method" value="DELETE" />
245 # <input type="hidden" name="_method" value="DELETE" />
249 # Or using helpers:
246 # Or using helpers:
250 # h.form(url('users_group', user_group_id=ID),
247 # h.form(url('users_group', user_group_id=ID),
251 # method='delete')
248 # method='delete')
252 # url('users_group', user_group_id=ID)
249 # url('users_group', user_group_id=ID)
253 user_group_id = safe_int(user_group_id)
250 user_group_id = safe_int(user_group_id)
254 c.user_group = UserGroup.get_or_404(user_group_id)
251 c.user_group = UserGroup.get_or_404(user_group_id)
255 force = str2bool(request.POST.get('force'))
252 force = str2bool(request.POST.get('force'))
256
253
257 try:
254 try:
258 UserGroupModel().delete(c.user_group, force=force)
255 UserGroupModel().delete(c.user_group, force=force)
259 Session().commit()
256 Session().commit()
260 h.flash(_('Successfully deleted user group'), category='success')
257 h.flash(_('Successfully deleted user group'), category='success')
261 except UserGroupAssignedException as e:
258 except UserGroupAssignedException as e:
262 h.flash(str(e), category='error')
259 h.flash(str(e), category='error')
263 except Exception:
260 except Exception:
264 log.exception("Exception during deletion of user group")
261 log.exception("Exception during deletion of user group")
265 h.flash(_('An error occurred during deletion of user group'),
262 h.flash(_('An error occurred during deletion of user group'),
266 category='error')
263 category='error')
267 return redirect(url('users_groups'))
264 return redirect(url('users_groups'))
268
265
269 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
266 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
270 def edit(self, user_group_id):
267 def edit(self, user_group_id):
271 """GET /user_groups/user_group_id/edit: Form to edit an existing item"""
268 """GET /user_groups/user_group_id/edit: Form to edit an existing item"""
272 # url('edit_users_group', user_group_id=ID)
269 # url('edit_users_group', user_group_id=ID)
273
270
274 user_group_id = safe_int(user_group_id)
271 user_group_id = safe_int(user_group_id)
275 c.user_group = UserGroup.get_or_404(user_group_id)
272 c.user_group = UserGroup.get_or_404(user_group_id)
276 c.active = 'settings'
273 c.active = 'settings'
277 self.__load_data(user_group_id)
274 self.__load_data(user_group_id)
278
275
279 defaults = self.__load_defaults(user_group_id)
276 defaults = self.__load_defaults(user_group_id)
280
277
281 return htmlfill.render(
278 return htmlfill.render(
282 render('admin/user_groups/user_group_edit.html'),
279 render('admin/user_groups/user_group_edit.html'),
283 defaults=defaults,
280 defaults=defaults,
284 encoding="UTF-8",
281 encoding="UTF-8",
285 force_defaults=False
282 force_defaults=False
286 )
283 )
287
284
288 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
285 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
289 def edit_perms(self, user_group_id):
286 def edit_perms(self, user_group_id):
290 user_group_id = safe_int(user_group_id)
287 user_group_id = safe_int(user_group_id)
291 c.user_group = UserGroup.get_or_404(user_group_id)
288 c.user_group = UserGroup.get_or_404(user_group_id)
292 c.active = 'perms'
289 c.active = 'perms'
293
290
294 defaults = {}
291 defaults = {}
295 # fill user group users
292 # fill user group users
296 for p in c.user_group.user_user_group_to_perm:
293 for p in c.user_group.user_user_group_to_perm:
297 defaults.update({'u_perm_%s' % p.user.user_id:
294 defaults.update({'u_perm_%s' % p.user.user_id:
298 p.permission.permission_name})
295 p.permission.permission_name})
299
296
300 for p in c.user_group.user_group_user_group_to_perm:
297 for p in c.user_group.user_group_user_group_to_perm:
301 defaults.update({'g_perm_%s' % p.user_group.users_group_id:
298 defaults.update({'g_perm_%s' % p.user_group.users_group_id:
302 p.permission.permission_name})
299 p.permission.permission_name})
303
300
304 return htmlfill.render(
301 return htmlfill.render(
305 render('admin/user_groups/user_group_edit.html'),
302 render('admin/user_groups/user_group_edit.html'),
306 defaults=defaults,
303 defaults=defaults,
307 encoding="UTF-8",
304 encoding="UTF-8",
308 force_defaults=False
305 force_defaults=False
309 )
306 )
310
307
311 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
308 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
312 @auth.CSRFRequired()
309 @auth.CSRFRequired()
313 def update_perms(self, user_group_id):
310 def update_perms(self, user_group_id):
314 """
311 """
315 grant permission for given usergroup
312 grant permission for given usergroup
316
313
317 :param user_group_id:
314 :param user_group_id:
318 """
315 """
319 user_group_id = safe_int(user_group_id)
316 user_group_id = safe_int(user_group_id)
320 c.user_group = UserGroup.get_or_404(user_group_id)
317 c.user_group = UserGroup.get_or_404(user_group_id)
321 form = UserGroupPermsForm()().to_python(request.POST)
318 form = UserGroupPermsForm()().to_python(request.POST)
322
319
323 if not c.rhodecode_user.is_admin:
320 if not c.rhodecode_user.is_admin:
324 if self._revoke_perms_on_yourself(form):
321 if self._revoke_perms_on_yourself(form):
325 msg = _('Cannot change permission for yourself as admin')
322 msg = _('Cannot change permission for yourself as admin')
326 h.flash(msg, category='warning')
323 h.flash(msg, category='warning')
327 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
324 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
328
325
329 try:
326 try:
330 UserGroupModel().update_permissions(user_group_id,
327 UserGroupModel().update_permissions(user_group_id,
331 form['perm_additions'], form['perm_updates'], form['perm_deletions'])
328 form['perm_additions'], form['perm_updates'], form['perm_deletions'])
332 except RepoGroupAssignmentError:
329 except RepoGroupAssignmentError:
333 h.flash(_('Target group cannot be the same'), category='error')
330 h.flash(_('Target group cannot be the same'), category='error')
334 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
331 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
335 #TODO: implement this
332 #TODO: implement this
336 #action_logger(c.rhodecode_user, 'admin_changed_repo_permissions',
333 #action_logger(c.rhodecode_user, 'admin_changed_repo_permissions',
337 # repo_name, self.ip_addr, self.sa)
334 # repo_name, self.ip_addr, self.sa)
338 Session().commit()
335 Session().commit()
339 h.flash(_('User Group permissions updated'), category='success')
336 h.flash(_('User Group permissions updated'), category='success')
340 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
337 return redirect(url('edit_user_group_perms', user_group_id=user_group_id))
341
338
342 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
339 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
343 def edit_perms_summary(self, user_group_id):
340 def edit_perms_summary(self, user_group_id):
344 user_group_id = safe_int(user_group_id)
341 user_group_id = safe_int(user_group_id)
345 c.user_group = UserGroup.get_or_404(user_group_id)
342 c.user_group = UserGroup.get_or_404(user_group_id)
346 c.active = 'perms_summary'
343 c.active = 'perms_summary'
347 permissions = {
344 permissions = {
348 'repositories': {},
345 'repositories': {},
349 'repositories_groups': {},
346 'repositories_groups': {},
350 }
347 }
351 ugroup_repo_perms = UserGroupRepoToPerm.query()\
348 ugroup_repo_perms = UserGroupRepoToPerm.query()\
352 .options(joinedload(UserGroupRepoToPerm.permission))\
349 .options(joinedload(UserGroupRepoToPerm.permission))\
353 .options(joinedload(UserGroupRepoToPerm.repository))\
350 .options(joinedload(UserGroupRepoToPerm.repository))\
354 .filter(UserGroupRepoToPerm.users_group_id == user_group_id)\
351 .filter(UserGroupRepoToPerm.users_group_id == user_group_id)\
355 .all()
352 .all()
356
353
357 for gr in ugroup_repo_perms:
354 for gr in ugroup_repo_perms:
358 permissions['repositories'][gr.repository.repo_name] \
355 permissions['repositories'][gr.repository.repo_name] \
359 = gr.permission.permission_name
356 = gr.permission.permission_name
360
357
361 ugroup_group_perms = UserGroupRepoGroupToPerm.query()\
358 ugroup_group_perms = UserGroupRepoGroupToPerm.query()\
362 .options(joinedload(UserGroupRepoGroupToPerm.permission))\
359 .options(joinedload(UserGroupRepoGroupToPerm.permission))\
363 .options(joinedload(UserGroupRepoGroupToPerm.group))\
360 .options(joinedload(UserGroupRepoGroupToPerm.group))\
364 .filter(UserGroupRepoGroupToPerm.users_group_id == user_group_id)\
361 .filter(UserGroupRepoGroupToPerm.users_group_id == user_group_id)\
365 .all()
362 .all()
366
363
367 for gr in ugroup_group_perms:
364 for gr in ugroup_group_perms:
368 permissions['repositories_groups'][gr.group.group_name] \
365 permissions['repositories_groups'][gr.group.group_name] \
369 = gr.permission.permission_name
366 = gr.permission.permission_name
370 c.permissions = permissions
367 c.permissions = permissions
371 return render('admin/user_groups/user_group_edit.html')
368 return render('admin/user_groups/user_group_edit.html')
372
369
373 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
370 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
374 def edit_global_perms(self, user_group_id):
371 def edit_global_perms(self, user_group_id):
375 user_group_id = safe_int(user_group_id)
372 user_group_id = safe_int(user_group_id)
376 c.user_group = UserGroup.get_or_404(user_group_id)
373 c.user_group = UserGroup.get_or_404(user_group_id)
377 c.active = 'global_perms'
374 c.active = 'global_perms'
378
375
379 c.default_user = User.get_default_user()
376 c.default_user = User.get_default_user()
380 defaults = c.user_group.get_dict()
377 defaults = c.user_group.get_dict()
381 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
378 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
382 defaults.update(c.user_group.get_default_perms())
379 defaults.update(c.user_group.get_default_perms())
383
380
384 return htmlfill.render(
381 return htmlfill.render(
385 render('admin/user_groups/user_group_edit.html'),
382 render('admin/user_groups/user_group_edit.html'),
386 defaults=defaults,
383 defaults=defaults,
387 encoding="UTF-8",
384 encoding="UTF-8",
388 force_defaults=False
385 force_defaults=False
389 )
386 )
390
387
391 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
388 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
392 @auth.CSRFRequired()
389 @auth.CSRFRequired()
393 def update_global_perms(self, user_group_id):
390 def update_global_perms(self, user_group_id):
394 """PUT /users_perm/user_group_id: Update an existing item"""
391 """PUT /users_perm/user_group_id: Update an existing item"""
395 # url('users_group_perm', user_group_id=ID, method='put')
392 # url('users_group_perm', user_group_id=ID, method='put')
396 user_group_id = safe_int(user_group_id)
393 user_group_id = safe_int(user_group_id)
397 user_group = UserGroup.get_or_404(user_group_id)
394 user_group = UserGroup.get_or_404(user_group_id)
398 c.active = 'global_perms'
395 c.active = 'global_perms'
399
396
400 try:
397 try:
401 # first stage that verifies the checkbox
398 # first stage that verifies the checkbox
402 _form = UserIndividualPermissionsForm()
399 _form = UserIndividualPermissionsForm()
403 form_result = _form.to_python(dict(request.POST))
400 form_result = _form.to_python(dict(request.POST))
404 inherit_perms = form_result['inherit_default_permissions']
401 inherit_perms = form_result['inherit_default_permissions']
405 user_group.inherit_default_permissions = inherit_perms
402 user_group.inherit_default_permissions = inherit_perms
406 Session().add(user_group)
403 Session().add(user_group)
407
404
408 if not inherit_perms:
405 if not inherit_perms:
409 # only update the individual ones if we un check the flag
406 # only update the individual ones if we un check the flag
410 _form = UserPermissionsForm(
407 _form = UserPermissionsForm(
411 [x[0] for x in c.repo_create_choices],
408 [x[0] for x in c.repo_create_choices],
412 [x[0] for x in c.repo_create_on_write_choices],
409 [x[0] for x in c.repo_create_on_write_choices],
413 [x[0] for x in c.repo_group_create_choices],
410 [x[0] for x in c.repo_group_create_choices],
414 [x[0] for x in c.user_group_create_choices],
411 [x[0] for x in c.user_group_create_choices],
415 [x[0] for x in c.fork_choices],
412 [x[0] for x in c.fork_choices],
416 [x[0] for x in c.inherit_default_permission_choices])()
413 [x[0] for x in c.inherit_default_permission_choices])()
417
414
418 form_result = _form.to_python(dict(request.POST))
415 form_result = _form.to_python(dict(request.POST))
419 form_result.update({'perm_user_group_id': user_group.users_group_id})
416 form_result.update({'perm_user_group_id': user_group.users_group_id})
420
417
421 PermissionModel().update_user_group_permissions(form_result)
418 PermissionModel().update_user_group_permissions(form_result)
422
419
423 Session().commit()
420 Session().commit()
424 h.flash(_('User Group global permissions updated successfully'),
421 h.flash(_('User Group global permissions updated successfully'),
425 category='success')
422 category='success')
426
423
427 except formencode.Invalid as errors:
424 except formencode.Invalid as errors:
428 defaults = errors.value
425 defaults = errors.value
429 c.user_group = user_group
426 c.user_group = user_group
430 return htmlfill.render(
427 return htmlfill.render(
431 render('admin/user_groups/user_group_edit.html'),
428 render('admin/user_groups/user_group_edit.html'),
432 defaults=defaults,
429 defaults=defaults,
433 errors=errors.error_dict or {},
430 errors=errors.error_dict or {},
434 prefix_error=False,
431 prefix_error=False,
435 encoding="UTF-8",
432 encoding="UTF-8",
436 force_defaults=False)
433 force_defaults=False)
437
434
438 except Exception:
435 except Exception:
439 log.exception("Exception during permissions saving")
436 log.exception("Exception during permissions saving")
440 h.flash(_('An error occurred during permissions saving'),
437 h.flash(_('An error occurred during permissions saving'),
441 category='error')
438 category='error')
442
439
443 return redirect(url('edit_user_group_global_perms', user_group_id=user_group_id))
440 return redirect(url('edit_user_group_global_perms', user_group_id=user_group_id))
444
441
445 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
442 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
446 def edit_advanced(self, user_group_id):
443 def edit_advanced(self, user_group_id):
447 user_group_id = safe_int(user_group_id)
444 user_group_id = safe_int(user_group_id)
448 c.user_group = UserGroup.get_or_404(user_group_id)
445 c.user_group = UserGroup.get_or_404(user_group_id)
449 c.active = 'advanced'
446 c.active = 'advanced'
450 c.group_members_obj = sorted(
447 c.group_members_obj = sorted(
451 (x.user for x in c.user_group.members),
448 (x.user for x in c.user_group.members),
452 key=lambda u: u.username.lower())
449 key=lambda u: u.username.lower())
453
450
454 c.group_to_repos = sorted(
451 c.group_to_repos = sorted(
455 (x.repository for x in c.user_group.users_group_repo_to_perm),
452 (x.repository for x in c.user_group.users_group_repo_to_perm),
456 key=lambda u: u.repo_name.lower())
453 key=lambda u: u.repo_name.lower())
457
454
458 c.group_to_repo_groups = sorted(
455 c.group_to_repo_groups = sorted(
459 (x.group for x in c.user_group.users_group_repo_group_to_perm),
456 (x.group for x in c.user_group.users_group_repo_group_to_perm),
460 key=lambda u: u.group_name.lower())
457 key=lambda u: u.group_name.lower())
461
458
462 return render('admin/user_groups/user_group_edit.html')
459 return render('admin/user_groups/user_group_edit.html')
463
460
464 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
461 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
465 def edit_members(self, user_group_id):
462 @XHRRequired()
463 @jsonify
464 def user_group_members(self, user_group_id):
466 user_group_id = safe_int(user_group_id)
465 user_group_id = safe_int(user_group_id)
467 c.user_group = UserGroup.get_or_404(user_group_id)
466 user_group = UserGroup.get_or_404(user_group_id)
468 c.active = 'members'
467 group_members_obj = sorted((x.user for x in user_group.members),
469 c.group_members_obj = sorted((x.user for x in c.user_group.members),
470 key=lambda u: u.username.lower())
468 key=lambda u: u.username.lower())
471
469
472 group_members = [(x.user_id, x.username) for x in c.group_members_obj]
470 group_members = [
471 {
472 'id': user.user_id,
473 'first_name': user.name,
474 'last_name': user.lastname,
475 'username': user.username,
476 'icon_link': h.gravatar_url(user.email, 30),
477 'value_display': h.person(user.email),
478 'value': user.username,
479 'value_type': 'user',
480 'active': user.active,
481 }
482 for user in group_members_obj
483 ]
473
484
474 if request.is_xhr:
485 return {
475 return jsonify(lambda *a, **k: {
476 'members': group_members
486 'members': group_members
477 })
487 }
478
479 c.group_members = group_members
480 return render('admin/user_groups/user_group_edit.html')
@@ -1,553 +1,547 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 this is forms validation classes
22 this is forms validation classes
23 http://formencode.org/module-formencode.validators.html
23 http://formencode.org/module-formencode.validators.html
24 for list off all availible validators
24 for list off all availible validators
25
25
26 we can create our own validators
26 we can create our own validators
27
27
28 The table below outlines the options which can be used in a schema in addition to the validators themselves
28 The table below outlines the options which can be used in a schema in addition to the validators themselves
29 pre_validators [] These validators will be applied before the schema
29 pre_validators [] These validators will be applied before the schema
30 chained_validators [] These validators will be applied after the schema
30 chained_validators [] These validators will be applied after the schema
31 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
31 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
32 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
32 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
33 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
33 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
34 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
34 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
35
35
36
36
37 <name> = formencode.validators.<name of validator>
37 <name> = formencode.validators.<name of validator>
38 <name> must equal form name
38 <name> must equal form name
39 list=[1,2,3,4,5]
39 list=[1,2,3,4,5]
40 for SELECT use formencode.All(OneOf(list), Int())
40 for SELECT use formencode.All(OneOf(list), Int())
41
41
42 """
42 """
43
43
44 import deform
44 import deform
45 import logging
45 import logging
46 import formencode
46 import formencode
47
47
48 from pkg_resources import resource_filename
48 from pkg_resources import resource_filename
49 from formencode import All, Pipe
49 from formencode import All, Pipe
50
50
51 from pylons.i18n.translation import _
51 from pylons.i18n.translation import _
52
52
53 from rhodecode import BACKENDS
53 from rhodecode import BACKENDS
54 from rhodecode.lib import helpers
54 from rhodecode.lib import helpers
55 from rhodecode.model import validators as v
55 from rhodecode.model import validators as v
56
56
57 log = logging.getLogger(__name__)
57 log = logging.getLogger(__name__)
58
58
59
59
60 deform_templates = resource_filename('deform', 'templates')
60 deform_templates = resource_filename('deform', 'templates')
61 rhodecode_templates = resource_filename('rhodecode', 'templates/forms')
61 rhodecode_templates = resource_filename('rhodecode', 'templates/forms')
62 search_path = (rhodecode_templates, deform_templates)
62 search_path = (rhodecode_templates, deform_templates)
63
63
64
64
65 class RhodecodeFormZPTRendererFactory(deform.ZPTRendererFactory):
65 class RhodecodeFormZPTRendererFactory(deform.ZPTRendererFactory):
66 """ Subclass of ZPTRendererFactory to add rhodecode context variables """
66 """ Subclass of ZPTRendererFactory to add rhodecode context variables """
67 def __call__(self, template_name, **kw):
67 def __call__(self, template_name, **kw):
68 kw['h'] = helpers
68 kw['h'] = helpers
69 return self.load(template_name)(**kw)
69 return self.load(template_name)(**kw)
70
70
71
71
72 form_renderer = RhodecodeFormZPTRendererFactory(search_path)
72 form_renderer = RhodecodeFormZPTRendererFactory(search_path)
73 deform.Form.set_default_renderer(form_renderer)
73 deform.Form.set_default_renderer(form_renderer)
74
74
75
75
76 def LoginForm():
76 def LoginForm():
77 class _LoginForm(formencode.Schema):
77 class _LoginForm(formencode.Schema):
78 allow_extra_fields = True
78 allow_extra_fields = True
79 filter_extra_fields = True
79 filter_extra_fields = True
80 username = v.UnicodeString(
80 username = v.UnicodeString(
81 strip=True,
81 strip=True,
82 min=1,
82 min=1,
83 not_empty=True,
83 not_empty=True,
84 messages={
84 messages={
85 'empty': _(u'Please enter a login'),
85 'empty': _(u'Please enter a login'),
86 'tooShort': _(u'Enter a value %(min)i characters long or more')
86 'tooShort': _(u'Enter a value %(min)i characters long or more')
87 }
87 }
88 )
88 )
89
89
90 password = v.UnicodeString(
90 password = v.UnicodeString(
91 strip=False,
91 strip=False,
92 min=3,
92 min=3,
93 not_empty=True,
93 not_empty=True,
94 messages={
94 messages={
95 'empty': _(u'Please enter a password'),
95 'empty': _(u'Please enter a password'),
96 'tooShort': _(u'Enter %(min)i characters or more')}
96 'tooShort': _(u'Enter %(min)i characters or more')}
97 )
97 )
98
98
99 remember = v.StringBoolean(if_missing=False)
99 remember = v.StringBoolean(if_missing=False)
100
100
101 chained_validators = [v.ValidAuth()]
101 chained_validators = [v.ValidAuth()]
102 return _LoginForm
102 return _LoginForm
103
103
104
104
105 def UserForm(edit=False, available_languages=[], old_data={}):
105 def UserForm(edit=False, available_languages=[], old_data={}):
106 class _UserForm(formencode.Schema):
106 class _UserForm(formencode.Schema):
107 allow_extra_fields = True
107 allow_extra_fields = True
108 filter_extra_fields = True
108 filter_extra_fields = True
109 username = All(v.UnicodeString(strip=True, min=1, not_empty=True),
109 username = All(v.UnicodeString(strip=True, min=1, not_empty=True),
110 v.ValidUsername(edit, old_data))
110 v.ValidUsername(edit, old_data))
111 if edit:
111 if edit:
112 new_password = All(
112 new_password = All(
113 v.ValidPassword(),
113 v.ValidPassword(),
114 v.UnicodeString(strip=False, min=6, not_empty=False)
114 v.UnicodeString(strip=False, min=6, not_empty=False)
115 )
115 )
116 password_confirmation = All(
116 password_confirmation = All(
117 v.ValidPassword(),
117 v.ValidPassword(),
118 v.UnicodeString(strip=False, min=6, not_empty=False),
118 v.UnicodeString(strip=False, min=6, not_empty=False),
119 )
119 )
120 admin = v.StringBoolean(if_missing=False)
120 admin = v.StringBoolean(if_missing=False)
121 else:
121 else:
122 password = All(
122 password = All(
123 v.ValidPassword(),
123 v.ValidPassword(),
124 v.UnicodeString(strip=False, min=6, not_empty=True)
124 v.UnicodeString(strip=False, min=6, not_empty=True)
125 )
125 )
126 password_confirmation = All(
126 password_confirmation = All(
127 v.ValidPassword(),
127 v.ValidPassword(),
128 v.UnicodeString(strip=False, min=6, not_empty=False)
128 v.UnicodeString(strip=False, min=6, not_empty=False)
129 )
129 )
130
130
131 password_change = v.StringBoolean(if_missing=False)
131 password_change = v.StringBoolean(if_missing=False)
132 create_repo_group = v.StringBoolean(if_missing=False)
132 create_repo_group = v.StringBoolean(if_missing=False)
133
133
134 active = v.StringBoolean(if_missing=False)
134 active = v.StringBoolean(if_missing=False)
135 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
135 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
136 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
136 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
137 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
137 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
138 extern_name = v.UnicodeString(strip=True)
138 extern_name = v.UnicodeString(strip=True)
139 extern_type = v.UnicodeString(strip=True)
139 extern_type = v.UnicodeString(strip=True)
140 language = v.OneOf(available_languages, hideList=False,
140 language = v.OneOf(available_languages, hideList=False,
141 testValueList=True, if_missing=None)
141 testValueList=True, if_missing=None)
142 chained_validators = [v.ValidPasswordsMatch()]
142 chained_validators = [v.ValidPasswordsMatch()]
143 return _UserForm
143 return _UserForm
144
144
145
145
146 def UserGroupForm(edit=False, old_data=None, available_members=None,
146 def UserGroupForm(edit=False, old_data=None, allow_disabled=False):
147 allow_disabled=False):
148 old_data = old_data or {}
147 old_data = old_data or {}
149 available_members = available_members or []
150
148
151 class _UserGroupForm(formencode.Schema):
149 class _UserGroupForm(formencode.Schema):
152 allow_extra_fields = True
150 allow_extra_fields = True
153 filter_extra_fields = True
151 filter_extra_fields = True
154
152
155 users_group_name = All(
153 users_group_name = All(
156 v.UnicodeString(strip=True, min=1, not_empty=True),
154 v.UnicodeString(strip=True, min=1, not_empty=True),
157 v.ValidUserGroup(edit, old_data)
155 v.ValidUserGroup(edit, old_data)
158 )
156 )
159 user_group_description = v.UnicodeString(strip=True, min=1,
157 user_group_description = v.UnicodeString(strip=True, min=1,
160 not_empty=False)
158 not_empty=False)
161
159
162 users_group_active = v.StringBoolean(if_missing=False)
160 users_group_active = v.StringBoolean(if_missing=False)
163
161
164 if edit:
162 if edit:
165 users_group_members = v.OneOf(
166 available_members, hideList=False, testValueList=True,
167 if_missing=None, not_empty=False
168 )
169 # this is user group owner
163 # this is user group owner
170 user = All(
164 user = All(
171 v.UnicodeString(not_empty=True),
165 v.UnicodeString(not_empty=True),
172 v.ValidRepoUser(allow_disabled))
166 v.ValidRepoUser(allow_disabled))
173 return _UserGroupForm
167 return _UserGroupForm
174
168
175
169
176 def RepoGroupForm(edit=False, old_data=None, available_groups=None,
170 def RepoGroupForm(edit=False, old_data=None, available_groups=None,
177 can_create_in_root=False, allow_disabled=False):
171 can_create_in_root=False, allow_disabled=False):
178 old_data = old_data or {}
172 old_data = old_data or {}
179 available_groups = available_groups or []
173 available_groups = available_groups or []
180
174
181 class _RepoGroupForm(formencode.Schema):
175 class _RepoGroupForm(formencode.Schema):
182 allow_extra_fields = True
176 allow_extra_fields = True
183 filter_extra_fields = False
177 filter_extra_fields = False
184
178
185 group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
179 group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
186 v.SlugifyName(),)
180 v.SlugifyName(),)
187 group_description = v.UnicodeString(strip=True, min=1,
181 group_description = v.UnicodeString(strip=True, min=1,
188 not_empty=False)
182 not_empty=False)
189 group_copy_permissions = v.StringBoolean(if_missing=False)
183 group_copy_permissions = v.StringBoolean(if_missing=False)
190
184
191 group_parent_id = v.OneOf(available_groups, hideList=False,
185 group_parent_id = v.OneOf(available_groups, hideList=False,
192 testValueList=True, not_empty=True)
186 testValueList=True, not_empty=True)
193 enable_locking = v.StringBoolean(if_missing=False)
187 enable_locking = v.StringBoolean(if_missing=False)
194 chained_validators = [
188 chained_validators = [
195 v.ValidRepoGroup(edit, old_data, can_create_in_root)]
189 v.ValidRepoGroup(edit, old_data, can_create_in_root)]
196
190
197 if edit:
191 if edit:
198 # this is repo group owner
192 # this is repo group owner
199 user = All(
193 user = All(
200 v.UnicodeString(not_empty=True),
194 v.UnicodeString(not_empty=True),
201 v.ValidRepoUser(allow_disabled))
195 v.ValidRepoUser(allow_disabled))
202
196
203 return _RepoGroupForm
197 return _RepoGroupForm
204
198
205
199
206 def RegisterForm(edit=False, old_data={}):
200 def RegisterForm(edit=False, old_data={}):
207 class _RegisterForm(formencode.Schema):
201 class _RegisterForm(formencode.Schema):
208 allow_extra_fields = True
202 allow_extra_fields = True
209 filter_extra_fields = True
203 filter_extra_fields = True
210 username = All(
204 username = All(
211 v.ValidUsername(edit, old_data),
205 v.ValidUsername(edit, old_data),
212 v.UnicodeString(strip=True, min=1, not_empty=True)
206 v.UnicodeString(strip=True, min=1, not_empty=True)
213 )
207 )
214 password = All(
208 password = All(
215 v.ValidPassword(),
209 v.ValidPassword(),
216 v.UnicodeString(strip=False, min=6, not_empty=True)
210 v.UnicodeString(strip=False, min=6, not_empty=True)
217 )
211 )
218 password_confirmation = All(
212 password_confirmation = All(
219 v.ValidPassword(),
213 v.ValidPassword(),
220 v.UnicodeString(strip=False, min=6, not_empty=True)
214 v.UnicodeString(strip=False, min=6, not_empty=True)
221 )
215 )
222 active = v.StringBoolean(if_missing=False)
216 active = v.StringBoolean(if_missing=False)
223 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
217 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
224 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
218 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
225 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
219 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
226
220
227 chained_validators = [v.ValidPasswordsMatch()]
221 chained_validators = [v.ValidPasswordsMatch()]
228
222
229 return _RegisterForm
223 return _RegisterForm
230
224
231
225
232 def PasswordResetForm():
226 def PasswordResetForm():
233 class _PasswordResetForm(formencode.Schema):
227 class _PasswordResetForm(formencode.Schema):
234 allow_extra_fields = True
228 allow_extra_fields = True
235 filter_extra_fields = True
229 filter_extra_fields = True
236 email = All(v.ValidSystemEmail(), v.Email(not_empty=True))
230 email = All(v.ValidSystemEmail(), v.Email(not_empty=True))
237 return _PasswordResetForm
231 return _PasswordResetForm
238
232
239
233
240 def RepoForm(edit=False, old_data=None, repo_groups=None, landing_revs=None,
234 def RepoForm(edit=False, old_data=None, repo_groups=None, landing_revs=None,
241 allow_disabled=False):
235 allow_disabled=False):
242 old_data = old_data or {}
236 old_data = old_data or {}
243 repo_groups = repo_groups or []
237 repo_groups = repo_groups or []
244 landing_revs = landing_revs or []
238 landing_revs = landing_revs or []
245 supported_backends = BACKENDS.keys()
239 supported_backends = BACKENDS.keys()
246
240
247 class _RepoForm(formencode.Schema):
241 class _RepoForm(formencode.Schema):
248 allow_extra_fields = True
242 allow_extra_fields = True
249 filter_extra_fields = False
243 filter_extra_fields = False
250 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
244 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
251 v.SlugifyName())
245 v.SlugifyName())
252 repo_group = All(v.CanWriteGroup(old_data),
246 repo_group = All(v.CanWriteGroup(old_data),
253 v.OneOf(repo_groups, hideList=True))
247 v.OneOf(repo_groups, hideList=True))
254 repo_type = v.OneOf(supported_backends, required=False,
248 repo_type = v.OneOf(supported_backends, required=False,
255 if_missing=old_data.get('repo_type'))
249 if_missing=old_data.get('repo_type'))
256 repo_description = v.UnicodeString(strip=True, min=1, not_empty=False)
250 repo_description = v.UnicodeString(strip=True, min=1, not_empty=False)
257 repo_private = v.StringBoolean(if_missing=False)
251 repo_private = v.StringBoolean(if_missing=False)
258 repo_landing_rev = v.OneOf(landing_revs, hideList=True)
252 repo_landing_rev = v.OneOf(landing_revs, hideList=True)
259 repo_copy_permissions = v.StringBoolean(if_missing=False)
253 repo_copy_permissions = v.StringBoolean(if_missing=False)
260 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
254 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
261
255
262 repo_enable_statistics = v.StringBoolean(if_missing=False)
256 repo_enable_statistics = v.StringBoolean(if_missing=False)
263 repo_enable_downloads = v.StringBoolean(if_missing=False)
257 repo_enable_downloads = v.StringBoolean(if_missing=False)
264 repo_enable_locking = v.StringBoolean(if_missing=False)
258 repo_enable_locking = v.StringBoolean(if_missing=False)
265
259
266 if edit:
260 if edit:
267 # this is repo owner
261 # this is repo owner
268 user = All(
262 user = All(
269 v.UnicodeString(not_empty=True),
263 v.UnicodeString(not_empty=True),
270 v.ValidRepoUser(allow_disabled))
264 v.ValidRepoUser(allow_disabled))
271 clone_uri_change = v.UnicodeString(
265 clone_uri_change = v.UnicodeString(
272 not_empty=False, if_missing=v.Missing)
266 not_empty=False, if_missing=v.Missing)
273
267
274 chained_validators = [v.ValidCloneUri(),
268 chained_validators = [v.ValidCloneUri(),
275 v.ValidRepoName(edit, old_data)]
269 v.ValidRepoName(edit, old_data)]
276 return _RepoForm
270 return _RepoForm
277
271
278
272
279 def RepoPermsForm():
273 def RepoPermsForm():
280 class _RepoPermsForm(formencode.Schema):
274 class _RepoPermsForm(formencode.Schema):
281 allow_extra_fields = True
275 allow_extra_fields = True
282 filter_extra_fields = False
276 filter_extra_fields = False
283 chained_validators = [v.ValidPerms(type_='repo')]
277 chained_validators = [v.ValidPerms(type_='repo')]
284 return _RepoPermsForm
278 return _RepoPermsForm
285
279
286
280
287 def RepoGroupPermsForm(valid_recursive_choices):
281 def RepoGroupPermsForm(valid_recursive_choices):
288 class _RepoGroupPermsForm(formencode.Schema):
282 class _RepoGroupPermsForm(formencode.Schema):
289 allow_extra_fields = True
283 allow_extra_fields = True
290 filter_extra_fields = False
284 filter_extra_fields = False
291 recursive = v.OneOf(valid_recursive_choices)
285 recursive = v.OneOf(valid_recursive_choices)
292 chained_validators = [v.ValidPerms(type_='repo_group')]
286 chained_validators = [v.ValidPerms(type_='repo_group')]
293 return _RepoGroupPermsForm
287 return _RepoGroupPermsForm
294
288
295
289
296 def UserGroupPermsForm():
290 def UserGroupPermsForm():
297 class _UserPermsForm(formencode.Schema):
291 class _UserPermsForm(formencode.Schema):
298 allow_extra_fields = True
292 allow_extra_fields = True
299 filter_extra_fields = False
293 filter_extra_fields = False
300 chained_validators = [v.ValidPerms(type_='user_group')]
294 chained_validators = [v.ValidPerms(type_='user_group')]
301 return _UserPermsForm
295 return _UserPermsForm
302
296
303
297
304 def RepoFieldForm():
298 def RepoFieldForm():
305 class _RepoFieldForm(formencode.Schema):
299 class _RepoFieldForm(formencode.Schema):
306 filter_extra_fields = True
300 filter_extra_fields = True
307 allow_extra_fields = True
301 allow_extra_fields = True
308
302
309 new_field_key = All(v.FieldKey(),
303 new_field_key = All(v.FieldKey(),
310 v.UnicodeString(strip=True, min=3, not_empty=True))
304 v.UnicodeString(strip=True, min=3, not_empty=True))
311 new_field_value = v.UnicodeString(not_empty=False, if_missing=u'')
305 new_field_value = v.UnicodeString(not_empty=False, if_missing=u'')
312 new_field_type = v.OneOf(['str', 'unicode', 'list', 'tuple'],
306 new_field_type = v.OneOf(['str', 'unicode', 'list', 'tuple'],
313 if_missing='str')
307 if_missing='str')
314 new_field_label = v.UnicodeString(not_empty=False)
308 new_field_label = v.UnicodeString(not_empty=False)
315 new_field_desc = v.UnicodeString(not_empty=False)
309 new_field_desc = v.UnicodeString(not_empty=False)
316
310
317 return _RepoFieldForm
311 return _RepoFieldForm
318
312
319
313
320 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
314 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
321 repo_groups=[], landing_revs=[]):
315 repo_groups=[], landing_revs=[]):
322 class _RepoForkForm(formencode.Schema):
316 class _RepoForkForm(formencode.Schema):
323 allow_extra_fields = True
317 allow_extra_fields = True
324 filter_extra_fields = False
318 filter_extra_fields = False
325 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
319 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
326 v.SlugifyName())
320 v.SlugifyName())
327 repo_group = All(v.CanWriteGroup(),
321 repo_group = All(v.CanWriteGroup(),
328 v.OneOf(repo_groups, hideList=True))
322 v.OneOf(repo_groups, hideList=True))
329 repo_type = All(v.ValidForkType(old_data), v.OneOf(supported_backends))
323 repo_type = All(v.ValidForkType(old_data), v.OneOf(supported_backends))
330 description = v.UnicodeString(strip=True, min=1, not_empty=True)
324 description = v.UnicodeString(strip=True, min=1, not_empty=True)
331 private = v.StringBoolean(if_missing=False)
325 private = v.StringBoolean(if_missing=False)
332 copy_permissions = v.StringBoolean(if_missing=False)
326 copy_permissions = v.StringBoolean(if_missing=False)
333 fork_parent_id = v.UnicodeString()
327 fork_parent_id = v.UnicodeString()
334 chained_validators = [v.ValidForkName(edit, old_data)]
328 chained_validators = [v.ValidForkName(edit, old_data)]
335 landing_rev = v.OneOf(landing_revs, hideList=True)
329 landing_rev = v.OneOf(landing_revs, hideList=True)
336
330
337 return _RepoForkForm
331 return _RepoForkForm
338
332
339
333
340 def ApplicationSettingsForm():
334 def ApplicationSettingsForm():
341 class _ApplicationSettingsForm(formencode.Schema):
335 class _ApplicationSettingsForm(formencode.Schema):
342 allow_extra_fields = True
336 allow_extra_fields = True
343 filter_extra_fields = False
337 filter_extra_fields = False
344 rhodecode_title = v.UnicodeString(strip=True, max=40, not_empty=False)
338 rhodecode_title = v.UnicodeString(strip=True, max=40, not_empty=False)
345 rhodecode_realm = v.UnicodeString(strip=True, min=1, not_empty=True)
339 rhodecode_realm = v.UnicodeString(strip=True, min=1, not_empty=True)
346 rhodecode_pre_code = v.UnicodeString(strip=True, min=1, not_empty=False)
340 rhodecode_pre_code = v.UnicodeString(strip=True, min=1, not_empty=False)
347 rhodecode_post_code = v.UnicodeString(strip=True, min=1, not_empty=False)
341 rhodecode_post_code = v.UnicodeString(strip=True, min=1, not_empty=False)
348 rhodecode_captcha_public_key = v.UnicodeString(strip=True, min=1, not_empty=False)
342 rhodecode_captcha_public_key = v.UnicodeString(strip=True, min=1, not_empty=False)
349 rhodecode_captcha_private_key = v.UnicodeString(strip=True, min=1, not_empty=False)
343 rhodecode_captcha_private_key = v.UnicodeString(strip=True, min=1, not_empty=False)
350
344
351 return _ApplicationSettingsForm
345 return _ApplicationSettingsForm
352
346
353
347
354 def ApplicationVisualisationForm():
348 def ApplicationVisualisationForm():
355 class _ApplicationVisualisationForm(formencode.Schema):
349 class _ApplicationVisualisationForm(formencode.Schema):
356 allow_extra_fields = True
350 allow_extra_fields = True
357 filter_extra_fields = False
351 filter_extra_fields = False
358 rhodecode_show_public_icon = v.StringBoolean(if_missing=False)
352 rhodecode_show_public_icon = v.StringBoolean(if_missing=False)
359 rhodecode_show_private_icon = v.StringBoolean(if_missing=False)
353 rhodecode_show_private_icon = v.StringBoolean(if_missing=False)
360 rhodecode_stylify_metatags = v.StringBoolean(if_missing=False)
354 rhodecode_stylify_metatags = v.StringBoolean(if_missing=False)
361
355
362 rhodecode_repository_fields = v.StringBoolean(if_missing=False)
356 rhodecode_repository_fields = v.StringBoolean(if_missing=False)
363 rhodecode_lightweight_journal = v.StringBoolean(if_missing=False)
357 rhodecode_lightweight_journal = v.StringBoolean(if_missing=False)
364 rhodecode_dashboard_items = v.Int(min=5, not_empty=True)
358 rhodecode_dashboard_items = v.Int(min=5, not_empty=True)
365 rhodecode_admin_grid_items = v.Int(min=5, not_empty=True)
359 rhodecode_admin_grid_items = v.Int(min=5, not_empty=True)
366 rhodecode_show_version = v.StringBoolean(if_missing=False)
360 rhodecode_show_version = v.StringBoolean(if_missing=False)
367 rhodecode_use_gravatar = v.StringBoolean(if_missing=False)
361 rhodecode_use_gravatar = v.StringBoolean(if_missing=False)
368 rhodecode_markup_renderer = v.OneOf(['markdown', 'rst'])
362 rhodecode_markup_renderer = v.OneOf(['markdown', 'rst'])
369 rhodecode_gravatar_url = v.UnicodeString(min=3)
363 rhodecode_gravatar_url = v.UnicodeString(min=3)
370 rhodecode_clone_uri_tmpl = v.UnicodeString(min=3)
364 rhodecode_clone_uri_tmpl = v.UnicodeString(min=3)
371 rhodecode_support_url = v.UnicodeString()
365 rhodecode_support_url = v.UnicodeString()
372 rhodecode_show_revision_number = v.StringBoolean(if_missing=False)
366 rhodecode_show_revision_number = v.StringBoolean(if_missing=False)
373 rhodecode_show_sha_length = v.Int(min=4, not_empty=True)
367 rhodecode_show_sha_length = v.Int(min=4, not_empty=True)
374
368
375 return _ApplicationVisualisationForm
369 return _ApplicationVisualisationForm
376
370
377
371
378 class _BaseVcsSettingsForm(formencode.Schema):
372 class _BaseVcsSettingsForm(formencode.Schema):
379 allow_extra_fields = True
373 allow_extra_fields = True
380 filter_extra_fields = False
374 filter_extra_fields = False
381 hooks_changegroup_repo_size = v.StringBoolean(if_missing=False)
375 hooks_changegroup_repo_size = v.StringBoolean(if_missing=False)
382 hooks_changegroup_push_logger = v.StringBoolean(if_missing=False)
376 hooks_changegroup_push_logger = v.StringBoolean(if_missing=False)
383 hooks_outgoing_pull_logger = v.StringBoolean(if_missing=False)
377 hooks_outgoing_pull_logger = v.StringBoolean(if_missing=False)
384
378
385 extensions_largefiles = v.StringBoolean(if_missing=False)
379 extensions_largefiles = v.StringBoolean(if_missing=False)
386 phases_publish = v.StringBoolean(if_missing=False)
380 phases_publish = v.StringBoolean(if_missing=False)
387
381
388 rhodecode_pr_merge_enabled = v.StringBoolean(if_missing=False)
382 rhodecode_pr_merge_enabled = v.StringBoolean(if_missing=False)
389 rhodecode_use_outdated_comments = v.StringBoolean(if_missing=False)
383 rhodecode_use_outdated_comments = v.StringBoolean(if_missing=False)
390 rhodecode_hg_use_rebase_for_merging = v.StringBoolean(if_missing=False)
384 rhodecode_hg_use_rebase_for_merging = v.StringBoolean(if_missing=False)
391
385
392 vcs_svn_proxy_http_requests_enabled = v.StringBoolean(if_missing=False)
386 vcs_svn_proxy_http_requests_enabled = v.StringBoolean(if_missing=False)
393 vcs_svn_proxy_http_server_url = v.UnicodeString(strip=True, if_missing=None)
387 vcs_svn_proxy_http_server_url = v.UnicodeString(strip=True, if_missing=None)
394
388
395
389
396 def ApplicationUiSettingsForm():
390 def ApplicationUiSettingsForm():
397 class _ApplicationUiSettingsForm(_BaseVcsSettingsForm):
391 class _ApplicationUiSettingsForm(_BaseVcsSettingsForm):
398 web_push_ssl = v.StringBoolean(if_missing=False)
392 web_push_ssl = v.StringBoolean(if_missing=False)
399 paths_root_path = All(
393 paths_root_path = All(
400 v.ValidPath(),
394 v.ValidPath(),
401 v.UnicodeString(strip=True, min=1, not_empty=True)
395 v.UnicodeString(strip=True, min=1, not_empty=True)
402 )
396 )
403 extensions_hgsubversion = v.StringBoolean(if_missing=False)
397 extensions_hgsubversion = v.StringBoolean(if_missing=False)
404 extensions_hggit = v.StringBoolean(if_missing=False)
398 extensions_hggit = v.StringBoolean(if_missing=False)
405 new_svn_branch = v.ValidSvnPattern(section='vcs_svn_branch')
399 new_svn_branch = v.ValidSvnPattern(section='vcs_svn_branch')
406 new_svn_tag = v.ValidSvnPattern(section='vcs_svn_tag')
400 new_svn_tag = v.ValidSvnPattern(section='vcs_svn_tag')
407
401
408 return _ApplicationUiSettingsForm
402 return _ApplicationUiSettingsForm
409
403
410
404
411 def RepoVcsSettingsForm(repo_name):
405 def RepoVcsSettingsForm(repo_name):
412 class _RepoVcsSettingsForm(_BaseVcsSettingsForm):
406 class _RepoVcsSettingsForm(_BaseVcsSettingsForm):
413 inherit_global_settings = v.StringBoolean(if_missing=False)
407 inherit_global_settings = v.StringBoolean(if_missing=False)
414 new_svn_branch = v.ValidSvnPattern(
408 new_svn_branch = v.ValidSvnPattern(
415 section='vcs_svn_branch', repo_name=repo_name)
409 section='vcs_svn_branch', repo_name=repo_name)
416 new_svn_tag = v.ValidSvnPattern(
410 new_svn_tag = v.ValidSvnPattern(
417 section='vcs_svn_tag', repo_name=repo_name)
411 section='vcs_svn_tag', repo_name=repo_name)
418
412
419 return _RepoVcsSettingsForm
413 return _RepoVcsSettingsForm
420
414
421
415
422 def LabsSettingsForm():
416 def LabsSettingsForm():
423 class _LabSettingsForm(formencode.Schema):
417 class _LabSettingsForm(formencode.Schema):
424 allow_extra_fields = True
418 allow_extra_fields = True
425 filter_extra_fields = False
419 filter_extra_fields = False
426
420
427 return _LabSettingsForm
421 return _LabSettingsForm
428
422
429
423
430 def ApplicationPermissionsForm(
424 def ApplicationPermissionsForm(
431 register_choices, password_reset_choices, extern_activate_choices):
425 register_choices, password_reset_choices, extern_activate_choices):
432 class _DefaultPermissionsForm(formencode.Schema):
426 class _DefaultPermissionsForm(formencode.Schema):
433 allow_extra_fields = True
427 allow_extra_fields = True
434 filter_extra_fields = True
428 filter_extra_fields = True
435
429
436 anonymous = v.StringBoolean(if_missing=False)
430 anonymous = v.StringBoolean(if_missing=False)
437 default_register = v.OneOf(register_choices)
431 default_register = v.OneOf(register_choices)
438 default_register_message = v.UnicodeString()
432 default_register_message = v.UnicodeString()
439 default_password_reset = v.OneOf(password_reset_choices)
433 default_password_reset = v.OneOf(password_reset_choices)
440 default_extern_activate = v.OneOf(extern_activate_choices)
434 default_extern_activate = v.OneOf(extern_activate_choices)
441
435
442 return _DefaultPermissionsForm
436 return _DefaultPermissionsForm
443
437
444
438
445 def ObjectPermissionsForm(repo_perms_choices, group_perms_choices,
439 def ObjectPermissionsForm(repo_perms_choices, group_perms_choices,
446 user_group_perms_choices):
440 user_group_perms_choices):
447 class _ObjectPermissionsForm(formencode.Schema):
441 class _ObjectPermissionsForm(formencode.Schema):
448 allow_extra_fields = True
442 allow_extra_fields = True
449 filter_extra_fields = True
443 filter_extra_fields = True
450 overwrite_default_repo = v.StringBoolean(if_missing=False)
444 overwrite_default_repo = v.StringBoolean(if_missing=False)
451 overwrite_default_group = v.StringBoolean(if_missing=False)
445 overwrite_default_group = v.StringBoolean(if_missing=False)
452 overwrite_default_user_group = v.StringBoolean(if_missing=False)
446 overwrite_default_user_group = v.StringBoolean(if_missing=False)
453 default_repo_perm = v.OneOf(repo_perms_choices)
447 default_repo_perm = v.OneOf(repo_perms_choices)
454 default_group_perm = v.OneOf(group_perms_choices)
448 default_group_perm = v.OneOf(group_perms_choices)
455 default_user_group_perm = v.OneOf(user_group_perms_choices)
449 default_user_group_perm = v.OneOf(user_group_perms_choices)
456
450
457 return _ObjectPermissionsForm
451 return _ObjectPermissionsForm
458
452
459
453
460 def UserPermissionsForm(create_choices, create_on_write_choices,
454 def UserPermissionsForm(create_choices, create_on_write_choices,
461 repo_group_create_choices, user_group_create_choices,
455 repo_group_create_choices, user_group_create_choices,
462 fork_choices, inherit_default_permissions_choices):
456 fork_choices, inherit_default_permissions_choices):
463 class _DefaultPermissionsForm(formencode.Schema):
457 class _DefaultPermissionsForm(formencode.Schema):
464 allow_extra_fields = True
458 allow_extra_fields = True
465 filter_extra_fields = True
459 filter_extra_fields = True
466
460
467 anonymous = v.StringBoolean(if_missing=False)
461 anonymous = v.StringBoolean(if_missing=False)
468
462
469 default_repo_create = v.OneOf(create_choices)
463 default_repo_create = v.OneOf(create_choices)
470 default_repo_create_on_write = v.OneOf(create_on_write_choices)
464 default_repo_create_on_write = v.OneOf(create_on_write_choices)
471 default_user_group_create = v.OneOf(user_group_create_choices)
465 default_user_group_create = v.OneOf(user_group_create_choices)
472 default_repo_group_create = v.OneOf(repo_group_create_choices)
466 default_repo_group_create = v.OneOf(repo_group_create_choices)
473 default_fork_create = v.OneOf(fork_choices)
467 default_fork_create = v.OneOf(fork_choices)
474 default_inherit_default_permissions = v.OneOf(inherit_default_permissions_choices)
468 default_inherit_default_permissions = v.OneOf(inherit_default_permissions_choices)
475
469
476 return _DefaultPermissionsForm
470 return _DefaultPermissionsForm
477
471
478
472
479 def UserIndividualPermissionsForm():
473 def UserIndividualPermissionsForm():
480 class _DefaultPermissionsForm(formencode.Schema):
474 class _DefaultPermissionsForm(formencode.Schema):
481 allow_extra_fields = True
475 allow_extra_fields = True
482 filter_extra_fields = True
476 filter_extra_fields = True
483
477
484 inherit_default_permissions = v.StringBoolean(if_missing=False)
478 inherit_default_permissions = v.StringBoolean(if_missing=False)
485
479
486 return _DefaultPermissionsForm
480 return _DefaultPermissionsForm
487
481
488
482
489 def DefaultsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
483 def DefaultsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
490 class _DefaultsForm(formencode.Schema):
484 class _DefaultsForm(formencode.Schema):
491 allow_extra_fields = True
485 allow_extra_fields = True
492 filter_extra_fields = True
486 filter_extra_fields = True
493 default_repo_type = v.OneOf(supported_backends)
487 default_repo_type = v.OneOf(supported_backends)
494 default_repo_private = v.StringBoolean(if_missing=False)
488 default_repo_private = v.StringBoolean(if_missing=False)
495 default_repo_enable_statistics = v.StringBoolean(if_missing=False)
489 default_repo_enable_statistics = v.StringBoolean(if_missing=False)
496 default_repo_enable_downloads = v.StringBoolean(if_missing=False)
490 default_repo_enable_downloads = v.StringBoolean(if_missing=False)
497 default_repo_enable_locking = v.StringBoolean(if_missing=False)
491 default_repo_enable_locking = v.StringBoolean(if_missing=False)
498
492
499 return _DefaultsForm
493 return _DefaultsForm
500
494
501
495
502 def AuthSettingsForm():
496 def AuthSettingsForm():
503 class _AuthSettingsForm(formencode.Schema):
497 class _AuthSettingsForm(formencode.Schema):
504 allow_extra_fields = True
498 allow_extra_fields = True
505 filter_extra_fields = True
499 filter_extra_fields = True
506 auth_plugins = All(v.ValidAuthPlugins(),
500 auth_plugins = All(v.ValidAuthPlugins(),
507 v.UniqueListFromString()(not_empty=True))
501 v.UniqueListFromString()(not_empty=True))
508
502
509 return _AuthSettingsForm
503 return _AuthSettingsForm
510
504
511
505
512 def UserExtraEmailForm():
506 def UserExtraEmailForm():
513 class _UserExtraEmailForm(formencode.Schema):
507 class _UserExtraEmailForm(formencode.Schema):
514 email = All(v.UniqSystemEmail(), v.Email(not_empty=True))
508 email = All(v.UniqSystemEmail(), v.Email(not_empty=True))
515 return _UserExtraEmailForm
509 return _UserExtraEmailForm
516
510
517
511
518 def UserExtraIpForm():
512 def UserExtraIpForm():
519 class _UserExtraIpForm(formencode.Schema):
513 class _UserExtraIpForm(formencode.Schema):
520 ip = v.ValidIp()(not_empty=True)
514 ip = v.ValidIp()(not_empty=True)
521 return _UserExtraIpForm
515 return _UserExtraIpForm
522
516
523
517
524
518
525 def PullRequestForm(repo_id):
519 def PullRequestForm(repo_id):
526 class ReviewerForm(formencode.Schema):
520 class ReviewerForm(formencode.Schema):
527 user_id = v.Int(not_empty=True)
521 user_id = v.Int(not_empty=True)
528 reasons = All()
522 reasons = All()
529
523
530 class _PullRequestForm(formencode.Schema):
524 class _PullRequestForm(formencode.Schema):
531 allow_extra_fields = True
525 allow_extra_fields = True
532 filter_extra_fields = True
526 filter_extra_fields = True
533
527
534 user = v.UnicodeString(strip=True, required=True)
528 user = v.UnicodeString(strip=True, required=True)
535 source_repo = v.UnicodeString(strip=True, required=True)
529 source_repo = v.UnicodeString(strip=True, required=True)
536 source_ref = v.UnicodeString(strip=True, required=True)
530 source_ref = v.UnicodeString(strip=True, required=True)
537 target_repo = v.UnicodeString(strip=True, required=True)
531 target_repo = v.UnicodeString(strip=True, required=True)
538 target_ref = v.UnicodeString(strip=True, required=True)
532 target_ref = v.UnicodeString(strip=True, required=True)
539 revisions = All(#v.NotReviewedRevisions(repo_id)(),
533 revisions = All(#v.NotReviewedRevisions(repo_id)(),
540 v.UniqueList()(not_empty=True))
534 v.UniqueList()(not_empty=True))
541 review_members = formencode.ForEach(ReviewerForm())
535 review_members = formencode.ForEach(ReviewerForm())
542 pullrequest_title = v.UnicodeString(strip=True, required=True)
536 pullrequest_title = v.UnicodeString(strip=True, required=True)
543 pullrequest_desc = v.UnicodeString(strip=True, required=False)
537 pullrequest_desc = v.UnicodeString(strip=True, required=False)
544
538
545 return _PullRequestForm
539 return _PullRequestForm
546
540
547
541
548 def IssueTrackerPatternsForm():
542 def IssueTrackerPatternsForm():
549 class _IssueTrackerPatternsForm(formencode.Schema):
543 class _IssueTrackerPatternsForm(formencode.Schema):
550 allow_extra_fields = True
544 allow_extra_fields = True
551 filter_extra_fields = False
545 filter_extra_fields = False
552 chained_validators = [v.ValidPattern()]
546 chained_validators = [v.ValidPattern()]
553 return _IssueTrackerPatternsForm
547 return _IssueTrackerPatternsForm
@@ -1,517 +1,514 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2016 RhodeCode GmbH
3 # Copyright (C) 2011-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 """
22 """
23 user group model for RhodeCode
23 user group model for RhodeCode
24 """
24 """
25
25
26
26
27 import logging
27 import logging
28 import traceback
28 import traceback
29
29
30 from rhodecode.lib.utils2 import safe_str
30 from rhodecode.lib.utils2 import safe_str
31 from rhodecode.model import BaseModel
31 from rhodecode.model import BaseModel
32 from rhodecode.model.db import UserGroupMember, UserGroup,\
32 from rhodecode.model.db import UserGroupMember, UserGroup,\
33 UserGroupRepoToPerm, Permission, UserGroupToPerm, User, UserUserGroupToPerm,\
33 UserGroupRepoToPerm, Permission, UserGroupToPerm, User, UserUserGroupToPerm,\
34 UserGroupUserGroupToPerm, UserGroupRepoGroupToPerm
34 UserGroupUserGroupToPerm, UserGroupRepoGroupToPerm
35 from rhodecode.lib.exceptions import UserGroupAssignedException,\
35 from rhodecode.lib.exceptions import UserGroupAssignedException,\
36 RepoGroupAssignmentError
36 RepoGroupAssignmentError
37 from rhodecode.lib.utils2 import get_current_rhodecode_user, action_logger_generic
37 from rhodecode.lib.utils2 import get_current_rhodecode_user, action_logger_generic
38
38
39 log = logging.getLogger(__name__)
39 log = logging.getLogger(__name__)
40
40
41
41
42 class UserGroupModel(BaseModel):
42 class UserGroupModel(BaseModel):
43
43
44 cls = UserGroup
44 cls = UserGroup
45
45
46 def _get_user_group(self, user_group):
46 def _get_user_group(self, user_group):
47 return self._get_instance(UserGroup, user_group,
47 return self._get_instance(UserGroup, user_group,
48 callback=UserGroup.get_by_group_name)
48 callback=UserGroup.get_by_group_name)
49
49
50 def _create_default_perms(self, user_group):
50 def _create_default_perms(self, user_group):
51 # create default permission
51 # create default permission
52 default_perm = 'usergroup.read'
52 default_perm = 'usergroup.read'
53 def_user = User.get_default_user()
53 def_user = User.get_default_user()
54 for p in def_user.user_perms:
54 for p in def_user.user_perms:
55 if p.permission.permission_name.startswith('usergroup.'):
55 if p.permission.permission_name.startswith('usergroup.'):
56 default_perm = p.permission.permission_name
56 default_perm = p.permission.permission_name
57 break
57 break
58
58
59 user_group_to_perm = UserUserGroupToPerm()
59 user_group_to_perm = UserUserGroupToPerm()
60 user_group_to_perm.permission = Permission.get_by_key(default_perm)
60 user_group_to_perm.permission = Permission.get_by_key(default_perm)
61
61
62 user_group_to_perm.user_group = user_group
62 user_group_to_perm.user_group = user_group
63 user_group_to_perm.user_id = def_user.user_id
63 user_group_to_perm.user_id = def_user.user_id
64 return user_group_to_perm
64 return user_group_to_perm
65
65
66 def update_permissions(self, user_group, perm_additions=None, perm_updates=None,
66 def update_permissions(self, user_group, perm_additions=None, perm_updates=None,
67 perm_deletions=None, check_perms=True, cur_user=None):
67 perm_deletions=None, check_perms=True, cur_user=None):
68 from rhodecode.lib.auth import HasUserGroupPermissionAny
68 from rhodecode.lib.auth import HasUserGroupPermissionAny
69 if not perm_additions:
69 if not perm_additions:
70 perm_additions = []
70 perm_additions = []
71 if not perm_updates:
71 if not perm_updates:
72 perm_updates = []
72 perm_updates = []
73 if not perm_deletions:
73 if not perm_deletions:
74 perm_deletions = []
74 perm_deletions = []
75
75
76 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
76 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
77
77
78 # update permissions
78 # update permissions
79 for member_id, perm, member_type in perm_updates:
79 for member_id, perm, member_type in perm_updates:
80 member_id = int(member_id)
80 member_id = int(member_id)
81 if member_type == 'user':
81 if member_type == 'user':
82 # this updates existing one
82 # this updates existing one
83 self.grant_user_permission(
83 self.grant_user_permission(
84 user_group=user_group, user=member_id, perm=perm
84 user_group=user_group, user=member_id, perm=perm
85 )
85 )
86 else:
86 else:
87 # check if we have permissions to alter this usergroup
87 # check if we have permissions to alter this usergroup
88 member_name = UserGroup.get(member_id).users_group_name
88 member_name = UserGroup.get(member_id).users_group_name
89 if not check_perms or HasUserGroupPermissionAny(*req_perms)(member_name, user=cur_user):
89 if not check_perms or HasUserGroupPermissionAny(*req_perms)(member_name, user=cur_user):
90 self.grant_user_group_permission(
90 self.grant_user_group_permission(
91 target_user_group=user_group, user_group=member_id, perm=perm
91 target_user_group=user_group, user_group=member_id, perm=perm
92 )
92 )
93
93
94 # set new permissions
94 # set new permissions
95 for member_id, perm, member_type in perm_additions:
95 for member_id, perm, member_type in perm_additions:
96 member_id = int(member_id)
96 member_id = int(member_id)
97 if member_type == 'user':
97 if member_type == 'user':
98 self.grant_user_permission(
98 self.grant_user_permission(
99 user_group=user_group, user=member_id, perm=perm
99 user_group=user_group, user=member_id, perm=perm
100 )
100 )
101 else:
101 else:
102 # check if we have permissions to alter this usergroup
102 # check if we have permissions to alter this usergroup
103 member_name = UserGroup.get(member_id).users_group_name
103 member_name = UserGroup.get(member_id).users_group_name
104 if not check_perms or HasUserGroupPermissionAny(*req_perms)(member_name, user=cur_user):
104 if not check_perms or HasUserGroupPermissionAny(*req_perms)(member_name, user=cur_user):
105 self.grant_user_group_permission(
105 self.grant_user_group_permission(
106 target_user_group=user_group, user_group=member_id, perm=perm
106 target_user_group=user_group, user_group=member_id, perm=perm
107 )
107 )
108
108
109 # delete permissions
109 # delete permissions
110 for member_id, perm, member_type in perm_deletions:
110 for member_id, perm, member_type in perm_deletions:
111 member_id = int(member_id)
111 member_id = int(member_id)
112 if member_type == 'user':
112 if member_type == 'user':
113 self.revoke_user_permission(user_group=user_group, user=member_id)
113 self.revoke_user_permission(user_group=user_group, user=member_id)
114 else:
114 else:
115 #check if we have permissions to alter this usergroup
115 #check if we have permissions to alter this usergroup
116 member_name = UserGroup.get(member_id).users_group_name
116 member_name = UserGroup.get(member_id).users_group_name
117 if not check_perms or HasUserGroupPermissionAny(*req_perms)(member_name, user=cur_user):
117 if not check_perms or HasUserGroupPermissionAny(*req_perms)(member_name, user=cur_user):
118 self.revoke_user_group_permission(
118 self.revoke_user_group_permission(
119 target_user_group=user_group, user_group=member_id
119 target_user_group=user_group, user_group=member_id
120 )
120 )
121
121
122 def get(self, user_group_id, cache=False):
122 def get(self, user_group_id, cache=False):
123 return UserGroup.get(user_group_id)
123 return UserGroup.get(user_group_id)
124
124
125 def get_group(self, user_group):
125 def get_group(self, user_group):
126 return self._get_user_group(user_group)
126 return self._get_user_group(user_group)
127
127
128 def get_by_name(self, name, cache=False, case_insensitive=False):
128 def get_by_name(self, name, cache=False, case_insensitive=False):
129 return UserGroup.get_by_group_name(name, cache, case_insensitive)
129 return UserGroup.get_by_group_name(name, cache, case_insensitive)
130
130
131 def create(self, name, description, owner, active=True, group_data=None):
131 def create(self, name, description, owner, active=True, group_data=None):
132 try:
132 try:
133 new_user_group = UserGroup()
133 new_user_group = UserGroup()
134 new_user_group.user = self._get_user(owner)
134 new_user_group.user = self._get_user(owner)
135 new_user_group.users_group_name = name
135 new_user_group.users_group_name = name
136 new_user_group.user_group_description = description
136 new_user_group.user_group_description = description
137 new_user_group.users_group_active = active
137 new_user_group.users_group_active = active
138 if group_data:
138 if group_data:
139 new_user_group.group_data = group_data
139 new_user_group.group_data = group_data
140 self.sa.add(new_user_group)
140 self.sa.add(new_user_group)
141 perm_obj = self._create_default_perms(new_user_group)
141 perm_obj = self._create_default_perms(new_user_group)
142 self.sa.add(perm_obj)
142 self.sa.add(perm_obj)
143
143
144 self.grant_user_permission(user_group=new_user_group,
144 self.grant_user_permission(user_group=new_user_group,
145 user=owner, perm='usergroup.admin')
145 user=owner, perm='usergroup.admin')
146
146
147 return new_user_group
147 return new_user_group
148 except Exception:
148 except Exception:
149 log.error(traceback.format_exc())
149 log.error(traceback.format_exc())
150 raise
150 raise
151
151
152 def _get_memberships_for_user_ids(self, user_group, user_id_list):
152 def _get_memberships_for_user_ids(self, user_group, user_id_list):
153 members = []
153 members = []
154 for user_id in user_id_list:
154 for user_id in user_id_list:
155 member = self._get_membership(user_group.users_group_id, user_id)
155 member = self._get_membership(user_group.users_group_id, user_id)
156 members.append(member)
156 members.append(member)
157 return members
157 return members
158
158
159 def _get_added_and_removed_user_ids(self, user_group, user_id_list):
159 def _get_added_and_removed_user_ids(self, user_group, user_id_list):
160 current_members = user_group.members or []
160 current_members = user_group.members or []
161 current_members_ids = [m.user.user_id for m in current_members]
161 current_members_ids = [m.user.user_id for m in current_members]
162
162
163 added_members = [
163 added_members = [
164 user_id for user_id in user_id_list
164 user_id for user_id in user_id_list
165 if user_id not in current_members_ids]
165 if user_id not in current_members_ids]
166 if user_id_list == []:
166 if user_id_list == []:
167 # all members were deleted
167 # all members were deleted
168 deleted_members = current_members_ids
168 deleted_members = current_members_ids
169 else:
169 else:
170 deleted_members = [
170 deleted_members = [
171 user_id for user_id in current_members_ids
171 user_id for user_id in current_members_ids
172 if user_id not in user_id_list]
172 if user_id not in user_id_list]
173
173
174 return (added_members, deleted_members)
174 return (added_members, deleted_members)
175
175
176 def _set_users_as_members(self, user_group, user_ids):
176 def _set_users_as_members(self, user_group, user_ids):
177 user_group.members = []
177 user_group.members = []
178 self.sa.flush()
178 self.sa.flush()
179 members = self._get_memberships_for_user_ids(
179 members = self._get_memberships_for_user_ids(
180 user_group, user_ids)
180 user_group, user_ids)
181 user_group.members = members
181 user_group.members = members
182 self.sa.add(user_group)
182 self.sa.add(user_group)
183
183
184 def _update_members_from_user_ids(self, user_group, user_ids):
184 def _update_members_from_user_ids(self, user_group, user_ids):
185 added, removed = self._get_added_and_removed_user_ids(
185 added, removed = self._get_added_and_removed_user_ids(
186 user_group, user_ids)
186 user_group, user_ids)
187 self._set_users_as_members(user_group, user_ids)
187 self._set_users_as_members(user_group, user_ids)
188 self._log_user_changes('added to', user_group, added)
188 self._log_user_changes('added to', user_group, added)
189 self._log_user_changes('removed from', user_group, removed)
189 self._log_user_changes('removed from', user_group, removed)
190
190
191 def _clean_members_data(self, members_data):
191 def _clean_members_data(self, members_data):
192 # TODO: anderson: this should be in the form validation but I couldn't
193 # make it work there as it conflicts with the other validator
194 if not members_data:
192 if not members_data:
195 members_data = []
193 members_data = []
196
194
197 if isinstance(members_data, basestring):
195 members = []
198 new_members = [members_data]
196 for user in members_data:
199 else:
197 uid = int(user['member_user_id'])
200 new_members = members_data
198 if uid not in members and user['type'] in ['new', 'existing']:
201
199 members.append(uid)
202 new_members = [int(uid) for uid in new_members]
200 return members
203 return new_members
204
201
205 def update(self, user_group, form_data):
202 def update(self, user_group, form_data):
206 user_group = self._get_user_group(user_group)
203 user_group = self._get_user_group(user_group)
207 if 'users_group_name' in form_data:
204 if 'users_group_name' in form_data:
208 user_group.users_group_name = form_data['users_group_name']
205 user_group.users_group_name = form_data['users_group_name']
209 if 'users_group_active' in form_data:
206 if 'users_group_active' in form_data:
210 user_group.users_group_active = form_data['users_group_active']
207 user_group.users_group_active = form_data['users_group_active']
211 if 'user_group_description' in form_data:
208 if 'user_group_description' in form_data:
212 user_group.user_group_description = form_data[
209 user_group.user_group_description = form_data[
213 'user_group_description']
210 'user_group_description']
214
211
215 # handle owner change
212 # handle owner change
216 if 'user' in form_data:
213 if 'user' in form_data:
217 owner = form_data['user']
214 owner = form_data['user']
218 if isinstance(owner, basestring):
215 if isinstance(owner, basestring):
219 owner = User.get_by_username(form_data['user'])
216 owner = User.get_by_username(form_data['user'])
220
217
221 if not isinstance(owner, User):
218 if not isinstance(owner, User):
222 raise ValueError(
219 raise ValueError(
223 'invalid owner for user group: %s' % form_data['user'])
220 'invalid owner for user group: %s' % form_data['user'])
224
221
225 user_group.user = owner
222 user_group.user = owner
226
223
227 if 'users_group_members' in form_data:
224 if 'users_group_members' in form_data:
228 members_id_list = self._clean_members_data(
225 members_id_list = self._clean_members_data(
229 form_data['users_group_members'])
226 form_data['users_group_members'])
230 self._update_members_from_user_ids(user_group, members_id_list)
227 self._update_members_from_user_ids(user_group, members_id_list)
231
228
232 self.sa.add(user_group)
229 self.sa.add(user_group)
233
230
234 def delete(self, user_group, force=False):
231 def delete(self, user_group, force=False):
235 """
232 """
236 Deletes repository group, unless force flag is used
233 Deletes repository group, unless force flag is used
237 raises exception if there are members in that group, else deletes
234 raises exception if there are members in that group, else deletes
238 group and users
235 group and users
239
236
240 :param user_group:
237 :param user_group:
241 :param force:
238 :param force:
242 """
239 """
243 user_group = self._get_user_group(user_group)
240 user_group = self._get_user_group(user_group)
244 try:
241 try:
245 # check if this group is not assigned to repo
242 # check if this group is not assigned to repo
246 assigned_to_repo = [x.repository for x in UserGroupRepoToPerm.query()\
243 assigned_to_repo = [x.repository for x in UserGroupRepoToPerm.query()\
247 .filter(UserGroupRepoToPerm.users_group == user_group).all()]
244 .filter(UserGroupRepoToPerm.users_group == user_group).all()]
248 # check if this group is not assigned to repo
245 # check if this group is not assigned to repo
249 assigned_to_repo_group = [x.group for x in UserGroupRepoGroupToPerm.query()\
246 assigned_to_repo_group = [x.group for x in UserGroupRepoGroupToPerm.query()\
250 .filter(UserGroupRepoGroupToPerm.users_group == user_group).all()]
247 .filter(UserGroupRepoGroupToPerm.users_group == user_group).all()]
251
248
252 if (assigned_to_repo or assigned_to_repo_group) and not force:
249 if (assigned_to_repo or assigned_to_repo_group) and not force:
253 assigned = ','.join(map(safe_str,
250 assigned = ','.join(map(safe_str,
254 assigned_to_repo+assigned_to_repo_group))
251 assigned_to_repo+assigned_to_repo_group))
255
252
256 raise UserGroupAssignedException(
253 raise UserGroupAssignedException(
257 'UserGroup assigned to %s' % (assigned,))
254 'UserGroup assigned to %s' % (assigned,))
258 self.sa.delete(user_group)
255 self.sa.delete(user_group)
259 except Exception:
256 except Exception:
260 log.error(traceback.format_exc())
257 log.error(traceback.format_exc())
261 raise
258 raise
262
259
263 def _log_user_changes(self, action, user_group, user_or_users):
260 def _log_user_changes(self, action, user_group, user_or_users):
264 users = user_or_users
261 users = user_or_users
265 if not isinstance(users, (list, tuple)):
262 if not isinstance(users, (list, tuple)):
266 users = [users]
263 users = [users]
267 rhodecode_user = get_current_rhodecode_user()
264 rhodecode_user = get_current_rhodecode_user()
268 ipaddr = getattr(rhodecode_user, 'ip_addr', '')
265 ipaddr = getattr(rhodecode_user, 'ip_addr', '')
269 group_name = user_group.users_group_name
266 group_name = user_group.users_group_name
270
267
271 for user_or_user_id in users:
268 for user_or_user_id in users:
272 user = self._get_user(user_or_user_id)
269 user = self._get_user(user_or_user_id)
273 log_text = 'User {user} {action} {group}'.format(
270 log_text = 'User {user} {action} {group}'.format(
274 action=action, user=user.username, group=group_name)
271 action=action, user=user.username, group=group_name)
275 log.info('Logging action: {0} by {1} ip:{2}'.format(
272 log.info('Logging action: {0} by {1} ip:{2}'.format(
276 log_text, rhodecode_user, ipaddr))
273 log_text, rhodecode_user, ipaddr))
277
274
278 def _find_user_in_group(self, user, user_group):
275 def _find_user_in_group(self, user, user_group):
279 user_group_member = None
276 user_group_member = None
280 for m in user_group.members:
277 for m in user_group.members:
281 if m.user_id == user.user_id:
278 if m.user_id == user.user_id:
282 # Found this user's membership row
279 # Found this user's membership row
283 user_group_member = m
280 user_group_member = m
284 break
281 break
285
282
286 return user_group_member
283 return user_group_member
287
284
288 def _get_membership(self, user_group_id, user_id):
285 def _get_membership(self, user_group_id, user_id):
289 user_group_member = UserGroupMember(user_group_id, user_id)
286 user_group_member = UserGroupMember(user_group_id, user_id)
290 return user_group_member
287 return user_group_member
291
288
292 def add_user_to_group(self, user_group, user):
289 def add_user_to_group(self, user_group, user):
293 user_group = self._get_user_group(user_group)
290 user_group = self._get_user_group(user_group)
294 user = self._get_user(user)
291 user = self._get_user(user)
295 user_member = self._find_user_in_group(user, user_group)
292 user_member = self._find_user_in_group(user, user_group)
296 if user_member:
293 if user_member:
297 # user already in the group, skip
294 # user already in the group, skip
298 return True
295 return True
299
296
300 member = self._get_membership(
297 member = self._get_membership(
301 user_group.users_group_id, user.user_id)
298 user_group.users_group_id, user.user_id)
302 user_group.members.append(member)
299 user_group.members.append(member)
303
300
304 try:
301 try:
305 self.sa.add(member)
302 self.sa.add(member)
306 except Exception:
303 except Exception:
307 # what could go wrong here?
304 # what could go wrong here?
308 log.error(traceback.format_exc())
305 log.error(traceback.format_exc())
309 raise
306 raise
310
307
311 self._log_user_changes('added to', user_group, user)
308 self._log_user_changes('added to', user_group, user)
312 return member
309 return member
313
310
314 def remove_user_from_group(self, user_group, user):
311 def remove_user_from_group(self, user_group, user):
315 user_group = self._get_user_group(user_group)
312 user_group = self._get_user_group(user_group)
316 user = self._get_user(user)
313 user = self._get_user(user)
317 user_group_member = self._find_user_in_group(user, user_group)
314 user_group_member = self._find_user_in_group(user, user_group)
318
315
319 if not user_group_member:
316 if not user_group_member:
320 # User isn't in that group
317 # User isn't in that group
321 return False
318 return False
322
319
323 try:
320 try:
324 self.sa.delete(user_group_member)
321 self.sa.delete(user_group_member)
325 except Exception:
322 except Exception:
326 log.error(traceback.format_exc())
323 log.error(traceback.format_exc())
327 raise
324 raise
328
325
329 self._log_user_changes('removed from', user_group, user)
326 self._log_user_changes('removed from', user_group, user)
330 return True
327 return True
331
328
332 def has_perm(self, user_group, perm):
329 def has_perm(self, user_group, perm):
333 user_group = self._get_user_group(user_group)
330 user_group = self._get_user_group(user_group)
334 perm = self._get_perm(perm)
331 perm = self._get_perm(perm)
335
332
336 return UserGroupToPerm.query()\
333 return UserGroupToPerm.query()\
337 .filter(UserGroupToPerm.users_group == user_group)\
334 .filter(UserGroupToPerm.users_group == user_group)\
338 .filter(UserGroupToPerm.permission == perm).scalar() is not None
335 .filter(UserGroupToPerm.permission == perm).scalar() is not None
339
336
340 def grant_perm(self, user_group, perm):
337 def grant_perm(self, user_group, perm):
341 user_group = self._get_user_group(user_group)
338 user_group = self._get_user_group(user_group)
342 perm = self._get_perm(perm)
339 perm = self._get_perm(perm)
343
340
344 # if this permission is already granted skip it
341 # if this permission is already granted skip it
345 _perm = UserGroupToPerm.query()\
342 _perm = UserGroupToPerm.query()\
346 .filter(UserGroupToPerm.users_group == user_group)\
343 .filter(UserGroupToPerm.users_group == user_group)\
347 .filter(UserGroupToPerm.permission == perm)\
344 .filter(UserGroupToPerm.permission == perm)\
348 .scalar()
345 .scalar()
349 if _perm:
346 if _perm:
350 return
347 return
351
348
352 new = UserGroupToPerm()
349 new = UserGroupToPerm()
353 new.users_group = user_group
350 new.users_group = user_group
354 new.permission = perm
351 new.permission = perm
355 self.sa.add(new)
352 self.sa.add(new)
356 return new
353 return new
357
354
358 def revoke_perm(self, user_group, perm):
355 def revoke_perm(self, user_group, perm):
359 user_group = self._get_user_group(user_group)
356 user_group = self._get_user_group(user_group)
360 perm = self._get_perm(perm)
357 perm = self._get_perm(perm)
361
358
362 obj = UserGroupToPerm.query()\
359 obj = UserGroupToPerm.query()\
363 .filter(UserGroupToPerm.users_group == user_group)\
360 .filter(UserGroupToPerm.users_group == user_group)\
364 .filter(UserGroupToPerm.permission == perm).scalar()
361 .filter(UserGroupToPerm.permission == perm).scalar()
365 if obj:
362 if obj:
366 self.sa.delete(obj)
363 self.sa.delete(obj)
367
364
368 def grant_user_permission(self, user_group, user, perm):
365 def grant_user_permission(self, user_group, user, perm):
369 """
366 """
370 Grant permission for user on given user group, or update
367 Grant permission for user on given user group, or update
371 existing one if found
368 existing one if found
372
369
373 :param user_group: Instance of UserGroup, users_group_id,
370 :param user_group: Instance of UserGroup, users_group_id,
374 or users_group_name
371 or users_group_name
375 :param user: Instance of User, user_id or username
372 :param user: Instance of User, user_id or username
376 :param perm: Instance of Permission, or permission_name
373 :param perm: Instance of Permission, or permission_name
377 """
374 """
378
375
379 user_group = self._get_user_group(user_group)
376 user_group = self._get_user_group(user_group)
380 user = self._get_user(user)
377 user = self._get_user(user)
381 permission = self._get_perm(perm)
378 permission = self._get_perm(perm)
382
379
383 # check if we have that permission already
380 # check if we have that permission already
384 obj = self.sa.query(UserUserGroupToPerm)\
381 obj = self.sa.query(UserUserGroupToPerm)\
385 .filter(UserUserGroupToPerm.user == user)\
382 .filter(UserUserGroupToPerm.user == user)\
386 .filter(UserUserGroupToPerm.user_group == user_group)\
383 .filter(UserUserGroupToPerm.user_group == user_group)\
387 .scalar()
384 .scalar()
388 if obj is None:
385 if obj is None:
389 # create new !
386 # create new !
390 obj = UserUserGroupToPerm()
387 obj = UserUserGroupToPerm()
391 obj.user_group = user_group
388 obj.user_group = user_group
392 obj.user = user
389 obj.user = user
393 obj.permission = permission
390 obj.permission = permission
394 self.sa.add(obj)
391 self.sa.add(obj)
395 log.debug('Granted perm %s to %s on %s', perm, user, user_group)
392 log.debug('Granted perm %s to %s on %s', perm, user, user_group)
396 action_logger_generic(
393 action_logger_generic(
397 'granted permission: {} to user: {} on usergroup: {}'.format(
394 'granted permission: {} to user: {} on usergroup: {}'.format(
398 perm, user, user_group), namespace='security.usergroup')
395 perm, user, user_group), namespace='security.usergroup')
399
396
400 return obj
397 return obj
401
398
402 def revoke_user_permission(self, user_group, user):
399 def revoke_user_permission(self, user_group, user):
403 """
400 """
404 Revoke permission for user on given user group
401 Revoke permission for user on given user group
405
402
406 :param user_group: Instance of UserGroup, users_group_id,
403 :param user_group: Instance of UserGroup, users_group_id,
407 or users_group name
404 or users_group name
408 :param user: Instance of User, user_id or username
405 :param user: Instance of User, user_id or username
409 """
406 """
410
407
411 user_group = self._get_user_group(user_group)
408 user_group = self._get_user_group(user_group)
412 user = self._get_user(user)
409 user = self._get_user(user)
413
410
414 obj = self.sa.query(UserUserGroupToPerm)\
411 obj = self.sa.query(UserUserGroupToPerm)\
415 .filter(UserUserGroupToPerm.user == user)\
412 .filter(UserUserGroupToPerm.user == user)\
416 .filter(UserUserGroupToPerm.user_group == user_group)\
413 .filter(UserUserGroupToPerm.user_group == user_group)\
417 .scalar()
414 .scalar()
418 if obj:
415 if obj:
419 self.sa.delete(obj)
416 self.sa.delete(obj)
420 log.debug('Revoked perm on %s on %s', user_group, user)
417 log.debug('Revoked perm on %s on %s', user_group, user)
421 action_logger_generic(
418 action_logger_generic(
422 'revoked permission from user: {} on usergroup: {}'.format(
419 'revoked permission from user: {} on usergroup: {}'.format(
423 user, user_group), namespace='security.usergroup')
420 user, user_group), namespace='security.usergroup')
424
421
425 def grant_user_group_permission(self, target_user_group, user_group, perm):
422 def grant_user_group_permission(self, target_user_group, user_group, perm):
426 """
423 """
427 Grant user group permission for given target_user_group
424 Grant user group permission for given target_user_group
428
425
429 :param target_user_group:
426 :param target_user_group:
430 :param user_group:
427 :param user_group:
431 :param perm:
428 :param perm:
432 """
429 """
433 target_user_group = self._get_user_group(target_user_group)
430 target_user_group = self._get_user_group(target_user_group)
434 user_group = self._get_user_group(user_group)
431 user_group = self._get_user_group(user_group)
435 permission = self._get_perm(perm)
432 permission = self._get_perm(perm)
436 # forbid assigning same user group to itself
433 # forbid assigning same user group to itself
437 if target_user_group == user_group:
434 if target_user_group == user_group:
438 raise RepoGroupAssignmentError('target repo:%s cannot be '
435 raise RepoGroupAssignmentError('target repo:%s cannot be '
439 'assigned to itself' % target_user_group)
436 'assigned to itself' % target_user_group)
440
437
441 # check if we have that permission already
438 # check if we have that permission already
442 obj = self.sa.query(UserGroupUserGroupToPerm)\
439 obj = self.sa.query(UserGroupUserGroupToPerm)\
443 .filter(UserGroupUserGroupToPerm.target_user_group == target_user_group)\
440 .filter(UserGroupUserGroupToPerm.target_user_group == target_user_group)\
444 .filter(UserGroupUserGroupToPerm.user_group == user_group)\
441 .filter(UserGroupUserGroupToPerm.user_group == user_group)\
445 .scalar()
442 .scalar()
446 if obj is None:
443 if obj is None:
447 # create new !
444 # create new !
448 obj = UserGroupUserGroupToPerm()
445 obj = UserGroupUserGroupToPerm()
449 obj.user_group = user_group
446 obj.user_group = user_group
450 obj.target_user_group = target_user_group
447 obj.target_user_group = target_user_group
451 obj.permission = permission
448 obj.permission = permission
452 self.sa.add(obj)
449 self.sa.add(obj)
453 log.debug(
450 log.debug(
454 'Granted perm %s to %s on %s', perm, target_user_group, user_group)
451 'Granted perm %s to %s on %s', perm, target_user_group, user_group)
455 action_logger_generic(
452 action_logger_generic(
456 'granted permission: {} to usergroup: {} on usergroup: {}'.format(
453 'granted permission: {} to usergroup: {} on usergroup: {}'.format(
457 perm, user_group, target_user_group),
454 perm, user_group, target_user_group),
458 namespace='security.usergroup')
455 namespace='security.usergroup')
459
456
460 return obj
457 return obj
461
458
462 def revoke_user_group_permission(self, target_user_group, user_group):
459 def revoke_user_group_permission(self, target_user_group, user_group):
463 """
460 """
464 Revoke user group permission for given target_user_group
461 Revoke user group permission for given target_user_group
465
462
466 :param target_user_group:
463 :param target_user_group:
467 :param user_group:
464 :param user_group:
468 """
465 """
469 target_user_group = self._get_user_group(target_user_group)
466 target_user_group = self._get_user_group(target_user_group)
470 user_group = self._get_user_group(user_group)
467 user_group = self._get_user_group(user_group)
471
468
472 obj = self.sa.query(UserGroupUserGroupToPerm)\
469 obj = self.sa.query(UserGroupUserGroupToPerm)\
473 .filter(UserGroupUserGroupToPerm.target_user_group == target_user_group)\
470 .filter(UserGroupUserGroupToPerm.target_user_group == target_user_group)\
474 .filter(UserGroupUserGroupToPerm.user_group == user_group)\
471 .filter(UserGroupUserGroupToPerm.user_group == user_group)\
475 .scalar()
472 .scalar()
476 if obj:
473 if obj:
477 self.sa.delete(obj)
474 self.sa.delete(obj)
478 log.debug(
475 log.debug(
479 'Revoked perm on %s on %s', target_user_group, user_group)
476 'Revoked perm on %s on %s', target_user_group, user_group)
480 action_logger_generic(
477 action_logger_generic(
481 'revoked permission from usergroup: {} on usergroup: {}'.format(
478 'revoked permission from usergroup: {} on usergroup: {}'.format(
482 user_group, target_user_group),
479 user_group, target_user_group),
483 namespace='security.repogroup')
480 namespace='security.repogroup')
484
481
485 def enforce_groups(self, user, groups, extern_type=None):
482 def enforce_groups(self, user, groups, extern_type=None):
486 user = self._get_user(user)
483 user = self._get_user(user)
487 log.debug('Enforcing groups %s on user %s', groups, user)
484 log.debug('Enforcing groups %s on user %s', groups, user)
488 current_groups = user.group_member
485 current_groups = user.group_member
489 # find the external created groups
486 # find the external created groups
490 externals = [x.users_group for x in current_groups
487 externals = [x.users_group for x in current_groups
491 if 'extern_type' in x.users_group.group_data]
488 if 'extern_type' in x.users_group.group_data]
492
489
493 # calculate from what groups user should be removed
490 # calculate from what groups user should be removed
494 # externals that are not in groups
491 # externals that are not in groups
495 for gr in externals:
492 for gr in externals:
496 if gr.users_group_name not in groups:
493 if gr.users_group_name not in groups:
497 log.debug('Removing user %s from user group %s', user, gr)
494 log.debug('Removing user %s from user group %s', user, gr)
498 self.remove_user_from_group(gr, user)
495 self.remove_user_from_group(gr, user)
499
496
500 # now we calculate in which groups user should be == groups params
497 # now we calculate in which groups user should be == groups params
501 owner = User.get_first_super_admin().username
498 owner = User.get_first_super_admin().username
502 for gr in set(groups):
499 for gr in set(groups):
503 existing_group = UserGroup.get_by_group_name(gr)
500 existing_group = UserGroup.get_by_group_name(gr)
504 if not existing_group:
501 if not existing_group:
505 desc = 'Automatically created from plugin:%s' % extern_type
502 desc = 'Automatically created from plugin:%s' % extern_type
506 # we use first admin account to set the owner of the group
503 # we use first admin account to set the owner of the group
507 existing_group = UserGroupModel().create(gr, desc, owner,
504 existing_group = UserGroupModel().create(gr, desc, owner,
508 group_data={'extern_type': extern_type})
505 group_data={'extern_type': extern_type})
509
506
510 # we can only add users to special groups created via plugins
507 # we can only add users to special groups created via plugins
511 managed = 'extern_type' in existing_group.group_data
508 managed = 'extern_type' in existing_group.group_data
512 if managed:
509 if managed:
513 log.debug('Adding user %s to user group %s', user, gr)
510 log.debug('Adding user %s to user group %s', user, gr)
514 UserGroupModel().add_user_to_group(existing_group, user)
511 UserGroupModel().add_user_to_group(existing_group, user)
515 else:
512 else:
516 log.debug('Skipping addition to group %s since it is '
513 log.debug('Skipping addition to group %s since it is '
517 'not managed by auth plugins' % gr)
514 'not managed by auth plugins' % gr)
@@ -1,2184 +1,2204 b''
1 //Primary CSS
1 //Primary CSS
2
2
3 //--- IMPORTS ------------------//
3 //--- IMPORTS ------------------//
4
4
5 @import 'helpers';
5 @import 'helpers';
6 @import 'mixins';
6 @import 'mixins';
7 @import 'rcicons';
7 @import 'rcicons';
8 @import 'fonts';
8 @import 'fonts';
9 @import 'variables';
9 @import 'variables';
10 @import 'bootstrap-variables';
10 @import 'bootstrap-variables';
11 @import 'form-bootstrap';
11 @import 'form-bootstrap';
12 @import 'codemirror';
12 @import 'codemirror';
13 @import 'legacy_code_styles';
13 @import 'legacy_code_styles';
14 @import 'progress-bar';
14 @import 'progress-bar';
15
15
16 @import 'type';
16 @import 'type';
17 @import 'alerts';
17 @import 'alerts';
18 @import 'buttons';
18 @import 'buttons';
19 @import 'tags';
19 @import 'tags';
20 @import 'code-block';
20 @import 'code-block';
21 @import 'examples';
21 @import 'examples';
22 @import 'login';
22 @import 'login';
23 @import 'main-content';
23 @import 'main-content';
24 @import 'select2';
24 @import 'select2';
25 @import 'comments';
25 @import 'comments';
26 @import 'panels-bootstrap';
26 @import 'panels-bootstrap';
27 @import 'panels';
27 @import 'panels';
28 @import 'deform';
28 @import 'deform';
29
29
30 //--- BASE ------------------//
30 //--- BASE ------------------//
31 .noscript-error {
31 .noscript-error {
32 top: 0;
32 top: 0;
33 left: 0;
33 left: 0;
34 width: 100%;
34 width: 100%;
35 z-index: 101;
35 z-index: 101;
36 text-align: center;
36 text-align: center;
37 font-family: @text-semibold;
37 font-family: @text-semibold;
38 font-size: 120%;
38 font-size: 120%;
39 color: white;
39 color: white;
40 background-color: @alert2;
40 background-color: @alert2;
41 padding: 5px 0 5px 0;
41 padding: 5px 0 5px 0;
42 }
42 }
43
43
44 html {
44 html {
45 display: table;
45 display: table;
46 height: 100%;
46 height: 100%;
47 width: 100%;
47 width: 100%;
48 }
48 }
49
49
50 body {
50 body {
51 display: table-cell;
51 display: table-cell;
52 width: 100%;
52 width: 100%;
53 }
53 }
54
54
55 //--- LAYOUT ------------------//
55 //--- LAYOUT ------------------//
56
56
57 .hidden{
57 .hidden{
58 display: none !important;
58 display: none !important;
59 }
59 }
60
60
61 .box{
61 .box{
62 float: left;
62 float: left;
63 width: 100%;
63 width: 100%;
64 }
64 }
65
65
66 .browser-header {
66 .browser-header {
67 clear: both;
67 clear: both;
68 }
68 }
69 .main {
69 .main {
70 clear: both;
70 clear: both;
71 padding:0 0 @pagepadding;
71 padding:0 0 @pagepadding;
72 height: auto;
72 height: auto;
73
73
74 &:after { //clearfix
74 &:after { //clearfix
75 content:"";
75 content:"";
76 clear:both;
76 clear:both;
77 width:100%;
77 width:100%;
78 display:block;
78 display:block;
79 }
79 }
80 }
80 }
81
81
82 .action-link{
82 .action-link{
83 margin-left: @padding;
83 margin-left: @padding;
84 padding-left: @padding;
84 padding-left: @padding;
85 border-left: @border-thickness solid @border-default-color;
85 border-left: @border-thickness solid @border-default-color;
86 }
86 }
87
87
88 input + .action-link, .action-link.first{
88 input + .action-link, .action-link.first{
89 border-left: none;
89 border-left: none;
90 }
90 }
91
91
92 .action-link.last{
92 .action-link.last{
93 margin-right: @padding;
93 margin-right: @padding;
94 padding-right: @padding;
94 padding-right: @padding;
95 }
95 }
96
96
97 .action-link.active,
97 .action-link.active,
98 .action-link.active a{
98 .action-link.active a{
99 color: @grey4;
99 color: @grey4;
100 }
100 }
101
101
102 ul.simple-list{
102 ul.simple-list{
103 list-style: none;
103 list-style: none;
104 margin: 0;
104 margin: 0;
105 padding: 0;
105 padding: 0;
106 }
106 }
107
107
108 .main-content {
108 .main-content {
109 padding-bottom: @pagepadding;
109 padding-bottom: @pagepadding;
110 }
110 }
111
111
112 .wrapper {
112 .wrapper {
113 position: relative;
113 position: relative;
114 max-width: @wrapper-maxwidth;
114 max-width: @wrapper-maxwidth;
115 margin: 0 auto;
115 margin: 0 auto;
116 }
116 }
117
117
118 #content {
118 #content {
119 clear: both;
119 clear: both;
120 padding: 0 @contentpadding;
120 padding: 0 @contentpadding;
121 }
121 }
122
122
123 .advanced-settings-fields{
123 .advanced-settings-fields{
124 input{
124 input{
125 margin-left: @textmargin;
125 margin-left: @textmargin;
126 margin-right: @padding/2;
126 margin-right: @padding/2;
127 }
127 }
128 }
128 }
129
129
130 .cs_files_title {
130 .cs_files_title {
131 margin: @pagepadding 0 0;
131 margin: @pagepadding 0 0;
132 }
132 }
133
133
134 input.inline[type="file"] {
134 input.inline[type="file"] {
135 display: inline;
135 display: inline;
136 }
136 }
137
137
138 .error_page {
138 .error_page {
139 margin: 10% auto;
139 margin: 10% auto;
140
140
141 h1 {
141 h1 {
142 color: @grey2;
142 color: @grey2;
143 }
143 }
144
144
145 .alert {
145 .alert {
146 margin: @padding 0;
146 margin: @padding 0;
147 }
147 }
148
148
149 .error-branding {
149 .error-branding {
150 font-family: @text-semibold;
150 font-family: @text-semibold;
151 color: @grey4;
151 color: @grey4;
152 }
152 }
153
153
154 .error_message {
154 .error_message {
155 font-family: @text-regular;
155 font-family: @text-regular;
156 }
156 }
157
157
158 .sidebar {
158 .sidebar {
159 min-height: 275px;
159 min-height: 275px;
160 margin: 0;
160 margin: 0;
161 padding: 0 0 @sidebarpadding @sidebarpadding;
161 padding: 0 0 @sidebarpadding @sidebarpadding;
162 border: none;
162 border: none;
163 }
163 }
164
164
165 .main-content {
165 .main-content {
166 position: relative;
166 position: relative;
167 margin: 0 @sidebarpadding @sidebarpadding;
167 margin: 0 @sidebarpadding @sidebarpadding;
168 padding: 0 0 0 @sidebarpadding;
168 padding: 0 0 0 @sidebarpadding;
169 border-left: @border-thickness solid @grey5;
169 border-left: @border-thickness solid @grey5;
170
170
171 @media (max-width:767px) {
171 @media (max-width:767px) {
172 clear: both;
172 clear: both;
173 width: 100%;
173 width: 100%;
174 margin: 0;
174 margin: 0;
175 border: none;
175 border: none;
176 }
176 }
177 }
177 }
178
178
179 .inner-column {
179 .inner-column {
180 float: left;
180 float: left;
181 width: 29.75%;
181 width: 29.75%;
182 min-height: 150px;
182 min-height: 150px;
183 margin: @sidebarpadding 2% 0 0;
183 margin: @sidebarpadding 2% 0 0;
184 padding: 0 2% 0 0;
184 padding: 0 2% 0 0;
185 border-right: @border-thickness solid @grey5;
185 border-right: @border-thickness solid @grey5;
186
186
187 @media (max-width:767px) {
187 @media (max-width:767px) {
188 clear: both;
188 clear: both;
189 width: 100%;
189 width: 100%;
190 border: none;
190 border: none;
191 }
191 }
192
192
193 ul {
193 ul {
194 padding-left: 1.25em;
194 padding-left: 1.25em;
195 }
195 }
196
196
197 &:last-child {
197 &:last-child {
198 margin: @sidebarpadding 0 0;
198 margin: @sidebarpadding 0 0;
199 border: none;
199 border: none;
200 }
200 }
201
201
202 h4 {
202 h4 {
203 margin: 0 0 @padding;
203 margin: 0 0 @padding;
204 font-family: @text-semibold;
204 font-family: @text-semibold;
205 }
205 }
206 }
206 }
207 }
207 }
208 .error-page-logo {
208 .error-page-logo {
209 width: 130px;
209 width: 130px;
210 height: 160px;
210 height: 160px;
211 }
211 }
212
212
213 // HEADER
213 // HEADER
214 .header {
214 .header {
215
215
216 // TODO: johbo: Fix login pages, so that they work without a min-height
216 // TODO: johbo: Fix login pages, so that they work without a min-height
217 // for the header and then remove the min-height. I chose a smaller value
217 // for the header and then remove the min-height. I chose a smaller value
218 // intentionally here to avoid rendering issues in the main navigation.
218 // intentionally here to avoid rendering issues in the main navigation.
219 min-height: 49px;
219 min-height: 49px;
220
220
221 position: relative;
221 position: relative;
222 vertical-align: bottom;
222 vertical-align: bottom;
223 padding: 0 @header-padding;
223 padding: 0 @header-padding;
224 background-color: @grey2;
224 background-color: @grey2;
225 color: @grey5;
225 color: @grey5;
226
226
227 .title {
227 .title {
228 overflow: visible;
228 overflow: visible;
229 }
229 }
230
230
231 &:before,
231 &:before,
232 &:after {
232 &:after {
233 content: "";
233 content: "";
234 clear: both;
234 clear: both;
235 width: 100%;
235 width: 100%;
236 }
236 }
237
237
238 // TODO: johbo: Avoids breaking "Repositories" chooser
238 // TODO: johbo: Avoids breaking "Repositories" chooser
239 .select2-container .select2-choice .select2-arrow {
239 .select2-container .select2-choice .select2-arrow {
240 display: none;
240 display: none;
241 }
241 }
242 }
242 }
243
243
244 #header-inner {
244 #header-inner {
245 &.title {
245 &.title {
246 margin: 0;
246 margin: 0;
247 }
247 }
248 &:before,
248 &:before,
249 &:after {
249 &:after {
250 content: "";
250 content: "";
251 clear: both;
251 clear: both;
252 }
252 }
253 }
253 }
254
254
255 // Gists
255 // Gists
256 #files_data {
256 #files_data {
257 clear: both; //for firefox
257 clear: both; //for firefox
258 }
258 }
259 #gistid {
259 #gistid {
260 margin-right: @padding;
260 margin-right: @padding;
261 }
261 }
262
262
263 // Global Settings Editor
263 // Global Settings Editor
264 .textarea.editor {
264 .textarea.editor {
265 float: left;
265 float: left;
266 position: relative;
266 position: relative;
267 max-width: @texteditor-width;
267 max-width: @texteditor-width;
268
268
269 select {
269 select {
270 position: absolute;
270 position: absolute;
271 top:10px;
271 top:10px;
272 right:0;
272 right:0;
273 }
273 }
274
274
275 .CodeMirror {
275 .CodeMirror {
276 margin: 0;
276 margin: 0;
277 }
277 }
278
278
279 .help-block {
279 .help-block {
280 margin: 0 0 @padding;
280 margin: 0 0 @padding;
281 padding:.5em;
281 padding:.5em;
282 background-color: @grey6;
282 background-color: @grey6;
283 }
283 }
284 }
284 }
285
285
286 ul.auth_plugins {
286 ul.auth_plugins {
287 margin: @padding 0 @padding @legend-width;
287 margin: @padding 0 @padding @legend-width;
288 padding: 0;
288 padding: 0;
289
289
290 li {
290 li {
291 margin-bottom: @padding;
291 margin-bottom: @padding;
292 line-height: 1em;
292 line-height: 1em;
293 list-style-type: none;
293 list-style-type: none;
294
294
295 .auth_buttons .btn {
295 .auth_buttons .btn {
296 margin-right: @padding;
296 margin-right: @padding;
297 }
297 }
298
298
299 &:before { content: none; }
299 &:before { content: none; }
300 }
300 }
301 }
301 }
302
302
303
303
304 // My Account PR list
304 // My Account PR list
305
305
306 #show_closed {
306 #show_closed {
307 margin: 0 1em 0 0;
307 margin: 0 1em 0 0;
308 }
308 }
309
309
310 .pullrequestlist {
310 .pullrequestlist {
311 .closed {
311 .closed {
312 background-color: @grey6;
312 background-color: @grey6;
313 }
313 }
314 .td-status {
314 .td-status {
315 padding-left: .5em;
315 padding-left: .5em;
316 }
316 }
317 .log-container .truncate {
317 .log-container .truncate {
318 height: 2.75em;
318 height: 2.75em;
319 white-space: pre-line;
319 white-space: pre-line;
320 }
320 }
321 table.rctable .user {
321 table.rctable .user {
322 padding-left: 0;
322 padding-left: 0;
323 }
323 }
324 table.rctable {
324 table.rctable {
325 td.td-description,
325 td.td-description,
326 .rc-user {
326 .rc-user {
327 min-width: auto;
327 min-width: auto;
328 }
328 }
329 }
329 }
330 }
330 }
331
331
332 // Pull Requests
332 // Pull Requests
333
333
334 .pullrequests_section_head {
334 .pullrequests_section_head {
335 display: block;
335 display: block;
336 clear: both;
336 clear: both;
337 margin: @padding 0;
337 margin: @padding 0;
338 font-family: @text-bold;
338 font-family: @text-bold;
339 }
339 }
340
340
341 .pr-origininfo, .pr-targetinfo {
341 .pr-origininfo, .pr-targetinfo {
342 position: relative;
342 position: relative;
343
343
344 .tag {
344 .tag {
345 display: inline-block;
345 display: inline-block;
346 margin: 0 1em .5em 0;
346 margin: 0 1em .5em 0;
347 }
347 }
348
348
349 .clone-url {
349 .clone-url {
350 display: inline-block;
350 display: inline-block;
351 margin: 0 0 .5em 0;
351 margin: 0 0 .5em 0;
352 padding: 0;
352 padding: 0;
353 line-height: 1.2em;
353 line-height: 1.2em;
354 }
354 }
355 }
355 }
356
356
357 .pr-pullinfo {
357 .pr-pullinfo {
358 clear: both;
358 clear: both;
359 margin: .5em 0;
359 margin: .5em 0;
360 }
360 }
361
361
362 #pr-title-input {
362 #pr-title-input {
363 width: 72%;
363 width: 72%;
364 font-size: 1em;
364 font-size: 1em;
365 font-family: @text-bold;
365 font-family: @text-bold;
366 margin: 0;
366 margin: 0;
367 padding: 0 0 0 @padding/4;
367 padding: 0 0 0 @padding/4;
368 line-height: 1.7em;
368 line-height: 1.7em;
369 color: @text-color;
369 color: @text-color;
370 letter-spacing: .02em;
370 letter-spacing: .02em;
371 }
371 }
372
372
373 #pullrequest_title {
373 #pullrequest_title {
374 width: 100%;
374 width: 100%;
375 box-sizing: border-box;
375 box-sizing: border-box;
376 }
376 }
377
377
378 #pr_open_message {
378 #pr_open_message {
379 border: @border-thickness solid #fff;
379 border: @border-thickness solid #fff;
380 border-radius: @border-radius;
380 border-radius: @border-radius;
381 padding: @padding-large-vertical @padding-large-vertical @padding-large-vertical 0;
381 padding: @padding-large-vertical @padding-large-vertical @padding-large-vertical 0;
382 text-align: right;
382 text-align: right;
383 overflow: hidden;
383 overflow: hidden;
384 }
384 }
385
385
386 .pr-submit-button {
386 .pr-submit-button {
387 float: right;
387 float: right;
388 margin: 0 0 0 5px;
388 margin: 0 0 0 5px;
389 }
389 }
390
390
391 .pr-spacing-container {
391 .pr-spacing-container {
392 padding: 20px;
392 padding: 20px;
393 clear: both
393 clear: both
394 }
394 }
395
395
396 #pr-description-input {
396 #pr-description-input {
397 margin-bottom: 0;
397 margin-bottom: 0;
398 }
398 }
399
399
400 .pr-description-label {
400 .pr-description-label {
401 vertical-align: top;
401 vertical-align: top;
402 }
402 }
403
403
404 .perms_section_head {
404 .perms_section_head {
405 min-width: 625px;
405 min-width: 625px;
406
406
407 h2 {
407 h2 {
408 margin-bottom: 0;
408 margin-bottom: 0;
409 }
409 }
410
410
411 .label-checkbox {
411 .label-checkbox {
412 float: left;
412 float: left;
413 }
413 }
414
414
415 &.field {
415 &.field {
416 margin: @space 0 @padding;
416 margin: @space 0 @padding;
417 }
417 }
418
418
419 &:first-child.field {
419 &:first-child.field {
420 margin-top: 0;
420 margin-top: 0;
421
421
422 .label {
422 .label {
423 margin-top: 0;
423 margin-top: 0;
424 padding-top: 0;
424 padding-top: 0;
425 }
425 }
426
426
427 .radios {
427 .radios {
428 padding-top: 0;
428 padding-top: 0;
429 }
429 }
430 }
430 }
431
431
432 .radios {
432 .radios {
433 float: right;
433 float: right;
434 position: relative;
434 position: relative;
435 width: 405px;
435 width: 405px;
436 }
436 }
437 }
437 }
438
438
439 //--- MODULES ------------------//
439 //--- MODULES ------------------//
440
440
441
441
442 // Server Announcement
442 // Server Announcement
443 #server-announcement {
443 #server-announcement {
444 width: 95%;
444 width: 95%;
445 margin: @padding auto;
445 margin: @padding auto;
446 padding: @padding;
446 padding: @padding;
447 border-width: 2px;
447 border-width: 2px;
448 border-style: solid;
448 border-style: solid;
449 .border-radius(2px);
449 .border-radius(2px);
450 font-family: @text-bold;
450 font-family: @text-bold;
451
451
452 &.info { border-color: @alert4; background-color: @alert4-inner; }
452 &.info { border-color: @alert4; background-color: @alert4-inner; }
453 &.warning { border-color: @alert3; background-color: @alert3-inner; }
453 &.warning { border-color: @alert3; background-color: @alert3-inner; }
454 &.error { border-color: @alert2; background-color: @alert2-inner; }
454 &.error { border-color: @alert2; background-color: @alert2-inner; }
455 &.success { border-color: @alert1; background-color: @alert1-inner; }
455 &.success { border-color: @alert1; background-color: @alert1-inner; }
456 &.neutral { border-color: @grey3; background-color: @grey6; }
456 &.neutral { border-color: @grey3; background-color: @grey6; }
457 }
457 }
458
458
459 // Fixed Sidebar Column
459 // Fixed Sidebar Column
460 .sidebar-col-wrapper {
460 .sidebar-col-wrapper {
461 padding-left: @sidebar-all-width;
461 padding-left: @sidebar-all-width;
462
462
463 .sidebar {
463 .sidebar {
464 width: @sidebar-width;
464 width: @sidebar-width;
465 margin-left: -@sidebar-all-width;
465 margin-left: -@sidebar-all-width;
466 }
466 }
467 }
467 }
468
468
469 .sidebar-col-wrapper.scw-small {
469 .sidebar-col-wrapper.scw-small {
470 padding-left: @sidebar-small-all-width;
470 padding-left: @sidebar-small-all-width;
471
471
472 .sidebar {
472 .sidebar {
473 width: @sidebar-small-width;
473 width: @sidebar-small-width;
474 margin-left: -@sidebar-small-all-width;
474 margin-left: -@sidebar-small-all-width;
475 }
475 }
476 }
476 }
477
477
478
478
479 // FOOTER
479 // FOOTER
480 #footer {
480 #footer {
481 padding: 0;
481 padding: 0;
482 text-align: center;
482 text-align: center;
483 vertical-align: middle;
483 vertical-align: middle;
484 color: @grey2;
484 color: @grey2;
485 background-color: @grey6;
485 background-color: @grey6;
486
486
487 p {
487 p {
488 margin: 0;
488 margin: 0;
489 padding: 1em;
489 padding: 1em;
490 line-height: 1em;
490 line-height: 1em;
491 }
491 }
492
492
493 .server-instance { //server instance
493 .server-instance { //server instance
494 display: none;
494 display: none;
495 }
495 }
496
496
497 .title {
497 .title {
498 float: none;
498 float: none;
499 margin: 0 auto;
499 margin: 0 auto;
500 }
500 }
501 }
501 }
502
502
503 button.close {
503 button.close {
504 padding: 0;
504 padding: 0;
505 cursor: pointer;
505 cursor: pointer;
506 background: transparent;
506 background: transparent;
507 border: 0;
507 border: 0;
508 .box-shadow(none);
508 .box-shadow(none);
509 -webkit-appearance: none;
509 -webkit-appearance: none;
510 }
510 }
511
511
512 .close {
512 .close {
513 float: right;
513 float: right;
514 font-size: 21px;
514 font-size: 21px;
515 font-family: @text-bootstrap;
515 font-family: @text-bootstrap;
516 line-height: 1em;
516 line-height: 1em;
517 font-weight: bold;
517 font-weight: bold;
518 color: @grey2;
518 color: @grey2;
519
519
520 &:hover,
520 &:hover,
521 &:focus {
521 &:focus {
522 color: @grey1;
522 color: @grey1;
523 text-decoration: none;
523 text-decoration: none;
524 cursor: pointer;
524 cursor: pointer;
525 }
525 }
526 }
526 }
527
527
528 // GRID
528 // GRID
529 .sorting,
529 .sorting,
530 .sorting_desc,
530 .sorting_desc,
531 .sorting_asc {
531 .sorting_asc {
532 cursor: pointer;
532 cursor: pointer;
533 }
533 }
534 .sorting_desc:after {
534 .sorting_desc:after {
535 content: "\00A0\25B2";
535 content: "\00A0\25B2";
536 font-size: .75em;
536 font-size: .75em;
537 }
537 }
538 .sorting_asc:after {
538 .sorting_asc:after {
539 content: "\00A0\25BC";
539 content: "\00A0\25BC";
540 font-size: .68em;
540 font-size: .68em;
541 }
541 }
542
542
543
543
544 .user_auth_tokens {
544 .user_auth_tokens {
545
545
546 &.truncate {
546 &.truncate {
547 white-space: nowrap;
547 white-space: nowrap;
548 overflow: hidden;
548 overflow: hidden;
549 text-overflow: ellipsis;
549 text-overflow: ellipsis;
550 }
550 }
551
551
552 .fields .field .input {
552 .fields .field .input {
553 margin: 0;
553 margin: 0;
554 }
554 }
555
555
556 input#description {
556 input#description {
557 width: 100px;
557 width: 100px;
558 margin: 0;
558 margin: 0;
559 }
559 }
560
560
561 .drop-menu {
561 .drop-menu {
562 // TODO: johbo: Remove this, should work out of the box when
562 // TODO: johbo: Remove this, should work out of the box when
563 // having multiple inputs inline
563 // having multiple inputs inline
564 margin: 0 0 0 5px;
564 margin: 0 0 0 5px;
565 }
565 }
566 }
566 }
567 #user_list_table {
567 #user_list_table {
568 .closed {
568 .closed {
569 background-color: @grey6;
569 background-color: @grey6;
570 }
570 }
571 }
571 }
572
572
573
573
574 input {
574 input {
575 &.disabled {
575 &.disabled {
576 opacity: .5;
576 opacity: .5;
577 }
577 }
578 }
578 }
579
579
580 // remove extra padding in firefox
580 // remove extra padding in firefox
581 input::-moz-focus-inner { border:0; padding:0 }
581 input::-moz-focus-inner { border:0; padding:0 }
582
582
583 .adjacent input {
583 .adjacent input {
584 margin-bottom: @padding;
584 margin-bottom: @padding;
585 }
585 }
586
586
587 .permissions_boxes {
587 .permissions_boxes {
588 display: block;
588 display: block;
589 }
589 }
590
590
591 //TODO: lisa: this should be in tables
591 //TODO: lisa: this should be in tables
592 .show_more_col {
592 .show_more_col {
593 width: 20px;
593 width: 20px;
594 }
594 }
595
595
596 //FORMS
596 //FORMS
597
597
598 .medium-inline,
598 .medium-inline,
599 input#description.medium-inline {
599 input#description.medium-inline {
600 display: inline;
600 display: inline;
601 width: @medium-inline-input-width;
601 width: @medium-inline-input-width;
602 min-width: 100px;
602 min-width: 100px;
603 }
603 }
604
604
605 select {
605 select {
606 //reset
606 //reset
607 -webkit-appearance: none;
607 -webkit-appearance: none;
608 -moz-appearance: none;
608 -moz-appearance: none;
609
609
610 display: inline-block;
610 display: inline-block;
611 height: 28px;
611 height: 28px;
612 width: auto;
612 width: auto;
613 margin: 0 @padding @padding 0;
613 margin: 0 @padding @padding 0;
614 padding: 0 18px 0 8px;
614 padding: 0 18px 0 8px;
615 line-height:1em;
615 line-height:1em;
616 font-size: @basefontsize;
616 font-size: @basefontsize;
617 border: @border-thickness solid @rcblue;
617 border: @border-thickness solid @rcblue;
618 background:white url("../images/dt-arrow-dn.png") no-repeat 100% 50%;
618 background:white url("../images/dt-arrow-dn.png") no-repeat 100% 50%;
619 color: @rcblue;
619 color: @rcblue;
620
620
621 &:after {
621 &:after {
622 content: "\00A0\25BE";
622 content: "\00A0\25BE";
623 }
623 }
624
624
625 &:focus {
625 &:focus {
626 outline: none;
626 outline: none;
627 }
627 }
628 }
628 }
629
629
630 option {
630 option {
631 &:focus {
631 &:focus {
632 outline: none;
632 outline: none;
633 }
633 }
634 }
634 }
635
635
636 input,
636 input,
637 textarea {
637 textarea {
638 padding: @input-padding;
638 padding: @input-padding;
639 border: @input-border-thickness solid @border-highlight-color;
639 border: @input-border-thickness solid @border-highlight-color;
640 .border-radius (@border-radius);
640 .border-radius (@border-radius);
641 font-family: @text-light;
641 font-family: @text-light;
642 font-size: @basefontsize;
642 font-size: @basefontsize;
643
643
644 &.input-sm {
644 &.input-sm {
645 padding: 5px;
645 padding: 5px;
646 }
646 }
647
647
648 &#description {
648 &#description {
649 min-width: @input-description-minwidth;
649 min-width: @input-description-minwidth;
650 min-height: 1em;
650 min-height: 1em;
651 padding: 10px;
651 padding: 10px;
652 }
652 }
653 }
653 }
654
654
655 .field-sm {
655 .field-sm {
656 input,
656 input,
657 textarea {
657 textarea {
658 padding: 5px;
658 padding: 5px;
659 }
659 }
660 }
660 }
661
661
662 textarea {
662 textarea {
663 display: block;
663 display: block;
664 clear: both;
664 clear: both;
665 width: 100%;
665 width: 100%;
666 min-height: 100px;
666 min-height: 100px;
667 margin-bottom: @padding;
667 margin-bottom: @padding;
668 .box-sizing(border-box);
668 .box-sizing(border-box);
669 overflow: auto;
669 overflow: auto;
670 }
670 }
671
671
672 label {
672 label {
673 font-family: @text-light;
673 font-family: @text-light;
674 }
674 }
675
675
676 // GRAVATARS
676 // GRAVATARS
677 // centers gravatar on username to the right
677 // centers gravatar on username to the right
678
678
679 .gravatar {
679 .gravatar {
680 display: inline;
680 display: inline;
681 min-width: 16px;
681 min-width: 16px;
682 min-height: 16px;
682 min-height: 16px;
683 margin: -5px 0;
683 margin: -5px 0;
684 padding: 0;
684 padding: 0;
685 line-height: 1em;
685 line-height: 1em;
686 border: 1px solid @grey4;
686 border: 1px solid @grey4;
687
687
688 &.gravatar-large {
688 &.gravatar-large {
689 margin: -0.5em .25em -0.5em 0;
689 margin: -0.5em .25em -0.5em 0;
690 }
690 }
691
691
692 & + .user {
692 & + .user {
693 display: inline;
693 display: inline;
694 margin: 0;
694 margin: 0;
695 padding: 0 0 0 .17em;
695 padding: 0 0 0 .17em;
696 line-height: 1em;
696 line-height: 1em;
697 }
697 }
698 }
698 }
699
699
700 .user-inline-data {
700 .user-inline-data {
701 display: inline-block;
701 display: inline-block;
702 float: left;
702 float: left;
703 padding-left: .5em;
703 padding-left: .5em;
704 line-height: 1.3em;
704 line-height: 1.3em;
705 }
705 }
706
706
707 .rc-user { // gravatar + user wrapper
707 .rc-user { // gravatar + user wrapper
708 float: left;
708 float: left;
709 position: relative;
709 position: relative;
710 min-width: 100px;
710 min-width: 100px;
711 max-width: 200px;
711 max-width: 200px;
712 min-height: (@gravatar-size + @border-thickness * 2); // account for border
712 min-height: (@gravatar-size + @border-thickness * 2); // account for border
713 display: block;
713 display: block;
714 padding: 0 0 0 (@gravatar-size + @basefontsize/2 + @border-thickness * 2);
714 padding: 0 0 0 (@gravatar-size + @basefontsize/2 + @border-thickness * 2);
715
715
716
716
717 .gravatar {
717 .gravatar {
718 display: block;
718 display: block;
719 position: absolute;
719 position: absolute;
720 top: 0;
720 top: 0;
721 left: 0;
721 left: 0;
722 min-width: @gravatar-size;
722 min-width: @gravatar-size;
723 min-height: @gravatar-size;
723 min-height: @gravatar-size;
724 margin: 0;
724 margin: 0;
725 }
725 }
726
726
727 .user {
727 .user {
728 display: block;
728 display: block;
729 max-width: 175px;
729 max-width: 175px;
730 padding-top: 2px;
730 padding-top: 2px;
731 overflow: hidden;
731 overflow: hidden;
732 text-overflow: ellipsis;
732 text-overflow: ellipsis;
733 }
733 }
734 }
734 }
735
735
736 .gist-gravatar,
736 .gist-gravatar,
737 .journal_container {
737 .journal_container {
738 .gravatar-large {
738 .gravatar-large {
739 margin: 0 .5em -10px 0;
739 margin: 0 .5em -10px 0;
740 }
740 }
741 }
741 }
742
742
743
743
744 // ADMIN SETTINGS
744 // ADMIN SETTINGS
745
745
746 // Tag Patterns
746 // Tag Patterns
747 .tag_patterns {
747 .tag_patterns {
748 .tag_input {
748 .tag_input {
749 margin-bottom: @padding;
749 margin-bottom: @padding;
750 }
750 }
751 }
751 }
752
752
753 .locked_input {
753 .locked_input {
754 position: relative;
754 position: relative;
755
755
756 input {
756 input {
757 display: inline;
757 display: inline;
758 margin-top: 3px;
758 margin-top: 3px;
759 }
759 }
760
760
761 br {
761 br {
762 display: none;
762 display: none;
763 }
763 }
764
764
765 .error-message {
765 .error-message {
766 float: left;
766 float: left;
767 width: 100%;
767 width: 100%;
768 }
768 }
769
769
770 .lock_input_button {
770 .lock_input_button {
771 display: inline;
771 display: inline;
772 }
772 }
773
773
774 .help-block {
774 .help-block {
775 clear: both;
775 clear: both;
776 }
776 }
777 }
777 }
778
778
779 // Notifications
779 // Notifications
780
780
781 .notifications_buttons {
781 .notifications_buttons {
782 margin: 0 0 @space 0;
782 margin: 0 0 @space 0;
783 padding: 0;
783 padding: 0;
784
784
785 .btn {
785 .btn {
786 display: inline-block;
786 display: inline-block;
787 }
787 }
788 }
788 }
789
789
790 .notification-list {
790 .notification-list {
791
791
792 div {
792 div {
793 display: inline-block;
793 display: inline-block;
794 vertical-align: middle;
794 vertical-align: middle;
795 }
795 }
796
796
797 .container {
797 .container {
798 display: block;
798 display: block;
799 margin: 0 0 @padding 0;
799 margin: 0 0 @padding 0;
800 }
800 }
801
801
802 .delete-notifications {
802 .delete-notifications {
803 margin-left: @padding;
803 margin-left: @padding;
804 text-align: right;
804 text-align: right;
805 cursor: pointer;
805 cursor: pointer;
806 }
806 }
807
807
808 .read-notifications {
808 .read-notifications {
809 margin-left: @padding/2;
809 margin-left: @padding/2;
810 text-align: right;
810 text-align: right;
811 width: 35px;
811 width: 35px;
812 cursor: pointer;
812 cursor: pointer;
813 }
813 }
814
814
815 .icon-minus-sign {
815 .icon-minus-sign {
816 color: @alert2;
816 color: @alert2;
817 }
817 }
818
818
819 .icon-ok-sign {
819 .icon-ok-sign {
820 color: @alert1;
820 color: @alert1;
821 }
821 }
822 }
822 }
823
823
824 .user_settings {
824 .user_settings {
825 float: left;
825 float: left;
826 clear: both;
826 clear: both;
827 display: block;
827 display: block;
828 width: 100%;
828 width: 100%;
829
829
830 .gravatar_box {
830 .gravatar_box {
831 margin-bottom: @padding;
831 margin-bottom: @padding;
832
832
833 &:after {
833 &:after {
834 content: " ";
834 content: " ";
835 clear: both;
835 clear: both;
836 width: 100%;
836 width: 100%;
837 }
837 }
838 }
838 }
839
839
840 .fields .field {
840 .fields .field {
841 clear: both;
841 clear: both;
842 }
842 }
843 }
843 }
844
844
845 .advanced_settings {
845 .advanced_settings {
846 margin-bottom: @space;
846 margin-bottom: @space;
847
847
848 .help-block {
848 .help-block {
849 margin-left: 0;
849 margin-left: 0;
850 }
850 }
851
851
852 button + .help-block {
852 button + .help-block {
853 margin-top: @padding;
853 margin-top: @padding;
854 }
854 }
855 }
855 }
856
856
857 // admin settings radio buttons and labels
857 // admin settings radio buttons and labels
858 .label-2 {
858 .label-2 {
859 float: left;
859 float: left;
860 width: @label2-width;
860 width: @label2-width;
861
861
862 label {
862 label {
863 color: @grey1;
863 color: @grey1;
864 }
864 }
865 }
865 }
866 .checkboxes {
866 .checkboxes {
867 float: left;
867 float: left;
868 width: @checkboxes-width;
868 width: @checkboxes-width;
869 margin-bottom: @padding;
869 margin-bottom: @padding;
870
870
871 .checkbox {
871 .checkbox {
872 width: 100%;
872 width: 100%;
873
873
874 label {
874 label {
875 margin: 0;
875 margin: 0;
876 padding: 0;
876 padding: 0;
877 }
877 }
878 }
878 }
879
879
880 .checkbox + .checkbox {
880 .checkbox + .checkbox {
881 display: inline-block;
881 display: inline-block;
882 }
882 }
883
883
884 label {
884 label {
885 margin-right: 1em;
885 margin-right: 1em;
886 }
886 }
887 }
887 }
888
888
889 // CHANGELOG
889 // CHANGELOG
890 .container_header {
890 .container_header {
891 float: left;
891 float: left;
892 display: block;
892 display: block;
893 width: 100%;
893 width: 100%;
894 margin: @padding 0 @padding;
894 margin: @padding 0 @padding;
895
895
896 #filter_changelog {
896 #filter_changelog {
897 float: left;
897 float: left;
898 margin-right: @padding;
898 margin-right: @padding;
899 }
899 }
900
900
901 .breadcrumbs_light {
901 .breadcrumbs_light {
902 display: inline-block;
902 display: inline-block;
903 }
903 }
904 }
904 }
905
905
906 .info_box {
906 .info_box {
907 float: right;
907 float: right;
908 }
908 }
909
909
910
910
911 #graph_nodes {
911 #graph_nodes {
912 padding-top: 43px;
912 padding-top: 43px;
913 }
913 }
914
914
915 #graph_content{
915 #graph_content{
916
916
917 // adjust for table headers so that graph renders properly
917 // adjust for table headers so that graph renders properly
918 // #graph_nodes padding - table cell padding
918 // #graph_nodes padding - table cell padding
919 padding-top: (@space - (@basefontsize * 2.4));
919 padding-top: (@space - (@basefontsize * 2.4));
920
920
921 &.graph_full_width {
921 &.graph_full_width {
922 width: 100%;
922 width: 100%;
923 max-width: 100%;
923 max-width: 100%;
924 }
924 }
925 }
925 }
926
926
927 #graph {
927 #graph {
928 .flag_status {
928 .flag_status {
929 margin: 0;
929 margin: 0;
930 }
930 }
931
931
932 .pagination-left {
932 .pagination-left {
933 float: left;
933 float: left;
934 clear: both;
934 clear: both;
935 }
935 }
936
936
937 .log-container {
937 .log-container {
938 max-width: 345px;
938 max-width: 345px;
939
939
940 .message{
940 .message{
941 max-width: 340px;
941 max-width: 340px;
942 }
942 }
943 }
943 }
944
944
945 .graph-col-wrapper {
945 .graph-col-wrapper {
946 padding-left: 110px;
946 padding-left: 110px;
947
947
948 #graph_nodes {
948 #graph_nodes {
949 width: 100px;
949 width: 100px;
950 margin-left: -110px;
950 margin-left: -110px;
951 float: left;
951 float: left;
952 clear: left;
952 clear: left;
953 }
953 }
954 }
954 }
955 }
955 }
956
956
957 #filter_changelog {
957 #filter_changelog {
958 float: left;
958 float: left;
959 }
959 }
960
960
961
961
962 //--- THEME ------------------//
962 //--- THEME ------------------//
963
963
964 #logo {
964 #logo {
965 float: left;
965 float: left;
966 margin: 9px 0 0 0;
966 margin: 9px 0 0 0;
967
967
968 .header {
968 .header {
969 background-color: transparent;
969 background-color: transparent;
970 }
970 }
971
971
972 a {
972 a {
973 display: inline-block;
973 display: inline-block;
974 }
974 }
975
975
976 img {
976 img {
977 height:30px;
977 height:30px;
978 }
978 }
979 }
979 }
980
980
981 .logo-wrapper {
981 .logo-wrapper {
982 float:left;
982 float:left;
983 }
983 }
984
984
985 .branding{
985 .branding{
986 float: left;
986 float: left;
987 padding: 9px 2px;
987 padding: 9px 2px;
988 line-height: 1em;
988 line-height: 1em;
989 font-size: @navigation-fontsize;
989 font-size: @navigation-fontsize;
990 }
990 }
991
991
992 img {
992 img {
993 border: none;
993 border: none;
994 outline: none;
994 outline: none;
995 }
995 }
996 user-profile-header
996 user-profile-header
997 label {
997 label {
998
998
999 input[type="checkbox"] {
999 input[type="checkbox"] {
1000 margin-right: 1em;
1000 margin-right: 1em;
1001 }
1001 }
1002 input[type="radio"] {
1002 input[type="radio"] {
1003 margin-right: 1em;
1003 margin-right: 1em;
1004 }
1004 }
1005 }
1005 }
1006
1006
1007 .flag_status {
1007 .flag_status {
1008 margin: 2px 8px 6px 2px;
1008 margin: 2px 8px 6px 2px;
1009 &.under_review {
1009 &.under_review {
1010 .circle(5px, @alert3);
1010 .circle(5px, @alert3);
1011 }
1011 }
1012 &.approved {
1012 &.approved {
1013 .circle(5px, @alert1);
1013 .circle(5px, @alert1);
1014 }
1014 }
1015 &.rejected,
1015 &.rejected,
1016 &.forced_closed{
1016 &.forced_closed{
1017 .circle(5px, @alert2);
1017 .circle(5px, @alert2);
1018 }
1018 }
1019 &.not_reviewed {
1019 &.not_reviewed {
1020 .circle(5px, @grey5);
1020 .circle(5px, @grey5);
1021 }
1021 }
1022 }
1022 }
1023
1023
1024 .flag_status_comment_box {
1024 .flag_status_comment_box {
1025 margin: 5px 6px 0px 2px;
1025 margin: 5px 6px 0px 2px;
1026 }
1026 }
1027 .test_pattern_preview {
1027 .test_pattern_preview {
1028 margin: @space 0;
1028 margin: @space 0;
1029
1029
1030 p {
1030 p {
1031 margin-bottom: 0;
1031 margin-bottom: 0;
1032 border-bottom: @border-thickness solid @border-default-color;
1032 border-bottom: @border-thickness solid @border-default-color;
1033 color: @grey3;
1033 color: @grey3;
1034 }
1034 }
1035
1035
1036 .btn {
1036 .btn {
1037 margin-bottom: @padding;
1037 margin-bottom: @padding;
1038 }
1038 }
1039 }
1039 }
1040 #test_pattern_result {
1040 #test_pattern_result {
1041 display: none;
1041 display: none;
1042 &:extend(pre);
1042 &:extend(pre);
1043 padding: .9em;
1043 padding: .9em;
1044 color: @grey3;
1044 color: @grey3;
1045 background-color: @grey7;
1045 background-color: @grey7;
1046 border-right: @border-thickness solid @border-default-color;
1046 border-right: @border-thickness solid @border-default-color;
1047 border-bottom: @border-thickness solid @border-default-color;
1047 border-bottom: @border-thickness solid @border-default-color;
1048 border-left: @border-thickness solid @border-default-color;
1048 border-left: @border-thickness solid @border-default-color;
1049 }
1049 }
1050
1050
1051 #repo_vcs_settings {
1051 #repo_vcs_settings {
1052 #inherit_overlay_vcs_default {
1052 #inherit_overlay_vcs_default {
1053 display: none;
1053 display: none;
1054 }
1054 }
1055 #inherit_overlay_vcs_custom {
1055 #inherit_overlay_vcs_custom {
1056 display: custom;
1056 display: custom;
1057 }
1057 }
1058 &.inherited {
1058 &.inherited {
1059 #inherit_overlay_vcs_default {
1059 #inherit_overlay_vcs_default {
1060 display: block;
1060 display: block;
1061 }
1061 }
1062 #inherit_overlay_vcs_custom {
1062 #inherit_overlay_vcs_custom {
1063 display: none;
1063 display: none;
1064 }
1064 }
1065 }
1065 }
1066 }
1066 }
1067
1067
1068 .issue-tracker-link {
1068 .issue-tracker-link {
1069 color: @rcblue;
1069 color: @rcblue;
1070 }
1070 }
1071
1071
1072 // Issue Tracker Table Show/Hide
1072 // Issue Tracker Table Show/Hide
1073 #repo_issue_tracker {
1073 #repo_issue_tracker {
1074 #inherit_overlay {
1074 #inherit_overlay {
1075 display: none;
1075 display: none;
1076 }
1076 }
1077 #custom_overlay {
1077 #custom_overlay {
1078 display: custom;
1078 display: custom;
1079 }
1079 }
1080 &.inherited {
1080 &.inherited {
1081 #inherit_overlay {
1081 #inherit_overlay {
1082 display: block;
1082 display: block;
1083 }
1083 }
1084 #custom_overlay {
1084 #custom_overlay {
1085 display: none;
1085 display: none;
1086 }
1086 }
1087 }
1087 }
1088 }
1088 }
1089 table.issuetracker {
1089 table.issuetracker {
1090 &.readonly {
1090 &.readonly {
1091 tr, td {
1091 tr, td {
1092 color: @grey3;
1092 color: @grey3;
1093 }
1093 }
1094 }
1094 }
1095 .edit {
1095 .edit {
1096 display: none;
1096 display: none;
1097 }
1097 }
1098 .editopen {
1098 .editopen {
1099 .edit {
1099 .edit {
1100 display: inline;
1100 display: inline;
1101 }
1101 }
1102 .entry {
1102 .entry {
1103 display: none;
1103 display: none;
1104 }
1104 }
1105 }
1105 }
1106 tr td.td-action {
1106 tr td.td-action {
1107 min-width: 117px;
1107 min-width: 117px;
1108 }
1108 }
1109 td input {
1109 td input {
1110 max-width: none;
1110 max-width: none;
1111 min-width: 30px;
1111 min-width: 30px;
1112 width: 80%;
1112 width: 80%;
1113 }
1113 }
1114 .issuetracker_pref input {
1114 .issuetracker_pref input {
1115 width: 40%;
1115 width: 40%;
1116 }
1116 }
1117 input.edit_issuetracker_update {
1117 input.edit_issuetracker_update {
1118 margin-right: 0;
1118 margin-right: 0;
1119 width: auto;
1119 width: auto;
1120 }
1120 }
1121 }
1121 }
1122
1122
1123 table.integrations {
1123 table.integrations {
1124 .td-icon {
1124 .td-icon {
1125 width: 20px;
1125 width: 20px;
1126 .integration-icon {
1126 .integration-icon {
1127 height: 20px;
1127 height: 20px;
1128 width: 20px;
1128 width: 20px;
1129 }
1129 }
1130 }
1130 }
1131 }
1131 }
1132
1132
1133 .integrations {
1133 .integrations {
1134 a.integration-box {
1134 a.integration-box {
1135 color: @text-color;
1135 color: @text-color;
1136 &:hover {
1136 &:hover {
1137 .panel {
1137 .panel {
1138 background: #fbfbfb;
1138 background: #fbfbfb;
1139 }
1139 }
1140 }
1140 }
1141 .integration-icon {
1141 .integration-icon {
1142 width: 30px;
1142 width: 30px;
1143 height: 30px;
1143 height: 30px;
1144 margin-right: 20px;
1144 margin-right: 20px;
1145 float: left;
1145 float: left;
1146 }
1146 }
1147
1147
1148 .panel-body {
1148 .panel-body {
1149 padding: 10px;
1149 padding: 10px;
1150 }
1150 }
1151 .panel {
1151 .panel {
1152 margin-bottom: 10px;
1152 margin-bottom: 10px;
1153 }
1153 }
1154 h2 {
1154 h2 {
1155 display: inline-block;
1155 display: inline-block;
1156 margin: 0;
1156 margin: 0;
1157 min-width: 140px;
1157 min-width: 140px;
1158 }
1158 }
1159 }
1159 }
1160 }
1160 }
1161
1161
1162 //Permissions Settings
1162 //Permissions Settings
1163 #add_perm {
1163 #add_perm {
1164 margin: 0 0 @padding;
1164 margin: 0 0 @padding;
1165 cursor: pointer;
1165 cursor: pointer;
1166 }
1166 }
1167
1167
1168 .perm_ac {
1168 .perm_ac {
1169 input {
1169 input {
1170 width: 95%;
1170 width: 95%;
1171 }
1171 }
1172 }
1172 }
1173
1173
1174 .autocomplete-suggestions {
1174 .autocomplete-suggestions {
1175 width: auto !important; // overrides autocomplete.js
1175 width: auto !important; // overrides autocomplete.js
1176 margin: 0;
1176 margin: 0;
1177 border: @border-thickness solid @rcblue;
1177 border: @border-thickness solid @rcblue;
1178 border-radius: @border-radius;
1178 border-radius: @border-radius;
1179 color: @rcblue;
1179 color: @rcblue;
1180 background-color: white;
1180 background-color: white;
1181 }
1181 }
1182 .autocomplete-selected {
1182 .autocomplete-selected {
1183 background: #F0F0F0;
1183 background: #F0F0F0;
1184 }
1184 }
1185 .ac-container-wrap {
1185 .ac-container-wrap {
1186 margin: 0;
1186 margin: 0;
1187 padding: 8px;
1187 padding: 8px;
1188 border-bottom: @border-thickness solid @rclightblue;
1188 border-bottom: @border-thickness solid @rclightblue;
1189 list-style-type: none;
1189 list-style-type: none;
1190 cursor: pointer;
1190 cursor: pointer;
1191
1191
1192 &:hover {
1192 &:hover {
1193 background-color: @rclightblue;
1193 background-color: @rclightblue;
1194 }
1194 }
1195
1195
1196 img {
1196 img {
1197 height: @gravatar-size;
1197 height: @gravatar-size;
1198 width: @gravatar-size;
1198 width: @gravatar-size;
1199 margin-right: 1em;
1199 margin-right: 1em;
1200 }
1200 }
1201
1201
1202 strong {
1202 strong {
1203 font-weight: normal;
1203 font-weight: normal;
1204 }
1204 }
1205 }
1205 }
1206
1206
1207 // Settings Dropdown
1207 // Settings Dropdown
1208 .user-menu .container {
1208 .user-menu .container {
1209 padding: 0 4px;
1209 padding: 0 4px;
1210 margin: 0;
1210 margin: 0;
1211 }
1211 }
1212
1212
1213 .user-menu .gravatar {
1213 .user-menu .gravatar {
1214 cursor: pointer;
1214 cursor: pointer;
1215 }
1215 }
1216
1216
1217 .codeblock {
1217 .codeblock {
1218 margin-bottom: @padding;
1218 margin-bottom: @padding;
1219 clear: both;
1219 clear: both;
1220
1220
1221 .stats{
1221 .stats{
1222 overflow: hidden;
1222 overflow: hidden;
1223 }
1223 }
1224
1224
1225 .message{
1225 .message{
1226 textarea{
1226 textarea{
1227 margin: 0;
1227 margin: 0;
1228 }
1228 }
1229 }
1229 }
1230
1230
1231 .code-header {
1231 .code-header {
1232 .stats {
1232 .stats {
1233 line-height: 2em;
1233 line-height: 2em;
1234
1234
1235 .revision_id {
1235 .revision_id {
1236 margin-left: 0;
1236 margin-left: 0;
1237 }
1237 }
1238 .buttons {
1238 .buttons {
1239 padding-right: 0;
1239 padding-right: 0;
1240 }
1240 }
1241 }
1241 }
1242
1242
1243 .item{
1243 .item{
1244 margin-right: 0.5em;
1244 margin-right: 0.5em;
1245 }
1245 }
1246 }
1246 }
1247
1247
1248 #editor_container{
1248 #editor_container{
1249 position: relative;
1249 position: relative;
1250 margin: @padding;
1250 margin: @padding;
1251 }
1251 }
1252 }
1252 }
1253
1253
1254 #file_history_container {
1254 #file_history_container {
1255 display: none;
1255 display: none;
1256 }
1256 }
1257
1257
1258 .file-history-inner {
1258 .file-history-inner {
1259 margin-bottom: 10px;
1259 margin-bottom: 10px;
1260 }
1260 }
1261
1261
1262 // Pull Requests
1262 // Pull Requests
1263 .summary-details {
1263 .summary-details {
1264 width: 72%;
1264 width: 72%;
1265 }
1265 }
1266 .pr-summary {
1266 .pr-summary {
1267 border-bottom: @border-thickness solid @grey5;
1267 border-bottom: @border-thickness solid @grey5;
1268 margin-bottom: @space;
1268 margin-bottom: @space;
1269 }
1269 }
1270 .reviewers-title {
1270 .reviewers-title {
1271 width: 25%;
1271 width: 25%;
1272 min-width: 200px;
1272 min-width: 200px;
1273 }
1273 }
1274 .reviewers {
1274 .reviewers {
1275 width: 25%;
1275 width: 25%;
1276 min-width: 200px;
1276 min-width: 200px;
1277 }
1277 }
1278 .reviewers ul li {
1278 .reviewers ul li {
1279 position: relative;
1279 position: relative;
1280 width: 100%;
1280 width: 100%;
1281 margin-bottom: 8px;
1281 margin-bottom: 8px;
1282 }
1282 }
1283 .reviewers_member {
1283 .reviewers_member {
1284 width: 100%;
1284 width: 100%;
1285 overflow: auto;
1285 overflow: auto;
1286 }
1286 }
1287 .reviewer_reason {
1287 .reviewer_reason {
1288 padding-left: 20px;
1288 padding-left: 20px;
1289 }
1289 }
1290 .reviewer_status {
1290 .reviewer_status {
1291 display: inline-block;
1291 display: inline-block;
1292 vertical-align: top;
1292 vertical-align: top;
1293 width: 7%;
1293 width: 7%;
1294 min-width: 20px;
1294 min-width: 20px;
1295 height: 1.2em;
1295 height: 1.2em;
1296 margin-top: 3px;
1296 margin-top: 3px;
1297 line-height: 1em;
1297 line-height: 1em;
1298 }
1298 }
1299
1299
1300 .reviewer_name {
1300 .reviewer_name {
1301 display: inline-block;
1301 display: inline-block;
1302 max-width: 83%;
1302 max-width: 83%;
1303 padding-right: 20px;
1303 padding-right: 20px;
1304 vertical-align: middle;
1304 vertical-align: middle;
1305 line-height: 1;
1305 line-height: 1;
1306
1306
1307 .rc-user {
1307 .rc-user {
1308 min-width: 0;
1308 min-width: 0;
1309 margin: -2px 1em 0 0;
1309 margin: -2px 1em 0 0;
1310 }
1310 }
1311
1311
1312 .reviewer {
1312 .reviewer {
1313 float: left;
1313 float: left;
1314 }
1314 }
1315
1315
1316 &.to-delete {
1316 &.to-delete {
1317 .user,
1317 .user,
1318 .reviewer {
1318 .reviewer {
1319 text-decoration: line-through;
1319 text-decoration: line-through;
1320 }
1320 }
1321 }
1321 }
1322 }
1322 }
1323
1323
1324 .reviewer_member_remove {
1324 .reviewer_member_remove {
1325 position: absolute;
1325 position: absolute;
1326 right: 0;
1326 right: 0;
1327 top: 0;
1327 top: 0;
1328 width: 16px;
1328 width: 16px;
1329 margin-bottom: 10px;
1329 margin-bottom: 10px;
1330 padding: 0;
1330 padding: 0;
1331 color: black;
1331 color: black;
1332 }
1332 }
1333 .reviewer_member_status {
1333 .reviewer_member_status {
1334 margin-top: 5px;
1334 margin-top: 5px;
1335 }
1335 }
1336 .pr-summary #summary{
1336 .pr-summary #summary{
1337 width: 100%;
1337 width: 100%;
1338 }
1338 }
1339 .pr-summary .action_button:hover {
1339 .pr-summary .action_button:hover {
1340 border: 0;
1340 border: 0;
1341 cursor: pointer;
1341 cursor: pointer;
1342 }
1342 }
1343 .pr-details-title {
1343 .pr-details-title {
1344 padding-bottom: 8px;
1344 padding-bottom: 8px;
1345 border-bottom: @border-thickness solid @grey5;
1345 border-bottom: @border-thickness solid @grey5;
1346
1346
1347 .action_button.disabled {
1347 .action_button.disabled {
1348 color: @grey4;
1348 color: @grey4;
1349 cursor: inherit;
1349 cursor: inherit;
1350 }
1350 }
1351 .action_button {
1351 .action_button {
1352 color: @rcblue;
1352 color: @rcblue;
1353 }
1353 }
1354 }
1354 }
1355 .pr-details-content {
1355 .pr-details-content {
1356 margin-top: @textmargin;
1356 margin-top: @textmargin;
1357 margin-bottom: @textmargin;
1357 margin-bottom: @textmargin;
1358 }
1358 }
1359 .pr-description {
1359 .pr-description {
1360 white-space:pre-wrap;
1360 white-space:pre-wrap;
1361 }
1361 }
1362 .group_members {
1362 .group_members {
1363 margin-top: 0;
1363 margin-top: 0;
1364 padding: 0;
1364 padding: 0;
1365 list-style: outside none none;
1365 list-style: outside none none;
1366
1366
1367 img {
1367 img {
1368 height: @gravatar-size;
1368 height: @gravatar-size;
1369 width: @gravatar-size;
1369 width: @gravatar-size;
1370 margin-right: .5em;
1370 margin-right: .5em;
1371 margin-left: 3px;
1371 margin-left: 3px;
1372 }
1372 }
1373
1374 .to-delete {
1375 .user {
1376 text-decoration: line-through;
1373 }
1377 }
1378 }
1379 }
1380
1381 // new entry in group_members
1382 .td-author-new-entry {
1383 background-color: rgba(red(@alert1), green(@alert1), blue(@alert1), 0.3);
1384 }
1385
1386 .usergroup_member_remove {
1387 width: 16px;
1388 margin-bottom: 10px;
1389 padding: 0;
1390 color: black !important;
1391 cursor: pointer;
1392 }
1393
1374 .reviewer_ac .ac-input {
1394 .reviewer_ac .ac-input {
1375 width: 92%;
1395 width: 92%;
1376 margin-bottom: 1em;
1396 margin-bottom: 1em;
1377 }
1397 }
1378 #update_commits {
1398 #update_commits {
1379 float: right;
1399 float: right;
1380 }
1400 }
1381 .compare_view_commits tr{
1401 .compare_view_commits tr{
1382 height: 20px;
1402 height: 20px;
1383 }
1403 }
1384 .compare_view_commits td {
1404 .compare_view_commits td {
1385 vertical-align: top;
1405 vertical-align: top;
1386 padding-top: 10px;
1406 padding-top: 10px;
1387 }
1407 }
1388 .compare_view_commits .author {
1408 .compare_view_commits .author {
1389 margin-left: 5px;
1409 margin-left: 5px;
1390 }
1410 }
1391
1411
1392 .compare_view_files {
1412 .compare_view_files {
1393 width: 100%;
1413 width: 100%;
1394
1414
1395 td {
1415 td {
1396 vertical-align: middle;
1416 vertical-align: middle;
1397 }
1417 }
1398 }
1418 }
1399
1419
1400 .compare_view_filepath {
1420 .compare_view_filepath {
1401 color: @grey1;
1421 color: @grey1;
1402 }
1422 }
1403
1423
1404 .show_more {
1424 .show_more {
1405 display: inline-block;
1425 display: inline-block;
1406 position: relative;
1426 position: relative;
1407 vertical-align: middle;
1427 vertical-align: middle;
1408 width: 4px;
1428 width: 4px;
1409 height: @basefontsize;
1429 height: @basefontsize;
1410
1430
1411 &:after {
1431 &:after {
1412 content: "\00A0\25BE";
1432 content: "\00A0\25BE";
1413 display: inline-block;
1433 display: inline-block;
1414 width:10px;
1434 width:10px;
1415 line-height: 5px;
1435 line-height: 5px;
1416 font-size: 12px;
1436 font-size: 12px;
1417 cursor: pointer;
1437 cursor: pointer;
1418 }
1438 }
1419 }
1439 }
1420
1440
1421 .journal_more .show_more {
1441 .journal_more .show_more {
1422 display: inline;
1442 display: inline;
1423
1443
1424 &:after {
1444 &:after {
1425 content: none;
1445 content: none;
1426 }
1446 }
1427 }
1447 }
1428
1448
1429 .open .show_more:after,
1449 .open .show_more:after,
1430 .select2-dropdown-open .show_more:after {
1450 .select2-dropdown-open .show_more:after {
1431 .rotate(180deg);
1451 .rotate(180deg);
1432 margin-left: 4px;
1452 margin-left: 4px;
1433 }
1453 }
1434
1454
1435
1455
1436 .compare_view_commits .collapse_commit:after {
1456 .compare_view_commits .collapse_commit:after {
1437 cursor: pointer;
1457 cursor: pointer;
1438 content: "\00A0\25B4";
1458 content: "\00A0\25B4";
1439 margin-left: -3px;
1459 margin-left: -3px;
1440 font-size: 17px;
1460 font-size: 17px;
1441 color: @grey4;
1461 color: @grey4;
1442 }
1462 }
1443
1463
1444 .diff_links {
1464 .diff_links {
1445 margin-left: 8px;
1465 margin-left: 8px;
1446 }
1466 }
1447
1467
1448 p.ancestor {
1468 p.ancestor {
1449 margin: @padding 0;
1469 margin: @padding 0;
1450 }
1470 }
1451
1471
1452 .cs_icon_td input[type="checkbox"] {
1472 .cs_icon_td input[type="checkbox"] {
1453 display: none;
1473 display: none;
1454 }
1474 }
1455
1475
1456 .cs_icon_td .expand_file_icon:after {
1476 .cs_icon_td .expand_file_icon:after {
1457 cursor: pointer;
1477 cursor: pointer;
1458 content: "\00A0\25B6";
1478 content: "\00A0\25B6";
1459 font-size: 12px;
1479 font-size: 12px;
1460 color: @grey4;
1480 color: @grey4;
1461 }
1481 }
1462
1482
1463 .cs_icon_td .collapse_file_icon:after {
1483 .cs_icon_td .collapse_file_icon:after {
1464 cursor: pointer;
1484 cursor: pointer;
1465 content: "\00A0\25BC";
1485 content: "\00A0\25BC";
1466 font-size: 12px;
1486 font-size: 12px;
1467 color: @grey4;
1487 color: @grey4;
1468 }
1488 }
1469
1489
1470 /*new binary
1490 /*new binary
1471 NEW_FILENODE = 1
1491 NEW_FILENODE = 1
1472 DEL_FILENODE = 2
1492 DEL_FILENODE = 2
1473 MOD_FILENODE = 3
1493 MOD_FILENODE = 3
1474 RENAMED_FILENODE = 4
1494 RENAMED_FILENODE = 4
1475 COPIED_FILENODE = 5
1495 COPIED_FILENODE = 5
1476 CHMOD_FILENODE = 6
1496 CHMOD_FILENODE = 6
1477 BIN_FILENODE = 7
1497 BIN_FILENODE = 7
1478 */
1498 */
1479 .cs_files_expand {
1499 .cs_files_expand {
1480 font-size: @basefontsize + 5px;
1500 font-size: @basefontsize + 5px;
1481 line-height: 1.8em;
1501 line-height: 1.8em;
1482 float: right;
1502 float: right;
1483 }
1503 }
1484
1504
1485 .cs_files_expand span{
1505 .cs_files_expand span{
1486 color: @rcblue;
1506 color: @rcblue;
1487 cursor: pointer;
1507 cursor: pointer;
1488 }
1508 }
1489 .cs_files {
1509 .cs_files {
1490 clear: both;
1510 clear: both;
1491 padding-bottom: @padding;
1511 padding-bottom: @padding;
1492
1512
1493 .cur_cs {
1513 .cur_cs {
1494 margin: 10px 2px;
1514 margin: 10px 2px;
1495 font-weight: bold;
1515 font-weight: bold;
1496 }
1516 }
1497
1517
1498 .node {
1518 .node {
1499 float: left;
1519 float: left;
1500 }
1520 }
1501
1521
1502 .changes {
1522 .changes {
1503 float: right;
1523 float: right;
1504 color: white;
1524 color: white;
1505 font-size: @basefontsize - 4px;
1525 font-size: @basefontsize - 4px;
1506 margin-top: 4px;
1526 margin-top: 4px;
1507 opacity: 0.6;
1527 opacity: 0.6;
1508 filter: Alpha(opacity=60); /* IE8 and earlier */
1528 filter: Alpha(opacity=60); /* IE8 and earlier */
1509
1529
1510 .added {
1530 .added {
1511 background-color: @alert1;
1531 background-color: @alert1;
1512 float: left;
1532 float: left;
1513 text-align: center;
1533 text-align: center;
1514 }
1534 }
1515
1535
1516 .deleted {
1536 .deleted {
1517 background-color: @alert2;
1537 background-color: @alert2;
1518 float: left;
1538 float: left;
1519 text-align: center;
1539 text-align: center;
1520 }
1540 }
1521
1541
1522 .bin {
1542 .bin {
1523 background-color: @alert1;
1543 background-color: @alert1;
1524 text-align: center;
1544 text-align: center;
1525 }
1545 }
1526
1546
1527 /*new binary*/
1547 /*new binary*/
1528 .bin.bin1 {
1548 .bin.bin1 {
1529 background-color: @alert1;
1549 background-color: @alert1;
1530 text-align: center;
1550 text-align: center;
1531 }
1551 }
1532
1552
1533 /*deleted binary*/
1553 /*deleted binary*/
1534 .bin.bin2 {
1554 .bin.bin2 {
1535 background-color: @alert2;
1555 background-color: @alert2;
1536 text-align: center;
1556 text-align: center;
1537 }
1557 }
1538
1558
1539 /*mod binary*/
1559 /*mod binary*/
1540 .bin.bin3 {
1560 .bin.bin3 {
1541 background-color: @grey2;
1561 background-color: @grey2;
1542 text-align: center;
1562 text-align: center;
1543 }
1563 }
1544
1564
1545 /*rename file*/
1565 /*rename file*/
1546 .bin.bin4 {
1566 .bin.bin4 {
1547 background-color: @alert4;
1567 background-color: @alert4;
1548 text-align: center;
1568 text-align: center;
1549 }
1569 }
1550
1570
1551 /*copied file*/
1571 /*copied file*/
1552 .bin.bin5 {
1572 .bin.bin5 {
1553 background-color: @alert4;
1573 background-color: @alert4;
1554 text-align: center;
1574 text-align: center;
1555 }
1575 }
1556
1576
1557 /*chmod file*/
1577 /*chmod file*/
1558 .bin.bin6 {
1578 .bin.bin6 {
1559 background-color: @grey2;
1579 background-color: @grey2;
1560 text-align: center;
1580 text-align: center;
1561 }
1581 }
1562 }
1582 }
1563 }
1583 }
1564
1584
1565 .cs_files .cs_added, .cs_files .cs_A,
1585 .cs_files .cs_added, .cs_files .cs_A,
1566 .cs_files .cs_added, .cs_files .cs_M,
1586 .cs_files .cs_added, .cs_files .cs_M,
1567 .cs_files .cs_added, .cs_files .cs_D {
1587 .cs_files .cs_added, .cs_files .cs_D {
1568 height: 16px;
1588 height: 16px;
1569 padding-right: 10px;
1589 padding-right: 10px;
1570 margin-top: 7px;
1590 margin-top: 7px;
1571 text-align: left;
1591 text-align: left;
1572 }
1592 }
1573
1593
1574 .cs_icon_td {
1594 .cs_icon_td {
1575 min-width: 16px;
1595 min-width: 16px;
1576 width: 16px;
1596 width: 16px;
1577 }
1597 }
1578
1598
1579 .pull-request-merge {
1599 .pull-request-merge {
1580 padding: 10px 0;
1600 padding: 10px 0;
1581 margin-top: 10px;
1601 margin-top: 10px;
1582 margin-bottom: 20px;
1602 margin-bottom: 20px;
1583 }
1603 }
1584
1604
1585 .pull-request-merge .pull-request-wrap {
1605 .pull-request-merge .pull-request-wrap {
1586 height: 25px;
1606 height: 25px;
1587 padding: 5px 0;
1607 padding: 5px 0;
1588 }
1608 }
1589
1609
1590 .pull-request-merge span {
1610 .pull-request-merge span {
1591 margin-right: 10px;
1611 margin-right: 10px;
1592 }
1612 }
1593 #close_pull_request {
1613 #close_pull_request {
1594 margin-right: 0px;
1614 margin-right: 0px;
1595 }
1615 }
1596
1616
1597 .empty_data {
1617 .empty_data {
1598 color: @grey4;
1618 color: @grey4;
1599 }
1619 }
1600
1620
1601 #changeset_compare_view_content {
1621 #changeset_compare_view_content {
1602 margin-bottom: @space;
1622 margin-bottom: @space;
1603 clear: both;
1623 clear: both;
1604 width: 100%;
1624 width: 100%;
1605 box-sizing: border-box;
1625 box-sizing: border-box;
1606 .border-radius(@border-radius);
1626 .border-radius(@border-radius);
1607
1627
1608 .help-block {
1628 .help-block {
1609 margin: @padding 0;
1629 margin: @padding 0;
1610 color: @text-color;
1630 color: @text-color;
1611 }
1631 }
1612
1632
1613 .empty_data {
1633 .empty_data {
1614 margin: @padding 0;
1634 margin: @padding 0;
1615 }
1635 }
1616
1636
1617 .alert {
1637 .alert {
1618 margin-bottom: @space;
1638 margin-bottom: @space;
1619 }
1639 }
1620 }
1640 }
1621
1641
1622 .table_disp {
1642 .table_disp {
1623 .status {
1643 .status {
1624 width: auto;
1644 width: auto;
1625
1645
1626 .flag_status {
1646 .flag_status {
1627 float: left;
1647 float: left;
1628 }
1648 }
1629 }
1649 }
1630 }
1650 }
1631
1651
1632 .status_box_menu {
1652 .status_box_menu {
1633 margin: 0;
1653 margin: 0;
1634 }
1654 }
1635
1655
1636 .notification-table{
1656 .notification-table{
1637 margin-bottom: @space;
1657 margin-bottom: @space;
1638 display: table;
1658 display: table;
1639 width: 100%;
1659 width: 100%;
1640
1660
1641 .container{
1661 .container{
1642 display: table-row;
1662 display: table-row;
1643
1663
1644 .notification-header{
1664 .notification-header{
1645 border-bottom: @border-thickness solid @border-default-color;
1665 border-bottom: @border-thickness solid @border-default-color;
1646 }
1666 }
1647
1667
1648 .notification-subject{
1668 .notification-subject{
1649 display: table-cell;
1669 display: table-cell;
1650 }
1670 }
1651 }
1671 }
1652 }
1672 }
1653
1673
1654 // Notifications
1674 // Notifications
1655 .notification-header{
1675 .notification-header{
1656 display: table;
1676 display: table;
1657 width: 100%;
1677 width: 100%;
1658 padding: floor(@basefontsize/2) 0;
1678 padding: floor(@basefontsize/2) 0;
1659 line-height: 1em;
1679 line-height: 1em;
1660
1680
1661 .desc, .delete-notifications, .read-notifications{
1681 .desc, .delete-notifications, .read-notifications{
1662 display: table-cell;
1682 display: table-cell;
1663 text-align: left;
1683 text-align: left;
1664 }
1684 }
1665
1685
1666 .desc{
1686 .desc{
1667 width: 1163px;
1687 width: 1163px;
1668 }
1688 }
1669
1689
1670 .delete-notifications, .read-notifications{
1690 .delete-notifications, .read-notifications{
1671 width: 35px;
1691 width: 35px;
1672 min-width: 35px; //fixes when only one button is displayed
1692 min-width: 35px; //fixes when only one button is displayed
1673 }
1693 }
1674 }
1694 }
1675
1695
1676 .notification-body {
1696 .notification-body {
1677 .markdown-block,
1697 .markdown-block,
1678 .rst-block {
1698 .rst-block {
1679 padding: @padding 0;
1699 padding: @padding 0;
1680 }
1700 }
1681
1701
1682 .notification-subject {
1702 .notification-subject {
1683 padding: @textmargin 0;
1703 padding: @textmargin 0;
1684 border-bottom: @border-thickness solid @border-default-color;
1704 border-bottom: @border-thickness solid @border-default-color;
1685 }
1705 }
1686 }
1706 }
1687
1707
1688
1708
1689 .notifications_buttons{
1709 .notifications_buttons{
1690 float: right;
1710 float: right;
1691 }
1711 }
1692
1712
1693 #notification-status{
1713 #notification-status{
1694 display: inline;
1714 display: inline;
1695 }
1715 }
1696
1716
1697 // Repositories
1717 // Repositories
1698
1718
1699 #summary.fields{
1719 #summary.fields{
1700 display: table;
1720 display: table;
1701
1721
1702 .field{
1722 .field{
1703 display: table-row;
1723 display: table-row;
1704
1724
1705 .label-summary{
1725 .label-summary{
1706 display: table-cell;
1726 display: table-cell;
1707 min-width: @label-summary-minwidth;
1727 min-width: @label-summary-minwidth;
1708 padding-top: @padding/2;
1728 padding-top: @padding/2;
1709 padding-bottom: @padding/2;
1729 padding-bottom: @padding/2;
1710 padding-right: @padding/2;
1730 padding-right: @padding/2;
1711 }
1731 }
1712
1732
1713 .input{
1733 .input{
1714 display: table-cell;
1734 display: table-cell;
1715 padding: @padding/2;
1735 padding: @padding/2;
1716
1736
1717 input{
1737 input{
1718 min-width: 29em;
1738 min-width: 29em;
1719 padding: @padding/4;
1739 padding: @padding/4;
1720 }
1740 }
1721 }
1741 }
1722 .statistics, .downloads{
1742 .statistics, .downloads{
1723 .disabled{
1743 .disabled{
1724 color: @grey4;
1744 color: @grey4;
1725 }
1745 }
1726 }
1746 }
1727 }
1747 }
1728 }
1748 }
1729
1749
1730 #summary{
1750 #summary{
1731 width: 70%;
1751 width: 70%;
1732 }
1752 }
1733
1753
1734
1754
1735 // Journal
1755 // Journal
1736 .journal.title {
1756 .journal.title {
1737 h5 {
1757 h5 {
1738 float: left;
1758 float: left;
1739 margin: 0;
1759 margin: 0;
1740 width: 70%;
1760 width: 70%;
1741 }
1761 }
1742
1762
1743 ul {
1763 ul {
1744 float: right;
1764 float: right;
1745 display: inline-block;
1765 display: inline-block;
1746 margin: 0;
1766 margin: 0;
1747 width: 30%;
1767 width: 30%;
1748 text-align: right;
1768 text-align: right;
1749
1769
1750 li {
1770 li {
1751 display: inline;
1771 display: inline;
1752 font-size: @journal-fontsize;
1772 font-size: @journal-fontsize;
1753 line-height: 1em;
1773 line-height: 1em;
1754
1774
1755 &:before { content: none; }
1775 &:before { content: none; }
1756 }
1776 }
1757 }
1777 }
1758 }
1778 }
1759
1779
1760 .filterexample {
1780 .filterexample {
1761 position: absolute;
1781 position: absolute;
1762 top: 95px;
1782 top: 95px;
1763 left: @contentpadding;
1783 left: @contentpadding;
1764 color: @rcblue;
1784 color: @rcblue;
1765 font-size: 11px;
1785 font-size: 11px;
1766 font-family: @text-regular;
1786 font-family: @text-regular;
1767 cursor: help;
1787 cursor: help;
1768
1788
1769 &:hover {
1789 &:hover {
1770 color: @rcdarkblue;
1790 color: @rcdarkblue;
1771 }
1791 }
1772
1792
1773 @media (max-width:768px) {
1793 @media (max-width:768px) {
1774 position: relative;
1794 position: relative;
1775 top: auto;
1795 top: auto;
1776 left: auto;
1796 left: auto;
1777 display: block;
1797 display: block;
1778 }
1798 }
1779 }
1799 }
1780
1800
1781
1801
1782 #journal{
1802 #journal{
1783 margin-bottom: @space;
1803 margin-bottom: @space;
1784
1804
1785 .journal_day{
1805 .journal_day{
1786 margin-bottom: @textmargin/2;
1806 margin-bottom: @textmargin/2;
1787 padding-bottom: @textmargin/2;
1807 padding-bottom: @textmargin/2;
1788 font-size: @journal-fontsize;
1808 font-size: @journal-fontsize;
1789 border-bottom: @border-thickness solid @border-default-color;
1809 border-bottom: @border-thickness solid @border-default-color;
1790 }
1810 }
1791
1811
1792 .journal_container{
1812 .journal_container{
1793 margin-bottom: @space;
1813 margin-bottom: @space;
1794
1814
1795 .journal_user{
1815 .journal_user{
1796 display: inline-block;
1816 display: inline-block;
1797 }
1817 }
1798 .journal_action_container{
1818 .journal_action_container{
1799 display: block;
1819 display: block;
1800 margin-top: @textmargin;
1820 margin-top: @textmargin;
1801
1821
1802 div{
1822 div{
1803 display: inline;
1823 display: inline;
1804 }
1824 }
1805
1825
1806 div.journal_action_params{
1826 div.journal_action_params{
1807 display: block;
1827 display: block;
1808 }
1828 }
1809
1829
1810 div.journal_repo:after{
1830 div.journal_repo:after{
1811 content: "\A";
1831 content: "\A";
1812 white-space: pre;
1832 white-space: pre;
1813 }
1833 }
1814
1834
1815 div.date{
1835 div.date{
1816 display: block;
1836 display: block;
1817 margin-bottom: @textmargin;
1837 margin-bottom: @textmargin;
1818 }
1838 }
1819 }
1839 }
1820 }
1840 }
1821 }
1841 }
1822
1842
1823 // Files
1843 // Files
1824 .edit-file-title {
1844 .edit-file-title {
1825 border-bottom: @border-thickness solid @border-default-color;
1845 border-bottom: @border-thickness solid @border-default-color;
1826
1846
1827 .breadcrumbs {
1847 .breadcrumbs {
1828 margin-bottom: 0;
1848 margin-bottom: 0;
1829 }
1849 }
1830 }
1850 }
1831
1851
1832 .edit-file-fieldset {
1852 .edit-file-fieldset {
1833 margin-top: @sidebarpadding;
1853 margin-top: @sidebarpadding;
1834
1854
1835 .fieldset {
1855 .fieldset {
1836 .left-label {
1856 .left-label {
1837 width: 13%;
1857 width: 13%;
1838 }
1858 }
1839 .right-content {
1859 .right-content {
1840 width: 87%;
1860 width: 87%;
1841 max-width: 100%;
1861 max-width: 100%;
1842 }
1862 }
1843 .filename-label {
1863 .filename-label {
1844 margin-top: 13px;
1864 margin-top: 13px;
1845 }
1865 }
1846 .commit-message-label {
1866 .commit-message-label {
1847 margin-top: 4px;
1867 margin-top: 4px;
1848 }
1868 }
1849 .file-upload-input {
1869 .file-upload-input {
1850 input {
1870 input {
1851 display: none;
1871 display: none;
1852 }
1872 }
1853 }
1873 }
1854 p {
1874 p {
1855 margin-top: 5px;
1875 margin-top: 5px;
1856 }
1876 }
1857
1877
1858 }
1878 }
1859 .custom-path-link {
1879 .custom-path-link {
1860 margin-left: 5px;
1880 margin-left: 5px;
1861 }
1881 }
1862 #commit {
1882 #commit {
1863 resize: vertical;
1883 resize: vertical;
1864 }
1884 }
1865 }
1885 }
1866
1886
1867 .delete-file-preview {
1887 .delete-file-preview {
1868 max-height: 250px;
1888 max-height: 250px;
1869 }
1889 }
1870
1890
1871 .new-file,
1891 .new-file,
1872 #filter_activate,
1892 #filter_activate,
1873 #filter_deactivate {
1893 #filter_deactivate {
1874 float: left;
1894 float: left;
1875 margin: 0 0 0 15px;
1895 margin: 0 0 0 15px;
1876 }
1896 }
1877
1897
1878 h3.files_location{
1898 h3.files_location{
1879 line-height: 2.4em;
1899 line-height: 2.4em;
1880 }
1900 }
1881
1901
1882 .browser-nav {
1902 .browser-nav {
1883 display: table;
1903 display: table;
1884 margin-bottom: @space;
1904 margin-bottom: @space;
1885
1905
1886
1906
1887 .info_box {
1907 .info_box {
1888 display: inline-table;
1908 display: inline-table;
1889 height: 2.5em;
1909 height: 2.5em;
1890
1910
1891 .browser-cur-rev, .info_box_elem {
1911 .browser-cur-rev, .info_box_elem {
1892 display: table-cell;
1912 display: table-cell;
1893 vertical-align: middle;
1913 vertical-align: middle;
1894 }
1914 }
1895
1915
1896 .info_box_elem {
1916 .info_box_elem {
1897 border-top: @border-thickness solid @rcblue;
1917 border-top: @border-thickness solid @rcblue;
1898 border-bottom: @border-thickness solid @rcblue;
1918 border-bottom: @border-thickness solid @rcblue;
1899
1919
1900 #at_rev, a {
1920 #at_rev, a {
1901 padding: 0.6em 0.9em;
1921 padding: 0.6em 0.9em;
1902 margin: 0;
1922 margin: 0;
1903 .box-shadow(none);
1923 .box-shadow(none);
1904 border: 0;
1924 border: 0;
1905 height: 12px;
1925 height: 12px;
1906 }
1926 }
1907
1927
1908 input#at_rev {
1928 input#at_rev {
1909 max-width: 50px;
1929 max-width: 50px;
1910 text-align: right;
1930 text-align: right;
1911 }
1931 }
1912
1932
1913 &.previous {
1933 &.previous {
1914 border: @border-thickness solid @rcblue;
1934 border: @border-thickness solid @rcblue;
1915 .disabled {
1935 .disabled {
1916 color: @grey4;
1936 color: @grey4;
1917 cursor: not-allowed;
1937 cursor: not-allowed;
1918 }
1938 }
1919 }
1939 }
1920
1940
1921 &.next {
1941 &.next {
1922 border: @border-thickness solid @rcblue;
1942 border: @border-thickness solid @rcblue;
1923 .disabled {
1943 .disabled {
1924 color: @grey4;
1944 color: @grey4;
1925 cursor: not-allowed;
1945 cursor: not-allowed;
1926 }
1946 }
1927 }
1947 }
1928 }
1948 }
1929
1949
1930 .browser-cur-rev {
1950 .browser-cur-rev {
1931
1951
1932 span{
1952 span{
1933 margin: 0;
1953 margin: 0;
1934 color: @rcblue;
1954 color: @rcblue;
1935 height: 12px;
1955 height: 12px;
1936 display: inline-block;
1956 display: inline-block;
1937 padding: 0.7em 1em ;
1957 padding: 0.7em 1em ;
1938 border: @border-thickness solid @rcblue;
1958 border: @border-thickness solid @rcblue;
1939 margin-right: @padding;
1959 margin-right: @padding;
1940 }
1960 }
1941 }
1961 }
1942 }
1962 }
1943
1963
1944 .search_activate {
1964 .search_activate {
1945 display: table-cell;
1965 display: table-cell;
1946 vertical-align: middle;
1966 vertical-align: middle;
1947
1967
1948 input, label{
1968 input, label{
1949 margin: 0;
1969 margin: 0;
1950 padding: 0;
1970 padding: 0;
1951 }
1971 }
1952
1972
1953 input{
1973 input{
1954 margin-left: @textmargin;
1974 margin-left: @textmargin;
1955 }
1975 }
1956
1976
1957 }
1977 }
1958 }
1978 }
1959
1979
1960 .browser-cur-rev{
1980 .browser-cur-rev{
1961 margin-bottom: @textmargin;
1981 margin-bottom: @textmargin;
1962 }
1982 }
1963
1983
1964 #node_filter_box_loading{
1984 #node_filter_box_loading{
1965 .info_text;
1985 .info_text;
1966 }
1986 }
1967
1987
1968 .browser-search {
1988 .browser-search {
1969 margin: -25px 0px 5px 0px;
1989 margin: -25px 0px 5px 0px;
1970 }
1990 }
1971
1991
1972 .node-filter {
1992 .node-filter {
1973 font-size: @repo-title-fontsize;
1993 font-size: @repo-title-fontsize;
1974 padding: 4px 0px 0px 0px;
1994 padding: 4px 0px 0px 0px;
1975
1995
1976 .node-filter-path {
1996 .node-filter-path {
1977 float: left;
1997 float: left;
1978 color: @grey4;
1998 color: @grey4;
1979 }
1999 }
1980 .node-filter-input {
2000 .node-filter-input {
1981 float: left;
2001 float: left;
1982 margin: -2px 0px 0px 2px;
2002 margin: -2px 0px 0px 2px;
1983 input {
2003 input {
1984 padding: 2px;
2004 padding: 2px;
1985 border: none;
2005 border: none;
1986 font-size: @repo-title-fontsize;
2006 font-size: @repo-title-fontsize;
1987 }
2007 }
1988 }
2008 }
1989 }
2009 }
1990
2010
1991
2011
1992 .browser-result{
2012 .browser-result{
1993 td a{
2013 td a{
1994 margin-left: 0.5em;
2014 margin-left: 0.5em;
1995 display: inline-block;
2015 display: inline-block;
1996
2016
1997 em{
2017 em{
1998 font-family: @text-bold;
2018 font-family: @text-bold;
1999 }
2019 }
2000 }
2020 }
2001 }
2021 }
2002
2022
2003 .browser-highlight{
2023 .browser-highlight{
2004 background-color: @grey5-alpha;
2024 background-color: @grey5-alpha;
2005 }
2025 }
2006
2026
2007
2027
2008 // Search
2028 // Search
2009
2029
2010 .search-form{
2030 .search-form{
2011 #q {
2031 #q {
2012 width: @search-form-width;
2032 width: @search-form-width;
2013 }
2033 }
2014 .fields{
2034 .fields{
2015 margin: 0 0 @space;
2035 margin: 0 0 @space;
2016 }
2036 }
2017
2037
2018 label{
2038 label{
2019 display: inline-block;
2039 display: inline-block;
2020 margin-right: @textmargin;
2040 margin-right: @textmargin;
2021 padding-top: 0.25em;
2041 padding-top: 0.25em;
2022 }
2042 }
2023
2043
2024
2044
2025 .results{
2045 .results{
2026 clear: both;
2046 clear: both;
2027 margin: 0 0 @padding;
2047 margin: 0 0 @padding;
2028 }
2048 }
2029 }
2049 }
2030
2050
2031 div.search-feedback-items {
2051 div.search-feedback-items {
2032 display: inline-block;
2052 display: inline-block;
2033 padding:0px 0px 0px 96px;
2053 padding:0px 0px 0px 96px;
2034 }
2054 }
2035
2055
2036 div.search-code-body {
2056 div.search-code-body {
2037 background-color: #ffffff; padding: 5px 0 5px 10px;
2057 background-color: #ffffff; padding: 5px 0 5px 10px;
2038 pre {
2058 pre {
2039 .match { background-color: #faffa6;}
2059 .match { background-color: #faffa6;}
2040 .break { display: block; width: 100%; background-color: #DDE7EF; color: #747474; }
2060 .break { display: block; width: 100%; background-color: #DDE7EF; color: #747474; }
2041 }
2061 }
2042 }
2062 }
2043
2063
2044 .expand_commit.search {
2064 .expand_commit.search {
2045 .show_more.open {
2065 .show_more.open {
2046 height: auto;
2066 height: auto;
2047 max-height: none;
2067 max-height: none;
2048 }
2068 }
2049 }
2069 }
2050
2070
2051 .search-results {
2071 .search-results {
2052
2072
2053 h2 {
2073 h2 {
2054 margin-bottom: 0;
2074 margin-bottom: 0;
2055 }
2075 }
2056 .codeblock {
2076 .codeblock {
2057 border: none;
2077 border: none;
2058 background: transparent;
2078 background: transparent;
2059 }
2079 }
2060
2080
2061 .codeblock-header {
2081 .codeblock-header {
2062 border: none;
2082 border: none;
2063 background: transparent;
2083 background: transparent;
2064 }
2084 }
2065
2085
2066 .code-body {
2086 .code-body {
2067 border: @border-thickness solid @border-default-color;
2087 border: @border-thickness solid @border-default-color;
2068 .border-radius(@border-radius);
2088 .border-radius(@border-radius);
2069 }
2089 }
2070
2090
2071 .td-commit {
2091 .td-commit {
2072 &:extend(pre);
2092 &:extend(pre);
2073 border-bottom: @border-thickness solid @border-default-color;
2093 border-bottom: @border-thickness solid @border-default-color;
2074 }
2094 }
2075
2095
2076 .message {
2096 .message {
2077 height: auto;
2097 height: auto;
2078 max-width: 350px;
2098 max-width: 350px;
2079 white-space: normal;
2099 white-space: normal;
2080 text-overflow: initial;
2100 text-overflow: initial;
2081 overflow: visible;
2101 overflow: visible;
2082
2102
2083 .match { background-color: #faffa6;}
2103 .match { background-color: #faffa6;}
2084 .break { background-color: #DDE7EF; width: 100%; color: #747474; display: block; }
2104 .break { background-color: #DDE7EF; width: 100%; color: #747474; display: block; }
2085 }
2105 }
2086
2106
2087 }
2107 }
2088
2108
2089 table.rctable td.td-search-results div {
2109 table.rctable td.td-search-results div {
2090 max-width: 100%;
2110 max-width: 100%;
2091 }
2111 }
2092
2112
2093 #tip-box, .tip-box{
2113 #tip-box, .tip-box{
2094 padding: @menupadding/2;
2114 padding: @menupadding/2;
2095 display: block;
2115 display: block;
2096 border: @border-thickness solid @border-highlight-color;
2116 border: @border-thickness solid @border-highlight-color;
2097 .border-radius(@border-radius);
2117 .border-radius(@border-radius);
2098 background-color: white;
2118 background-color: white;
2099 z-index: 99;
2119 z-index: 99;
2100 white-space: pre-wrap;
2120 white-space: pre-wrap;
2101 }
2121 }
2102
2122
2103 #linktt {
2123 #linktt {
2104 width: 79px;
2124 width: 79px;
2105 }
2125 }
2106
2126
2107 #help_kb .modal-content{
2127 #help_kb .modal-content{
2108 max-width: 750px;
2128 max-width: 750px;
2109 margin: 10% auto;
2129 margin: 10% auto;
2110
2130
2111 table{
2131 table{
2112 td,th{
2132 td,th{
2113 border-bottom: none;
2133 border-bottom: none;
2114 line-height: 2.5em;
2134 line-height: 2.5em;
2115 }
2135 }
2116 th{
2136 th{
2117 padding-bottom: @textmargin/2;
2137 padding-bottom: @textmargin/2;
2118 }
2138 }
2119 td.keys{
2139 td.keys{
2120 text-align: center;
2140 text-align: center;
2121 }
2141 }
2122 }
2142 }
2123
2143
2124 .block-left{
2144 .block-left{
2125 width: 45%;
2145 width: 45%;
2126 margin-right: 5%;
2146 margin-right: 5%;
2127 }
2147 }
2128 .modal-footer{
2148 .modal-footer{
2129 clear: both;
2149 clear: both;
2130 }
2150 }
2131 .key.tag{
2151 .key.tag{
2132 padding: 0.5em;
2152 padding: 0.5em;
2133 background-color: @rcblue;
2153 background-color: @rcblue;
2134 color: white;
2154 color: white;
2135 border-color: @rcblue;
2155 border-color: @rcblue;
2136 .box-shadow(none);
2156 .box-shadow(none);
2137 }
2157 }
2138 }
2158 }
2139
2159
2140
2160
2141
2161
2142 //--- IMPORTS FOR REFACTORED STYLES ------------------//
2162 //--- IMPORTS FOR REFACTORED STYLES ------------------//
2143
2163
2144 @import 'statistics-graph';
2164 @import 'statistics-graph';
2145 @import 'tables';
2165 @import 'tables';
2146 @import 'forms';
2166 @import 'forms';
2147 @import 'diff';
2167 @import 'diff';
2148 @import 'summary';
2168 @import 'summary';
2149 @import 'navigation';
2169 @import 'navigation';
2150
2170
2151 //--- SHOW/HIDE SECTIONS --//
2171 //--- SHOW/HIDE SECTIONS --//
2152
2172
2153 .btn-collapse {
2173 .btn-collapse {
2154 float: right;
2174 float: right;
2155 text-align: right;
2175 text-align: right;
2156 font-family: @text-light;
2176 font-family: @text-light;
2157 font-size: @basefontsize;
2177 font-size: @basefontsize;
2158 cursor: pointer;
2178 cursor: pointer;
2159 border: none;
2179 border: none;
2160 color: @rcblue;
2180 color: @rcblue;
2161 }
2181 }
2162
2182
2163 table.rctable,
2183 table.rctable,
2164 table.dataTable {
2184 table.dataTable {
2165 .btn-collapse {
2185 .btn-collapse {
2166 float: right;
2186 float: right;
2167 text-align: right;
2187 text-align: right;
2168 }
2188 }
2169 }
2189 }
2170
2190
2171
2191
2172 // TODO: johbo: Fix for IE10, this avoids that we see a border
2192 // TODO: johbo: Fix for IE10, this avoids that we see a border
2173 // and padding around checkboxes and radio boxes. Move to the right place,
2193 // and padding around checkboxes and radio boxes. Move to the right place,
2174 // or better: Remove this once we did the form refactoring.
2194 // or better: Remove this once we did the form refactoring.
2175 input[type=checkbox],
2195 input[type=checkbox],
2176 input[type=radio] {
2196 input[type=radio] {
2177 padding: 0;
2197 padding: 0;
2178 border: none;
2198 border: none;
2179 }
2199 }
2180
2200
2181 .toggle-ajax-spinner{
2201 .toggle-ajax-spinner{
2182 height: 16px;
2202 height: 16px;
2183 width: 16px;
2203 width: 16px;
2184 }
2204 }
@@ -1,53 +1,54 b''
1
1
2 /******************************************************************************
2 /******************************************************************************
3 * *
3 * *
4 * DO NOT CHANGE THIS FILE MANUALLY *
4 * DO NOT CHANGE THIS FILE MANUALLY *
5 * *
5 * *
6 * *
6 * *
7 * This file is automatically generated when the app starts up with *
7 * This file is automatically generated when the app starts up with *
8 * generate_js_files = true *
8 * generate_js_files = true *
9 * *
9 * *
10 * To add a route here pass jsroute=True to the route definition in the app *
10 * To add a route here pass jsroute=True to the route definition in the app *
11 * *
11 * *
12 ******************************************************************************/
12 ******************************************************************************/
13 function registerRCRoutes() {
13 function registerRCRoutes() {
14 // routes registration
14 // routes registration
15 pyroutes.register('home', '/', []);
15 pyroutes.register('home', '/', []);
16 pyroutes.register('user_autocomplete_data', '/_users', []);
16 pyroutes.register('user_autocomplete_data', '/_users', []);
17 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
17 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
18 pyroutes.register('new_repo', '/_admin/create_repository', []);
18 pyroutes.register('new_repo', '/_admin/create_repository', []);
19 pyroutes.register('edit_user', '/_admin/users/%(user_id)s/edit', ['user_id']);
19 pyroutes.register('edit_user_group_members', '/_admin/user_groups/%(user_group_id)s/edit/members', ['user_group_id']);
20 pyroutes.register('edit_user_group_members', '/_admin/user_groups/%(user_group_id)s/edit/members', ['user_group_id']);
20 pyroutes.register('gists', '/_admin/gists', []);
21 pyroutes.register('gists', '/_admin/gists', []);
21 pyroutes.register('new_gist', '/_admin/gists/new', []);
22 pyroutes.register('new_gist', '/_admin/gists/new', []);
22 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
23 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
23 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
24 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
24 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
25 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
25 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
26 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
26 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/default-reviewers', ['repo_name']);
27 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/default-reviewers', ['repo_name']);
27 pyroutes.register('changeset_home', '/%(repo_name)s/changeset/%(revision)s', ['repo_name', 'revision']);
28 pyroutes.register('changeset_home', '/%(repo_name)s/changeset/%(revision)s', ['repo_name', 'revision']);
28 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
29 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
29 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
30 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
30 pyroutes.register('changeset_comment', '/%(repo_name)s/changeset/%(revision)s/comment', ['repo_name', 'revision']);
31 pyroutes.register('changeset_comment', '/%(repo_name)s/changeset/%(revision)s/comment', ['repo_name', 'revision']);
31 pyroutes.register('changeset_comment_preview', '/%(repo_name)s/changeset/comment/preview', ['repo_name']);
32 pyroutes.register('changeset_comment_preview', '/%(repo_name)s/changeset/comment/preview', ['repo_name']);
32 pyroutes.register('changeset_comment_delete', '/%(repo_name)s/changeset/comment/%(comment_id)s/delete', ['repo_name', 'comment_id']);
33 pyroutes.register('changeset_comment_delete', '/%(repo_name)s/changeset/comment/%(comment_id)s/delete', ['repo_name', 'comment_id']);
33 pyroutes.register('changeset_info', '/%(repo_name)s/changeset_info/%(revision)s', ['repo_name', 'revision']);
34 pyroutes.register('changeset_info', '/%(repo_name)s/changeset_info/%(revision)s', ['repo_name', 'revision']);
34 pyroutes.register('compare_url', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']);
35 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']);
35 pyroutes.register('pullrequest_home', '/%(repo_name)s/pull-request/new', ['repo_name']);
36 pyroutes.register('pullrequest_home', '/%(repo_name)s/pull-request/new', ['repo_name']);
36 pyroutes.register('pullrequest', '/%(repo_name)s/pull-request/new', ['repo_name']);
37 pyroutes.register('pullrequest', '/%(repo_name)s/pull-request/new', ['repo_name']);
37 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
38 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
38 pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']);
39 pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']);
39 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
40 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']);
41 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']);
42 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']);
43 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']);
44 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']);
45 pyroutes.register('changelog_file_home', '/%(repo_name)s/changelog/%(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']);
46 pyroutes.register('files_home', '/%(repo_name)s/files/%(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']);
47 pyroutes.register('files_history_home', '/%(repo_name)s/history/%(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']);
48 pyroutes.register('files_authors_home', '/%(repo_name)s/authors/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
48 pyroutes.register('files_archive_home', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
49 pyroutes.register('files_archive_home', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
49 pyroutes.register('files_nodelist_home', '/%(repo_name)s/nodelist/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
50 pyroutes.register('files_nodelist_home', '/%(repo_name)s/nodelist/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
50 pyroutes.register('files_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
51 pyroutes.register('files_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
51 pyroutes.register('summary_home_slash', '/%(repo_name)s/', ['repo_name']);
52 pyroutes.register('summary_home_slash', '/%(repo_name)s/', ['repo_name']);
52 pyroutes.register('summary_home', '/%(repo_name)s', ['repo_name']);
53 pyroutes.register('summary_home', '/%(repo_name)s', ['repo_name']);
53 }
54 }
@@ -1,47 +1,46 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
2 <%inherit file="/base/base.html"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('%s user group settings') % c.user_group.users_group_name}
5 ${_('%s user group settings') % c.user_group.users_group_name}
6 %if c.rhodecode_name:
6 %if c.rhodecode_name:
7 &middot; ${h.branding(c.rhodecode_name)}
7 &middot; ${h.branding(c.rhodecode_name)}
8 %endif
8 %endif
9 </%def>
9 </%def>
10
10
11 <%def name="breadcrumbs_links()">
11 <%def name="breadcrumbs_links()">
12 ${h.link_to(_('Admin'),h.url('admin_home'))}
12 ${h.link_to(_('Admin'),h.url('admin_home'))}
13 &raquo;
13 &raquo;
14 ${h.link_to(_('User Groups'),h.url('users_groups'))}
14 ${h.link_to(_('User Groups'),h.url('users_groups'))}
15 &raquo;
15 &raquo;
16 ${c.user_group.users_group_name}
16 ${c.user_group.users_group_name}
17 </%def>
17 </%def>
18
18
19 <%def name="menu_bar_nav()">
19 <%def name="menu_bar_nav()">
20 ${self.menu_items(active='admin')}
20 ${self.menu_items(active='admin')}
21 </%def>
21 </%def>
22
22
23 <%def name="main()">
23 <%def name="main()">
24 <div class="box">
24 <div class="box">
25 <div class="title">
25 <div class="title">
26 ${self.breadcrumbs()}
26 ${self.breadcrumbs()}
27 </div>
27 </div>
28
28
29 ##main
29 ##main
30 <div class="sidebar-col-wrapper">
30 <div class="sidebar-col-wrapper">
31 <div class="sidebar">
31 <div class="sidebar">
32 <ul class="nav nav-pills nav-stacked">
32 <ul class="nav nav-pills nav-stacked">
33 <li class="${'active' if c.active=='settings' else ''}"><a href="${h.url('edit_users_group', user_group_id=c.user_group.users_group_id)}">${_('Settings')}</a></li>
33 <li class="${'active' if c.active=='settings' else ''}"><a href="${h.url('edit_users_group', user_group_id=c.user_group.users_group_id)}">${_('Settings')}</a></li>
34 <li class="${'active' if c.active=='perms' else ''}"><a href="${h.url('edit_user_group_perms', user_group_id=c.user_group.users_group_id)}">${_('Permissions')}</a></li>
34 <li class="${'active' if c.active=='perms' else ''}"><a href="${h.url('edit_user_group_perms', user_group_id=c.user_group.users_group_id)}">${_('Permissions')}</a></li>
35 <li class="${'active' if c.active=='advanced' else ''}"><a href="${h.url('edit_user_group_advanced', user_group_id=c.user_group.users_group_id)}">${_('Advanced')}</a></li>
35 <li class="${'active' if c.active=='advanced' else ''}"><a href="${h.url('edit_user_group_advanced', user_group_id=c.user_group.users_group_id)}">${_('Advanced')}</a></li>
36 <li class="${'active' if c.active=='global_perms' else ''}"><a href="${h.url('edit_user_group_global_perms', user_group_id=c.user_group.users_group_id)}">${_('Global permissions')}</a></li>
36 <li class="${'active' if c.active=='global_perms' else ''}"><a href="${h.url('edit_user_group_global_perms', user_group_id=c.user_group.users_group_id)}">${_('Global permissions')}</a></li>
37 <li class="${'active' if c.active=='perms_summary' else ''}"><a href="${h.url('edit_user_group_perms_summary', user_group_id=c.user_group.users_group_id)}">${_('Permissions summary')}</a></li>
37 <li class="${'active' if c.active=='perms_summary' else ''}"><a href="${h.url('edit_user_group_perms_summary', user_group_id=c.user_group.users_group_id)}">${_('Permissions summary')}</a></li>
38 <li class="${'active' if c.active=='members' else ''}"><a href="${h.url('edit_user_group_members', user_group_id=c.user_group.users_group_id)}">${_('Members')}</a></li>
39 </ul>
38 </ul>
40 </div>
39 </div>
41
40
42 <div class="main-content-full-width">
41 <div class="main-content-full-width">
43 <%include file="/admin/user_groups/user_group_edit_${c.active}.html"/>
42 <%include file="/admin/user_groups/user_group_edit_${c.active}.html"/>
44 </div>
43 </div>
45 </div>
44 </div>
46 </div>
45 </div>
47 </%def>
46 </%def>
@@ -1,144 +1,186 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%namespace name="base" file="/base/base.html"/>
2 <%namespace name="base" file="/base/base.html"/>
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">${_('User Group: %s') % c.user_group.users_group_name}</h3>
6 <h3 class="panel-title">${_('User Group: %s') % c.user_group.users_group_name}</h3>
7 </div>
7 </div>
8 <div class="panel-body">
8 <div class="panel-body">
9 ${h.secure_form(url('update_users_group', user_group_id=c.user_group.users_group_id),method='put', id='edit_users_group')}
9 ${h.secure_form(url('update_users_group', user_group_id=c.user_group.users_group_id),method='put', id='edit_users_group')}
10 <div class="form">
10 <div class="form">
11 <!-- fields -->
11 <!-- fields -->
12 <div class="fields">
12 <div class="fields">
13 <div class="field">
13 <div class="field">
14 <div class="label">
14 <div class="label">
15 <label for="users_group_name">${_('Group name')}:</label>
15 <label for="users_group_name">${_('Group name')}:</label>
16 </div>
16 </div>
17 <div class="input">
17 <div class="input">
18 ${h.text('users_group_name',class_='medium')}
18 ${h.text('users_group_name',class_='medium')}
19 </div>
19 </div>
20 </div>
20 </div>
21
21
22 <div class="field badged-field">
22 <div class="field badged-field">
23 <div class="label">
23 <div class="label">
24 <label for="user">${_('Owner')}:</label>
24 <label for="user">${_('Owner')}:</label>
25 </div>
25 </div>
26 <div class="input">
26 <div class="input">
27 <div class="badge-input-container">
27 <div class="badge-input-container">
28 <div class="user-badge">
28 <div class="user-badge">
29 ${base.gravatar_with_user(c.user_group.user.email, show_disabled=not c.user_group.user.active)}
29 ${base.gravatar_with_user(c.user_group.user.email, show_disabled=not c.user_group.user.active)}
30 </div>
30 </div>
31 <div class="badge-input-wrap">
31 <div class="badge-input-wrap">
32 ${h.text('user', class_="medium", autocomplete="off")}
32 ${h.text('user', class_="medium", autocomplete="off")}
33 </div>
33 </div>
34 </div>
34 </div>
35 <form:error name="user"/>
35 <form:error name="user"/>
36 <p class="help-block">${_('Change owner of this user group.')}</p>
36 <p class="help-block">${_('Change owner of this user group.')}</p>
37 </div>
37 </div>
38 </div>
38 </div>
39
39
40 <div class="field">
40 <div class="field">
41 <div class="label label-textarea">
41 <div class="label label-textarea">
42 <label for="user_group_description">${_('Description')}:</label>
42 <label for="user_group_description">${_('Description')}:</label>
43 </div>
43 </div>
44 <div class="textarea textarea-small editor">
44 <div class="textarea textarea-small editor">
45 ${h.textarea('user_group_description',cols=23,rows=5,class_="medium")}
45 ${h.textarea('user_group_description',cols=23,rows=5,class_="medium")}
46 <span class="help-block">${_('Short, optional description for this user group.')}</span>
46 <span class="help-block">${_('Short, optional description for this user group.')}</span>
47 </div>
47 </div>
48 </div>
48 </div>
49 <div class="field">
49 <div class="field">
50 <div class="label label-checkbox">
50 <div class="label label-checkbox">
51 <label for="users_group_active">${_('Active')}:</label>
51 <label for="users_group_active">${_('Active')}:</label>
52 </div>
52 </div>
53 <div class="checkboxes">
53 <div class="checkboxes">
54 ${h.checkbox('users_group_active',value=True)}
54 ${h.checkbox('users_group_active',value=True)}
55 </div>
55 </div>
56 </div>
56 </div>
57
57 <div class="field">
58 <div class="field">
58 <div class="label">
59 <div class="label label-checkbox">
59 <label for="users_group_active">${_('Search')}:</label>
60 <label for="users_group_active">${_('Add members')}:</label>
60 ${h.text('from_user_group',
61 placeholder="user/usergroup",
62 class_="medium")}
63 </div>
61 </div>
64 <div class="select side-by-side-selector">
62 <div class="input">
65 <div class="left-group">
63 ${h.text('user_group_add_members', placeholder="user/usergroup", class_="medium")}
66 <label class="text"><strong>${_('Chosen group members')}</strong></label>
67 ${h.select('users_group_members',[x[0] for x in c.group_members],c.group_members,multiple=True,size=8,)}
68 <div class="btn" id="remove_all_elements" >
69 ${_('Remove all elements')}
70 <i class="icon-chevron-right"></i>
71 </div>
64 </div>
72 </div>
65 </div>
73 <div class="middle-group">
66
74 <i id="add_element" class="icon-chevron-left"></i>
67 <input type="hidden" name="__start__" value="user_group_members:sequence"/>
75 <br />
68 <table id="group_members_placeholder" class="rctable group_members">
76 <i id="remove_element" class="icon-chevron-right"></i>
69 <tr>
70 <th>${_('Username')}</th>
71 <th>${_('Action')}</th>
72 </tr>
73
74 % if c.group_members_obj:
75 % for user in c.group_members_obj:
76 <tr>
77 <td id="member_user_${user.user_id}" class="td-author">
78 <div class="group_member">
79 ${base.gravatar(user.email, 16)}
80 <span class="username user">${h.link_to(h.person(user), h.url( 'edit_user',user_id=user.user_id))}</span>
81 <input type="hidden" name="__start__" value="member:mapping">
82 <input type="hidden" name="member_user_id" value="${user.user_id}">
83 <input type="hidden" name="type" value="existing" id="member_${user.user_id}">
84 <input type="hidden" name="__end__" value="member:mapping">
77 </div>
85 </div>
78 <div class="right-group">
86 </td>
79 <label class="text" >${_('Available users')}
87 <td class="">
80 </label>
88 <div class="usergroup_member_remove action_button" onclick="removeUserGroupMember(${user.user_id}, true)" style="visibility: visible;">
81 ${h.select('available_members',[],c.available_members,multiple=True,size=8,)}
89 <i class="icon-remove-sign"></i>
82 <div class="btn" id="add_all_elements" >
83 <i class="icon-chevron-left"></i>${_('Add all elements')}
84 </div>
90 </div>
85 </div>
91 </td>
86 </div>
92 </tr>
87 </div>
93 % endfor
94
95 % else:
96 <tr><td colspan="2">${_('No members yet')}</td></tr>
97 % endif
98 </table>
99 <input type="hidden" name="__end__" value="user_group_members:sequence"/>
100
88 <div class="buttons">
101 <div class="buttons">
89 ${h.submit('Save',_('Save'),class_="btn")}
102 ${h.submit('Save',_('Save'),class_="btn")}
90 </div>
103 </div>
91 </div>
104 </div>
92 </div>
105 </div>
93 ${h.end_form()}
106 ${h.end_form()}
94 </div>
107 </div>
95 </div>
108 </div>
96 <script>
109 <script>
97 $(document).ready(function(){
110 $(document).ready(function(){
98 MultiSelectWidget('users_group_members','available_members','edit_users_group');
99
100 $("#group_parent_id").select2({
111 $("#group_parent_id").select2({
101 'containerCssClass': "drop-menu",
112 'containerCssClass': "drop-menu",
102 'dropdownCssClass': "drop-menu-dropdown",
113 'dropdownCssClass': "drop-menu-dropdown",
103 'dropdownAutoWidth': true
114 'dropdownAutoWidth': true
104 });
115 });
105
116
106 $('#from_user_group').autocomplete({
117 removeUserGroupMember = function(userId){
118 $('#member_'+userId).val('remove');
119 $('#member_user_'+userId).addClass('to-delete');
120 };
121
122 $('#user_group_add_members').autocomplete({
107 serviceUrl: pyroutes.url('user_autocomplete_data'),
123 serviceUrl: pyroutes.url('user_autocomplete_data'),
108 minChars:2,
124 minChars:2,
109 maxHeight:400,
125 maxHeight:400,
110 width:300,
126 width:300,
111 deferRequestBy: 300, //miliseconds
127 deferRequestBy: 300, //miliseconds
112 showNoSuggestionNotice: true,
128 showNoSuggestionNotice: true,
113 params: { user_groups:true },
129 params: { user_groups:true },
114 formatResult: autocompleteFormatResult,
130 formatResult: autocompleteFormatResult,
115 lookupFilter: autocompleteFilterResult,
131 lookupFilter: autocompleteFilterResult,
116 onSelect: function(element, suggestion){
132 onSelect: function(element, suggestion){
117
133
118 function preSelectUserIds(uids) {
134 function addMember(user, fromUserGroup) {
119 $('#available_members').val(uids);
135 var gravatar = user.icon_link;
120 $('#users_group_members').val(uids);
136 var username = user.value_display;
137 var userLink = pyroutes.url('edit_user', {"user_id": user.id});
138 var uid = user.id;
139
140 if (fromUserGroup) {
141 username = username +" "+ _gettext('(from usergroup {0})'.format(fromUserGroup))
142 }
143
144 var elem = $(
145 ('<tr>'+
146 '<td id="member_user_{6}" class="td-author td-author-new-entry">'+
147 '<div class="group_member">'+
148 '<img class="gravatar" src="{0}" height="16" width="16">'+
149 '<span class="username user"><a href="{1}">{2}</a></span>'+
150 '<input type="hidden" name="__start__" value="member:mapping">'+
151 '<input type="hidden" name="member_user_id" value="{3}">'+
152 '<input type="hidden" name="type" value="new" id="member_{4}">'+
153 '<input type="hidden" name="__end__" value="member:mapping">'+
154 '</div>'+
155 '</td>'+
156 '<td class="td-author-new-entry">'+
157 '<div class="usergroup_member_remove action_button" onclick="removeUserGroupMember({5}, true)" style="visibility: visible;">'+
158 '<i class="icon-remove-sign"></i>'+
159 '</div>'+
160 '</td>'+
161 '</tr>').format(gravatar, userLink, username,
162 uid, uid, uid, uid)
163 );
164 $('#group_members_placeholder').append(elem)
121 }
165 }
122
166
123 if (suggestion.value_type == 'user_group') {
167 if (suggestion.value_type == 'user_group') {
124 $.getJSON(
168 $.getJSON(
125 pyroutes.url('edit_user_group_members',
169 pyroutes.url('edit_user_group_members',
126 {'user_group_id': suggestion.id}),
170 {'user_group_id': suggestion.id}),
127 function(data) {
171 function(data) {
128 var uids = [];
129 $.each(data.members, function(idx, user) {
172 $.each(data.members, function(idx, user) {
130 var userid = user[0],
173 addMember(user, suggestion.value)
131 username = user[1];
132 uids.push(userid.toString());
133 });
174 });
134 preSelectUserIds(uids)
135 }
175 }
136 );
176 );
137 } else if (suggestion.value_type == 'user') {
177 } else if (suggestion.value_type == 'user') {
138 preSelectUserIds([suggestion.id.toString()]);
178 addMember(suggestion, null);
139 }
179 }
140 }
180 }
141 });
181 });
182
183
142 UsersAutoComplete('user', '${c.rhodecode_user.user_id}');
184 UsersAutoComplete('user', '${c.rhodecode_user.user_id}');
143 })
185 })
144 </script>
186 </script>
@@ -1,631 +1,619 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/debug_style/index.html"/>
2 <%inherit file="/debug_style/index.html"/>
3
3
4 <%def name="breadcrumbs_links()">
4 <%def name="breadcrumbs_links()">
5 ${h.link_to(_('Style'), h.url('debug_style_home'))}
5 ${h.link_to(_('Style'), h.url('debug_style_home'))}
6 &raquo;
6 &raquo;
7 ${c.active}
7 ${c.active}
8 </%def>
8 </%def>
9
9
10
10
11 <%def name="real_main()">
11 <%def name="real_main()">
12 <div class="box">
12 <div class="box">
13 <div class="title">
13 <div class="title">
14 ${self.breadcrumbs()}
14 ${self.breadcrumbs()}
15 </div>
15 </div>
16
16
17 <div class='sidebar-col-wrapper'>
17 <div class='sidebar-col-wrapper'>
18 ${self.sidebar()}
18 ${self.sidebar()}
19
19
20 <div class="main-content">
20 <div class="main-content">
21
21
22 <h2>Simple form elements (Depreciated)</h2>
22 <h2>Simple form elements (Depreciated)</h2>
23
23
24 <div class="bs-example">
24 <div class="bs-example">
25 <form method='post' action='none'>
25 <form method='post' action='none'>
26 <div class='form'>
26 <div class='form'>
27 <div class='fields'>
27 <div class='fields'>
28
28
29 <div class='field'>
29 <div class='field'>
30 <div class='label'>
30 <div class='label'>
31 <label for='example_input_ro'>Example input readonly:</label>
31 <label for='example_input_ro'>Example input readonly:</label>
32 </div>
32 </div>
33 <div class='input'>
33 <div class='input'>
34 <input id="example_input_ro" type="text" readonly="readonly" placeholder="Example input">
34 <input id="example_input_ro" type="text" readonly="readonly" placeholder="Example input">
35 </div>
35 </div>
36 </div>
36 </div>
37
37
38 <div class='field'>
38 <div class='field'>
39 <div class='label'>
39 <div class='label'>
40 <label for='example_input'>Example text:</label>
40 <label for='example_input'>Example text:</label>
41 </div>
41 </div>
42 <div class='input'>
42 <div class='input'>
43 <div class='text-as-placeholder'>
43 <div class='text-as-placeholder'>
44 http://something.example.com
44 http://something.example.com
45 <span class="link" id="edit_clone_uri"><i class="icon-edit"></i>${_('edit')}</span>
45 <span class="link" id="edit_clone_uri"><i class="icon-edit"></i>${_('edit')}</span>
46 </div>
46 </div>
47 <p class='help-block'>Help text in a paragraph.</p>
47 <p class='help-block'>Help text in a paragraph.</p>
48 </div>
48 </div>
49 </div>
49 </div>
50
50
51 <div class='field'>
51 <div class='field'>
52 <div class='label'>
52 <div class='label'>
53 <label for='example_select'>Example select input:</label>
53 <label for='example_select'>Example select input:</label>
54 </div>
54 </div>
55 <div class="select">
55 <div class="select">
56 <select id="example_select" >
56 <select id="example_select" >
57 <option value="#">${_('Templates...')}</option>
57 <option value="#">${_('Templates...')}</option>
58 <option value="ga">Google Analytics</option>
58 <option value="ga">Google Analytics</option>
59 <option value="clicky">Clicky</option>
59 <option value="clicky">Clicky</option>
60 <option value="server_announce">${_('Server Announcement')}</option>
60 <option value="server_announce">${_('Server Announcement')}</option>
61 </select>
61 </select>
62 </div>
62 </div>
63 </div>
63 </div>
64 <script>
64 <script>
65 $(document).ready(function() {
65 $(document).ready(function() {
66 $('#example_select').select2({
66 $('#example_select').select2({
67 containerCssClass: 'drop-menu',
67 containerCssClass: 'drop-menu',
68 dropdownCssClass: 'drop-menu-dropdown',
68 dropdownCssClass: 'drop-menu-dropdown',
69 dropdownAutoWidth: true,
69 dropdownAutoWidth: true,
70 minimumResultsForSearch: -1
70 minimumResultsForSearch: -1
71 });
71 });
72 });
72 });
73 </script>
73 </script>
74
74
75 <div class='field'>
75 <div class='field'>
76 <div class='label'>
76 <div class='label'>
77 <label for='example_select'>Example select input:</label>
77 <label for='example_select'>Example select input:</label>
78 </div>
78 </div>
79 <div class="select">
79 <div class="select">
80 text before
80 text before
81 <select id="example_select2" >
81 <select id="example_select2" >
82 <option value="#">${_('Templates...')}</option>
82 <option value="#">${_('Templates...')}</option>
83 <option value="ga">Google Analytics</option>
83 <option value="ga">Google Analytics</option>
84 <option value="clicky">Clicky</option>
84 <option value="clicky">Clicky</option>
85 <option value="server_announce">${_('Server Announcement')}</option>
85 <option value="server_announce">${_('Server Announcement')}</option>
86 </select>
86 </select>
87 text after
87 text after
88 </div>
88 </div>
89 </div>
89 </div>
90 <script>
90 <script>
91 $(document).ready(function() {
91 $(document).ready(function() {
92 $('#example_select2').select2({
92 $('#example_select2').select2({
93 containerCssClass: 'drop-menu',
93 containerCssClass: 'drop-menu',
94 dropdownCssClass: 'drop-menu-dropdown',
94 dropdownCssClass: 'drop-menu-dropdown',
95 dropdownAutoWidth: true,
95 dropdownAutoWidth: true,
96 minimumResultsForSearch: -1
96 minimumResultsForSearch: -1
97 });
97 });
98 });
98 });
99 </script>
99 </script>
100
100
101 <div class='field'>
101 <div class='field'>
102 <div class='label'>
102 <div class='label'>
103 <label for='example_select'>Example select input with submenus:</label>
103 <label for='example_select'>Example select input with submenus:</label>
104 </div>
104 </div>
105 <div class="select">
105 <div class="select">
106 <select id="example_select_sub" >
106 <select id="example_select_sub" >
107 <option value="#">${_('Default')}</option>
107 <option value="#">${_('Default')}</option>
108 <optgroup label="Group 1">
108 <optgroup label="Group 1">
109 <option>Option 1.1</option>
109 <option>Option 1.1</option>
110 </optgroup>
110 </optgroup>
111 <optgroup label="Group 2">
111 <optgroup label="Group 2">
112 <option>Option 2.1</option>
112 <option>Option 2.1</option>
113 <option>Option 2.2</option>
113 <option>Option 2.2</option>
114 </optgroup>
114 </optgroup>
115 <optgroup label="Group 3" disabled>
115 <optgroup label="Group 3" disabled>
116 <option>Option 3.1</option>
116 <option>Option 3.1</option>
117 <option>Option 3.2</option>
117 <option>Option 3.2</option>
118 <option>Option 3.3</option>
118 <option>Option 3.3</option>
119 </optgroup>
119 </optgroup>
120 </select>
120 </select>
121 </div>
121 </div>
122 </div>
122 </div>
123 <script>
123 <script>
124 $(document).ready(function() {
124 $(document).ready(function() {
125 $('#example_select_sub').select2({
125 $('#example_select_sub').select2({
126 containerCssClass: 'drop-menu',
126 containerCssClass: 'drop-menu',
127 dropdownCssClass: 'drop-menu-dropdown',
127 dropdownCssClass: 'drop-menu-dropdown',
128 dropdownAutoWidth: true,
128 dropdownAutoWidth: true,
129 minimumResultsForSearch: -1
129 minimumResultsForSearch: -1
130 });
130 });
131 });
131 });
132 </script>
132 </script>
133
133
134 <div class='field'>
134 <div class='field'>
135 <div class='label'>
135 <div class='label'>
136 <label for='example_checkbox'>Example checkbox:</label>
136 <label for='example_checkbox'>Example checkbox:</label>
137 </div>
137 </div>
138 <div class="checkboxes">
138 <div class="checkboxes">
139 <div class="checkbox">
139 <div class="checkbox">
140 <input id="example_checkbox" type="checkbox">
140 <input id="example_checkbox" type="checkbox">
141 <label for="example_checkbox">Label of the checkbox</label>
141 <label for="example_checkbox">Label of the checkbox</label>
142 </div>
142 </div>
143 </div>
143 </div>
144 </div>
144 </div>
145
145
146 <div class='field'>
146 <div class='field'>
147 <div class='label'>
147 <div class='label'>
148 <label for='example_checkboxes'>Example multiple checkboxes:</label>
148 <label for='example_checkboxes'>Example multiple checkboxes:</label>
149 </div>
149 </div>
150 <div class="checkboxes">
150 <div class="checkboxes">
151 <div class="checkbox">
151 <div class="checkbox">
152 <input id="example_checkboxes_01" type="checkbox">
152 <input id="example_checkboxes_01" type="checkbox">
153 <label for="example_checkboxes_01">Label of the first checkbox</label>
153 <label for="example_checkboxes_01">Label of the first checkbox</label>
154 </div>
154 </div>
155 <div class="checkbox">
155 <div class="checkbox">
156 <input id="example_checkboxes_02" type="checkbox">
156 <input id="example_checkboxes_02" type="checkbox">
157 <label for="example_checkboxes_02">Label of the first checkbox</label>
157 <label for="example_checkboxes_02">Label of the first checkbox</label>
158 </div>
158 </div>
159 <div class="checkbox">
159 <div class="checkbox">
160 <input id="example_checkboxes_03" type="checkbox">
160 <input id="example_checkboxes_03" type="checkbox">
161 <label for="example_checkboxes_03">Label of the first checkbox</label>
161 <label for="example_checkboxes_03">Label of the first checkbox</label>
162 </div>
162 </div>
163 </div>
163 </div>
164 </div>
164 </div>
165
165
166
166
167 <div class='field'>
167 <div class='field'>
168 <div class='label'>
168 <div class='label'>
169 <label for='example_checkboxes'>Example multiple checkboxes:</label>
169 <label for='example_checkboxes'>Example multiple checkboxes:</label>
170 </div>
170 </div>
171 ## TODO: johbo: This is off compared to the checkboxes
171 ## TODO: johbo: This is off compared to the checkboxes
172 <div class="radios">
172 <div class="radios">
173 <label><input type="radio" checked="checked" value="hg.create.repository" name="default_repo_create" id="default_repo_create_hgcreaterepository">Enabled</label>
173 <label><input type="radio" checked="checked" value="hg.create.repository" name="default_repo_create" id="default_repo_create_hgcreaterepository">Enabled</label>
174 <label><input type="radio" value="hg.create.none" name="default_repo_create" id="default_repo_create_hgcreatenone">Disabled</label>
174 <label><input type="radio" value="hg.create.none" name="default_repo_create" id="default_repo_create_hgcreatenone">Disabled</label>
175 <span class="help-block">
175 <span class="help-block">
176 Permission to allow repository creation. This includes ability to create
176 Permission to allow repository creation. This includes ability to create
177 repositories in root level. If this option is disabled admin of
177 repositories in root level. If this option is disabled admin of
178 repository group can still create repositories inside that
178 repository group can still create repositories inside that
179 repository group.
179 repository group.
180 </span>
180 </span>
181 </div>
181 </div>
182 </div>
182 </div>
183
183
184 <div class="buttons">
184 <div class="buttons">
185 <input type="submit" value="Save" id="example_save" class="btn">
185 <input type="submit" value="Save" id="example_save" class="btn">
186 <input type="reset" value="Reset" id="example_reset" class="btn">
186 <input type="reset" value="Reset" id="example_reset" class="btn">
187 </div>
187 </div>
188
188
189 </div>
189 </div>
190 </div>
190 </div>
191 </form>
191 </form>
192 </div>
192 </div>
193
193
194
194
195
195
196
196
197 <h2>Help text in form elements</h2>
197 <h2>Help text in form elements</h2>
198
198
199 <div class="bs-example">
199 <div class="bs-example">
200 <form method='post' action=''>
200 <form method='post' action=''>
201 <div class='form'>
201 <div class='form'>
202 <div class='fields'>
202 <div class='fields'>
203
203
204 <div class='field'>
204 <div class='field'>
205 <div class='label'>
205 <div class='label'>
206 <label for='02_example_input'>Example input label:</label>
206 <label for='02_example_input'>Example input label:</label>
207 </div>
207 </div>
208 <div class='input'>
208 <div class='input'>
209 <input id="02_example_input" type="text" placeholder="Placeholder text">
209 <input id="02_example_input" type="text" placeholder="Placeholder text">
210 <span class="help-block">
210 <span class="help-block">
211 Example help text for this input element. This help text
211 Example help text for this input element. This help text
212 will be shown under the input element itself. It can be
212 will be shown under the input element itself. It can be
213 so long that it will span multiple lines.
213 so long that it will span multiple lines.
214 </span>
214 </span>
215
215
216 </div>
216 </div>
217 </div>
217 </div>
218
218
219 <div class='field'>
219 <div class='field'>
220 <div class='label'>
220 <div class='label'>
221 <label for='example_select_help'>Example select input:</label>
221 <label for='example_select_help'>Example select input:</label>
222 </div>
222 </div>
223 <div class="select">
223 <div class="select">
224 <select id="example_select_help" >
224 <select id="example_select_help" >
225 <option value="#">${_('Templates...')}</option>
225 <option value="#">${_('Templates...')}</option>
226 <option value="ga">Google Analytics</option>
226 <option value="ga">Google Analytics</option>
227 <option value="clicky">Clicky</option>
227 <option value="clicky">Clicky</option>
228 <option value="server_announce">${_('Server Announcement')}</option>
228 <option value="server_announce">${_('Server Announcement')}</option>
229 </select>
229 </select>
230 <span class="help-block">
230 <span class="help-block">
231 Example help text for this input element. This help text
231 Example help text for this input element. This help text
232 will be shown under the input element itself. It can be
232 will be shown under the input element itself. It can be
233 so long that it will span multiple lines.
233 so long that it will span multiple lines.
234 </span>
234 </span>
235 </div>
235 </div>
236 </div>
236 </div>
237 <script>
237 <script>
238 $(document).ready(function() {
238 $(document).ready(function() {
239 $('#example_select_help').select2({
239 $('#example_select_help').select2({
240 containerCssClass: 'drop-menu',
240 containerCssClass: 'drop-menu',
241 dropdownCssClass: 'drop-menu-dropdown',
241 dropdownCssClass: 'drop-menu-dropdown',
242 dropdownAutoWidth: true,
242 dropdownAutoWidth: true,
243 minimumResultsForSearch: -1
243 minimumResultsForSearch: -1
244 });
244 });
245 });
245 });
246 </script>
246 </script>
247
247
248 <div class='field'>
248 <div class='field'>
249 <div class='label'>
249 <div class='label'>
250 <label for='02_example_checkbox'>Example checkbox with help block:</label>
250 <label for='02_example_checkbox'>Example checkbox with help block:</label>
251 </div>
251 </div>
252 <div class="checkboxes">
252 <div class="checkboxes">
253 <div class="checkbox">
253 <div class="checkbox">
254 <input id="02_example_checkbox" type="checkbox">
254 <input id="02_example_checkbox" type="checkbox">
255 <label for="02_example_checkbox">Label of the checkbox</label>
255 <label for="02_example_checkbox">Label of the checkbox</label>
256 </div>
256 </div>
257 <span class="help-block">
257 <span class="help-block">
258 Example help text for this checkbox element. This help text
258 Example help text for this checkbox element. This help text
259 will be shown under the checkbox element itself. It can be
259 will be shown under the checkbox element itself. It can be
260 so long that it will span multiple lines.
260 so long that it will span multiple lines.
261 </span>
261 </span>
262 </div>
262 </div>
263 </div>
263 </div>
264
264
265
265
266 <div class='field'>
266 <div class='field'>
267 <div class='label'>
267 <div class='label'>
268 <label>Multiple checkboxes:</label>
268 <label>Multiple checkboxes:</label>
269 </div>
269 </div>
270 <div class="checkboxes">
270 <div class="checkboxes">
271 <div class="checkbox">
271 <div class="checkbox">
272 <input id="02_example_checkboxes_01" type="checkbox">
272 <input id="02_example_checkboxes_01" type="checkbox">
273 <label for="02_example_checkboxes_01">Label of the first checkbox</label>
273 <label for="02_example_checkboxes_01">Label of the first checkbox</label>
274 </div>
274 </div>
275 <div class="checkbox">
275 <div class="checkbox">
276 <input id="02_example_checkboxes_02" type="checkbox">
276 <input id="02_example_checkboxes_02" type="checkbox">
277 <label for="02_example_checkboxes_02">Label of the first checkbox</label>
277 <label for="02_example_checkboxes_02">Label of the first checkbox</label>
278 </div>
278 </div>
279 <div class="checkbox">
279 <div class="checkbox">
280 <input id="02_example_checkboxes_03" type="checkbox">
280 <input id="02_example_checkboxes_03" type="checkbox">
281 <label for="02_example_checkboxes_03">Label of the first checkbox</label>
281 <label for="02_example_checkboxes_03">Label of the first checkbox</label>
282 </div>
282 </div>
283 <span class="help-block">
283 <span class="help-block">
284 Example help text for this checkbox element. This help text
284 Example help text for this checkbox element. This help text
285 will be shown under the checkbox element itself. It can be
285 will be shown under the checkbox element itself. It can be
286 so long that it will span multiple lines.
286 so long that it will span multiple lines.
287 </span>
287 </span>
288 </div>
288 </div>
289 </div>
289 </div>
290
290
291
291
292 </div>
292 </div>
293 </div>
293 </div>
294 </form>
294 </form>
295 </div>
295 </div>
296
296
297
297
298
298
299
299
300 <h2>Error messages</h2>
300 <h2>Error messages</h2>
301
301
302 <div class="bs-example">
302 <div class="bs-example">
303 <form method='post' action=''>
303 <form method='post' action=''>
304 <div class='form'>
304 <div class='form'>
305 <div class='fields'>
305 <div class='fields'>
306
306
307 <div class='field'>
307 <div class='field'>
308 <div class='label'>
308 <div class='label'>
309 <label for='04_example_input'>Example input label:</label>
309 <label for='04_example_input'>Example input label:</label>
310 </div>
310 </div>
311 <div class='input'>
311 <div class='input'>
312 <input id="04_example_input" type="text" placeholder="Example input"/>
312 <input id="04_example_input" type="text" placeholder="Example input"/>
313 <span class="error-message">
313 <span class="error-message">
314 If form validation fails, some input fields can show an
314 If form validation fails, some input fields can show an
315 error message close to the field.
315 error message close to the field.
316 </span>
316 </span>
317 </div>
317 </div>
318 </div>
318 </div>
319
319
320 </div>
320 </div>
321 </div>
321 </div>
322 </form>
322 </form>
323 </div>
323 </div>
324
324
325
325
326
326
327
327
328 <h2>Fields with buttons</h2>
328 <h2>Fields with buttons</h2>
329
329
330 <div class="bs-example">
330 <div class="bs-example">
331 <form method='post' action=''>
331 <form method='post' action=''>
332 <div class='form'>
332 <div class='form'>
333 <div class='fields'>
333 <div class='fields'>
334
334
335 <div class='field'>
335 <div class='field'>
336 <div class='label'>
336 <div class='label'>
337 <label for='05_example_input'>Example input label:</label>
337 <label for='05_example_input'>Example input label:</label>
338 </div>
338 </div>
339 <div class='input'>
339 <div class='input'>
340 <input id="05_example_input" type="text" readonly="readonly" placeholder="Example input">
340 <input id="05_example_input" type="text" readonly="readonly" placeholder="Example input">
341 <span class="btn btn-x">
341 <span class="btn btn-x">
342 <i class="icon-remove-sign"></i>
342 <i class="icon-remove-sign"></i>
343 delete
343 delete
344 </span>
344 </span>
345 <button class='btn btn-primary'>Action</button>
345 <button class='btn btn-primary'>Action</button>
346 <span class="help-block">
346 <span class="help-block">
347 Used if there is a list of values and the user can remove
347 Used if there is a list of values and the user can remove
348 single entries.
348 single entries.
349 </span>
349 </span>
350 </div>
350 </div>
351 </div>
351 </div>
352
352
353
353
354 <div class='field'>
354 <div class='field'>
355 <div class='label'>
355 <div class='label'>
356 <label for='05_example_input'>Example input label:</label>
356 <label for='05_example_input'>Example input label:</label>
357 </div>
357 </div>
358 <div class='input'>
358 <div class='input'>
359 <input id="05_example_input" type="text" readonly="readonly" placeholder="Example input">
359 <input id="05_example_input" type="text" readonly="readonly" placeholder="Example input">
360 <span title="Click to unlock. You must restart RhodeCode in order to make this setting take effect."
360 <span title="Click to unlock. You must restart RhodeCode in order to make this setting take effect."
361 class="tooltip" id="path_unlock"
361 class="tooltip" id="path_unlock"
362 tt_title="Click to unlock. You must restart RhodeCode in order to make this setting take effect.">
362 tt_title="Click to unlock. You must restart RhodeCode in order to make this setting take effect.">
363 <div class="btn btn-default">
363 <div class="btn btn-default">
364 <span><i class="icon-lock" id="path_unlock_icon"></i></span>
364 <span><i class="icon-lock" id="path_unlock_icon"></i></span>
365 </div>
365 </div>
366 <button class='btn btn-primary'>Action</button>
366 <button class='btn btn-primary'>Action</button>
367 </span>
367 </span>
368 <span class="help-block">
368 <span class="help-block">
369 Used together with locked fields, the user has to first
369 Used together with locked fields, the user has to first
370 unlock and afterwards it is possible to change the value.
370 unlock and afterwards it is possible to change the value.
371 </span>
371 </span>
372 </div>
372 </div>
373 </div>
373 </div>
374
374
375 <div class='field'>
375 <div class='field'>
376 <div class='label'>
376 <div class='label'>
377 <label for='05_example_select'>Example input label:</label>
377 <label for='05_example_select'>Example input label:</label>
378 </div>
378 </div>
379 <div class="select">
379 <div class="select">
380 <select id="05_example_select" >
380 <select id="05_example_select" >
381 <option value="#">${_('Templates...')}</option>
381 <option value="#">${_('Templates...')}</option>
382 <option value="ga">Google Analytics</option>
382 <option value="ga">Google Analytics</option>
383 <option value="clicky">Clicky</option>
383 <option value="clicky">Clicky</option>
384 <option value="server_announce">${_('Server Announcement')}</option>
384 <option value="server_announce">${_('Server Announcement')}</option>
385 </select>
385 </select>
386 <button class='btn btn-primary'>Action</button>
386 <button class='btn btn-primary'>Action</button>
387 </div>
387 </div>
388 </div>
388 </div>
389 <script>
389 <script>
390 $(document).ready(function() {
390 $(document).ready(function() {
391 $('#05_example_select').select2({
391 $('#05_example_select').select2({
392 containerCssClass: 'drop-menu',
392 containerCssClass: 'drop-menu',
393 dropdownCssClass: 'drop-menu-dropdown',
393 dropdownCssClass: 'drop-menu-dropdown',
394 dropdownAutoWidth: true
394 dropdownAutoWidth: true
395 });
395 });
396 });
396 });
397 </script>
397 </script>
398
398
399 <div class='field'>
399 <div class='field'>
400 <div class='label'>
400 <div class='label'>
401 <label for='05_example_select2'>Example input label:</label>
401 <label for='05_example_select2'>Example input label:</label>
402 </div>
402 </div>
403 <div class="select">
403 <div class="select">
404 <span>Some text</span>
404 <span>Some text</span>
405 before
405 before
406 <select id="05_example_select2" >
406 <select id="05_example_select2" >
407 <option value="#">${_('Templates...')}</option>
407 <option value="#">${_('Templates...')}</option>
408 <option value="ga">Google Analytics</option>
408 <option value="ga">Google Analytics</option>
409 <option value="clicky">Clicky</option>
409 <option value="clicky">Clicky</option>
410 <option value="server_announce">${_('Server Announcement')}</option>
410 <option value="server_announce">${_('Server Announcement')}</option>
411 </select>
411 </select>
412 after
412 after
413 <button class='btn btn-primary'>Action</button>
413 <button class='btn btn-primary'>Action</button>
414 Some text
414 Some text
415 </div>
415 </div>
416 </div>
416 </div>
417 <script>
417 <script>
418 $(document).ready(function() {
418 $(document).ready(function() {
419 $('#05_example_select2').select2({
419 $('#05_example_select2').select2({
420 containerCssClass: 'drop-menu',
420 containerCssClass: 'drop-menu',
421 dropdownCssClass: 'drop-menu-dropdown',
421 dropdownCssClass: 'drop-menu-dropdown',
422 dropdownAutoWidth: true
422 dropdownAutoWidth: true
423 });
423 });
424 });
424 });
425 </script>
425 </script>
426
426
427
427
428 </div>
428 </div>
429 </div>
429 </div>
430 </form>
430 </form>
431 </div>
431 </div>
432
432
433
433
434
434
435 <h2>Definition lists together with forms</h2>
435 <h2>Definition lists together with forms</h2>
436
436
437 <p>Some pages list values in a definition list. These lists align
437 <p>Some pages list values in a definition list. These lists align
438 properly with form elements on the same page.</p>
438 properly with form elements on the same page.</p>
439
439
440 <div class="bs-example">
440 <div class="bs-example">
441
441
442 <dl class="dl-horizontal">
442 <dl class="dl-horizontal">
443 <dt>RhodeCode version:</dt>
443 <dt>RhodeCode version:</dt>
444 <dd title="">3.0.0</dd>
444 <dd title="">3.0.0</dd>
445 <dt>License token:</dt>
445 <dt>License token:</dt>
446 <dd title=""><pre>abra-cada-bra1-rce3</pre></dd>
446 <dd title=""><pre>abra-cada-bra1-rce3</pre></dd>
447 <dt>License issued to:</dt>
447 <dt>License issued to:</dt>
448 <dd title="">RhodeCode Trial (RhodeCode GmbH)</dd>
448 <dd title="">RhodeCode Trial (RhodeCode GmbH)</dd>
449 <dt>License issued on:</dt>
449 <dt>License issued on:</dt>
450 <dd title="">Sun, 07 Dec 2014 16:34:10</dd>
450 <dd title="">Sun, 07 Dec 2014 16:34:10</dd>
451 <dt>License expires on:</dt>
451 <dt>License expires on:</dt>
452 <dd title="">Fri, 05 Jun 2015 17:34:10</dd>
452 <dd title="">Fri, 05 Jun 2015 17:34:10</dd>
453 <dt>License type:</dt>
453 <dt>License type:</dt>
454 <dd title="">trial</dd>
454 <dd title="">trial</dd>
455 <dt>License users limit:</dt>
455 <dt>License users limit:</dt>
456 <dd title="">20</dd>
456 <dd title="">20</dd>
457 </dl>
457 </dl>
458
458
459 <form method='post' action=''>
459 <form method='post' action=''>
460 <div class='form'>
460 <div class='form'>
461 <div class='fields'>
461 <div class='fields'>
462
462
463 <div class='field'>
463 <div class='field'>
464 <div class='label'>
464 <div class='label'>
465 <label for='07_example_input'>Example input label:</label>
465 <label for='07_example_input'>Example input label:</label>
466 </div>
466 </div>
467 <div class='input'>
467 <div class='input'>
468 <input id="07_example_input" type="text" placeholder="Example input">
468 <input id="07_example_input" type="text" placeholder="Example input">
469 </div>
469 </div>
470 </div>
470 </div>
471
471
472 <div class="buttons">
472 <div class="buttons">
473 <input type="submit" value="Save" id="07_example_save" class="btn">
473 <input type="submit" value="Save" id="07_example_save" class="btn">
474 <input type="reset" value="Reset" id="07_example_reset" class="btn">
474 <input type="reset" value="Reset" id="07_example_reset" class="btn">
475 </div>
475 </div>
476 </div>
476 </div>
477 </div>
477 </div>
478 </form>
478 </form>
479
479
480 </div>
480 </div>
481
481
482
482
483
483
484
484
485
485
486 <h2>Multi select widget</h2>
486 <h2>Multi select widget</h2>
487
487
488 <p>This example shows two multi select widgets, one having no selects
488 <p>This example shows two multi select widgets, one having no selects
489 currently. It is mixed up with other form elements to show the
489 currently. It is mixed up with other form elements to show the
490 magin effects.</p>
490 magin effects.</p>
491
491
492 <div class="bs-example">
492 <div class="bs-example">
493
493
494 <form method='post' action=''>
494 <form method='post' action=''>
495 <div class='form'>
495 <div class='form'>
496 <div class='fields'>
496 <div class='fields'>
497
497
498 <div class='field'>
498 <div class='field'>
499 <div class='label'>
499 <div class='label'>
500 <label for='example_input'>Example input label:</label>
500 <label for='example_input'>Example input label:</label>
501 </div>
501 </div>
502 <div class='input'>
502 <div class='input'>
503 <input id="example_input" type="text" placeholder="Example input">
503 <input id="example_input" type="text" placeholder="Example input">
504 </div>
504 </div>
505 </div>
505 </div>
506
506
507 <div class="field">
507 <div class="field">
508 <div class="label">
508 <div class="label">
509 <label for="users_group_active">${_('Members')}:</label>
509 <label for="users_group_active">${_('Members')}:</label>
510 </div>
510 </div>
511 <div class="select side-by-side-selector">
511 <div class="select side-by-side-selector">
512 <div class="left-group">
512 <div class="left-group">
513 <label class="text" >${_('Chosen group members')}</label>
513 <label class="text" >${_('Chosen group members')}</label>
514 <select id="users_group_members" multiple size='8'>
514 <select id="users_group_members" multiple size='8'>
515 <option value="#">${_('Templates...')}</option>
515 <option value="#">${_('Templates...')}</option>
516 <option value="ga">Google Analytics</option>
516 <option value="ga">Google Analytics</option>
517 <option value="clicky">Clicky</option>
517 <option value="clicky">Clicky</option>
518 <option value="server_announce">${_('Server Announcement')}</option>
518 <option value="server_announce">${_('Server Announcement')}</option>
519 <option value="#">${_('Templates...')}</option>
519 <option value="#">${_('Templates...')}</option>
520 <option value="ga">Google Analytics</option>
520 <option value="ga">Google Analytics</option>
521 <option value="clicky">Clicky</option>
521 <option value="clicky">Clicky</option>
522 <option value="server_announce">${_('Server Announcement')}</option>
522 <option value="server_announce">${_('Server Announcement')}</option>
523 </select>
523 </select>
524 <div class="btn" id="remove_all_elements" >
524 <div class="btn" id="remove_all_elements" >
525 ${_('Remove all elements')}
525 ${_('Remove all elements')}
526 <i class="icon-chevron-right"></i>
526 <i class="icon-chevron-right"></i>
527 </div>
527 </div>
528 </div>
528 </div>
529 <div class="middle-group">
529 <div class="middle-group">
530 <i id="add_element" class="icon-chevron-left"></i>
530 <i id="add_element" class="icon-chevron-left"></i>
531 <br />
531 <br />
532 <i id="remove_element" class="icon-chevron-right"></i>
532 <i id="remove_element" class="icon-chevron-right"></i>
533 </div>
533 </div>
534 <div class="right-group">
534 <div class="right-group">
535 <label class="text" >${_('Available members')}</label>
535 <label class="text" >${_('Available members')}</label>
536 <select id="available_members" multiple size='8'>
536 <select id="available_members" multiple size='8'>
537 <option value="#">${_('Templates...')}</option>
537 <option value="#">${_('Templates...')}</option>
538 <option value="ga">Google Analytics</option>
538 <option value="ga">Google Analytics</option>
539 <option value="clicky">Clicky</option>
539 <option value="clicky">Clicky</option>
540 <option value="server_announce">${_('Server Announcement')}</option>
540 <option value="server_announce">${_('Server Announcement')}</option>
541 </select>
541 </select>
542 <div class="btn" id="add_all_elements" >
542 <div class="btn" id="add_all_elements" >
543 <i class="icon-chevron-left"></i>${_('Add all elements')}
543 <i class="icon-chevron-left"></i>${_('Add all elements')}
544 </div>
544 </div>
545 </div>
545 </div>
546 </div>
546 </div>
547
547
548 <script>
549 $(document).ready(function(){
550 MultiSelectWidget('users_group_members','available_members','edit_users_group');
551 })
552 </script>
553
554 </div>
548 </div>
555
549
556 <div class='field'>
550 <div class='field'>
557 <div class='label'>
551 <div class='label'>
558 <label for='example_input'>Example input label:</label>
552 <label for='example_input'>Example input label:</label>
559 </div>
553 </div>
560 <div class='input'>
554 <div class='input'>
561 <input id="example_input" type="text" placeholder="Example input">
555 <input id="example_input" type="text" placeholder="Example input">
562 </div>
556 </div>
563 </div>
557 </div>
564
558
565 <div class="field">
559 <div class="field">
566 <div class="label">
560 <div class="label">
567 <label for="users_group_active2">Members with one side empty:</label>
561 <label for="users_group_active2">Members with one side empty:</label>
568 </div>
562 </div>
569 <div class="select side-by-side-selector">
563 <div class="select side-by-side-selector">
570 <div class="left-group">
564 <div class="left-group">
571 <label class="text" >${_('Chosen group members')}</label>
565 <label class="text" >${_('Chosen group members')}</label>
572 <select id="users_group_members2" multiple size='8'>
566 <select id="users_group_members2" multiple size='8'>
573 </select>
567 </select>
574 <div class="btn" id="remove_all_elements2" >
568 <div class="btn" id="remove_all_elements2" >
575 ${_('Remove all elements')}
569 ${_('Remove all elements')}
576 <i class="icon-chevron-right"></i>
570 <i class="icon-chevron-right"></i>
577 </div>
571 </div>
578 </div>
572 </div>
579 <div class="middle-group">
573 <div class="middle-group">
580 <i id="add_element2" class="icon-chevron-left"></i>
574 <i id="add_element2" class="icon-chevron-left"></i>
581 <br />
575 <br />
582 <i id="remove_element2" class="icon-chevron-right"></i>
576 <i id="remove_element2" class="icon-chevron-right"></i>
583 </div>
577 </div>
584 <div class="right-group">
578 <div class="right-group">
585 <label class="text" >${_('Available members')}</label>
579 <label class="text" >${_('Available members')}</label>
586 <select id="available_members2" multiple size='8'>
580 <select id="available_members2" multiple size='8'>
587 <option value="#">${_('Templates...')}</option>
581 <option value="#">${_('Templates...')}</option>
588 <option value="ga">Google Analytics</option>
582 <option value="ga">Google Analytics</option>
589 <option value="clicky">Clicky</option>
583 <option value="clicky">Clicky</option>
590 <option value="server_announce">${_('Server Announcement')}</option>
584 <option value="server_announce">${_('Server Announcement')}</option>
591 </select>
585 </select>
592 <div class="btn" id="add_all_elements2" >
586 <div class="btn" id="add_all_elements2" >
593 <i class="icon-chevron-left"></i>${_('Add all elements')}
587 <i class="icon-chevron-left"></i>${_('Add all elements')}
594 </div>
588 </div>
595 </div>
589 </div>
596 </div>
590 </div>
597
591
598 <script>
599 $(document).ready(function(){
600 MultiSelectWidget('users_group_members2','available_members','edit_users_group');
601 })
602 </script>
603
604 </div>
592 </div>
605
593
606 <div class='field'>
594 <div class='field'>
607 <div class='label'>
595 <div class='label'>
608 <label for='example_input'>Example input label:</label>
596 <label for='example_input'>Example input label:</label>
609 </div>
597 </div>
610 <div class='input'>
598 <div class='input'>
611 <input id="example_input" type="text" placeholder="Example input">
599 <input id="example_input" type="text" placeholder="Example input">
612 </div>
600 </div>
613 </div>
601 </div>
614
602
615 <div class="buttons">
603 <div class="buttons">
616 <input type="submit" value="Save" id="07_example_save" class="btn">
604 <input type="submit" value="Save" id="07_example_save" class="btn">
617 <input type="reset" value="Reset" id="07_example_reset" class="btn">
605 <input type="reset" value="Reset" id="07_example_reset" class="btn">
618 </div>
606 </div>
619 </div>
607 </div>
620 </div>
608 </div>
621 </form>
609 </form>
622
610
623 </div>
611 </div>
624
612
625
613
626
614
627
615
628 </div>
616 </div>
629 </div> <!-- .main-content -->
617 </div> <!-- .main-content -->
630 </div> <!-- .box -->
618 </div> <!-- .box -->
631 </%def>
619 </%def>
@@ -1,192 +1,233 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.tests import (
23 from rhodecode.tests import (
24 TestController, url, assert_session_flash, link_to)
24 TestController, url, assert_session_flash, link_to)
25 from rhodecode.model.db import User, UserGroup
25 from rhodecode.model.db import User, UserGroup
26 from rhodecode.model.meta import Session
26 from rhodecode.model.meta import Session
27 from rhodecode.tests.fixture import Fixture
27 from rhodecode.tests.fixture import Fixture
28
28
29 TEST_USER_GROUP = 'admins_test'
29 TEST_USER_GROUP = 'admins_test'
30
30
31 fixture = Fixture()
31 fixture = Fixture()
32
32
33
33
34 class TestAdminUsersGroupsController(TestController):
34 class TestAdminUsersGroupsController(TestController):
35
35
36 def test_index(self):
36 def test_index(self):
37 self.log_user()
37 self.log_user()
38 response = self.app.get(url('users_groups'))
38 response = self.app.get(url('users_groups'))
39 response.status_int == 200
39 assert response.status_int == 200
40
40
41 def test_create(self):
41 def test_create(self):
42 self.log_user()
42 self.log_user()
43 users_group_name = TEST_USER_GROUP
43 users_group_name = TEST_USER_GROUP
44 response = self.app.post(url('users_groups'), {
44 response = self.app.post(url('users_groups'), {
45 'users_group_name': users_group_name,
45 'users_group_name': users_group_name,
46 'user_group_description': 'DESC',
46 'user_group_description': 'DESC',
47 'active': True,
47 'active': True,
48 'csrf_token': self.csrf_token})
48 'csrf_token': self.csrf_token})
49
49
50 user_group_link = link_to(
50 user_group_link = link_to(
51 users_group_name,
51 users_group_name,
52 url('edit_users_group',
52 url('edit_users_group',
53 user_group_id=UserGroup.get_by_group_name(
53 user_group_id=UserGroup.get_by_group_name(
54 users_group_name).users_group_id))
54 users_group_name).users_group_id))
55 assert_session_flash(
55 assert_session_flash(
56 response,
56 response,
57 'Created user group %s' % user_group_link)
57 'Created user group %s' % user_group_link)
58
58
59 def test_delete(self):
59 def test_delete(self):
60 self.log_user()
60 self.log_user()
61 users_group_name = TEST_USER_GROUP + 'another'
61 users_group_name = TEST_USER_GROUP + 'another'
62 response = self.app.post(url('users_groups'), {
62 response = self.app.post(url('users_groups'), {
63 'users_group_name': users_group_name,
63 'users_group_name': users_group_name,
64 'user_group_description': 'DESC',
64 'user_group_description': 'DESC',
65 'active': True,
65 'active': True,
66 'csrf_token': self.csrf_token})
66 'csrf_token': self.csrf_token})
67
67
68 user_group_link = link_to(
68 user_group_link = link_to(
69 users_group_name,
69 users_group_name,
70 url('edit_users_group',
70 url('edit_users_group',
71 user_group_id=UserGroup.get_by_group_name(
71 user_group_id=UserGroup.get_by_group_name(
72 users_group_name).users_group_id))
72 users_group_name).users_group_id))
73 assert_session_flash(
73 assert_session_flash(
74 response,
74 response,
75 'Created user group %s' % user_group_link)
75 'Created user group %s' % user_group_link)
76
76
77 group = Session().query(UserGroup).filter(
77 group = Session().query(UserGroup).filter(
78 UserGroup.users_group_name == users_group_name).one()
78 UserGroup.users_group_name == users_group_name).one()
79
79
80 response = self.app.post(
80 response = self.app.post(
81 url('delete_users_group', user_group_id=group.users_group_id),
81 url('delete_users_group', user_group_id=group.users_group_id),
82 params={'_method': 'delete', 'csrf_token': self.csrf_token})
82 params={'_method': 'delete', 'csrf_token': self.csrf_token})
83
83
84 group = Session().query(UserGroup).filter(
84 group = Session().query(UserGroup).filter(
85 UserGroup.users_group_name == users_group_name).scalar()
85 UserGroup.users_group_name == users_group_name).scalar()
86
86
87 assert group is None
87 assert group is None
88
88
89 @pytest.mark.parametrize('repo_create, repo_create_write, user_group_create, repo_group_create, fork_create, inherit_default_permissions, expect_error, expect_form_error', [
89 @pytest.mark.parametrize('repo_create, repo_create_write, user_group_create, repo_group_create, fork_create, inherit_default_permissions, expect_error, expect_form_error', [
90 ('hg.create.none', 'hg.create.write_on_repogroup.false', 'hg.usergroup.create.false', 'hg.repogroup.create.false', 'hg.fork.none', 'hg.inherit_default_perms.false', False, False),
90 ('hg.create.none', 'hg.create.write_on_repogroup.false', 'hg.usergroup.create.false', 'hg.repogroup.create.false', 'hg.fork.none', 'hg.inherit_default_perms.false', False, False),
91 ('hg.create.repository', 'hg.create.write_on_repogroup.true', 'hg.usergroup.create.true', 'hg.repogroup.create.true', 'hg.fork.repository', 'hg.inherit_default_perms.false', False, False),
91 ('hg.create.repository', 'hg.create.write_on_repogroup.true', 'hg.usergroup.create.true', 'hg.repogroup.create.true', 'hg.fork.repository', 'hg.inherit_default_perms.false', False, False),
92 ('hg.create.XXX', 'hg.create.write_on_repogroup.true', 'hg.usergroup.create.true', 'hg.repogroup.create.true', 'hg.fork.repository', 'hg.inherit_default_perms.false', False, True),
92 ('hg.create.XXX', 'hg.create.write_on_repogroup.true', 'hg.usergroup.create.true', 'hg.repogroup.create.true', 'hg.fork.repository', 'hg.inherit_default_perms.false', False, True),
93 ('', '', '', '', '', '', True, False),
93 ('', '', '', '', '', '', True, False),
94 ])
94 ])
95 def test_global_perms_on_group(
95 def test_global_perms_on_group(
96 self, repo_create, repo_create_write, user_group_create,
96 self, repo_create, repo_create_write, user_group_create,
97 repo_group_create, fork_create, expect_error, expect_form_error,
97 repo_group_create, fork_create, expect_error, expect_form_error,
98 inherit_default_permissions):
98 inherit_default_permissions):
99 self.log_user()
99 self.log_user()
100 users_group_name = TEST_USER_GROUP + 'another2'
100 users_group_name = TEST_USER_GROUP + 'another2'
101 response = self.app.post(url('users_groups'),
101 response = self.app.post(url('users_groups'),
102 {'users_group_name': users_group_name,
102 {'users_group_name': users_group_name,
103 'user_group_description': 'DESC',
103 'user_group_description': 'DESC',
104 'active': True,
104 'active': True,
105 'csrf_token': self.csrf_token})
105 'csrf_token': self.csrf_token})
106
106
107 ug = UserGroup.get_by_group_name(users_group_name)
107 ug = UserGroup.get_by_group_name(users_group_name)
108 user_group_link = link_to(
108 user_group_link = link_to(
109 users_group_name,
109 users_group_name,
110 url('edit_users_group', user_group_id=ug.users_group_id))
110 url('edit_users_group', user_group_id=ug.users_group_id))
111 assert_session_flash(
111 assert_session_flash(
112 response,
112 response,
113 'Created user group %s' % user_group_link)
113 'Created user group %s' % user_group_link)
114 response.follow()
114 response.follow()
115
115
116 # ENABLE REPO CREATE ON A GROUP
116 # ENABLE REPO CREATE ON A GROUP
117 perm_params = {
117 perm_params = {
118 'inherit_default_permissions': False,
118 'inherit_default_permissions': False,
119 'default_repo_create': repo_create,
119 'default_repo_create': repo_create,
120 'default_repo_create_on_write': repo_create_write,
120 'default_repo_create_on_write': repo_create_write,
121 'default_user_group_create': user_group_create,
121 'default_user_group_create': user_group_create,
122 'default_repo_group_create': repo_group_create,
122 'default_repo_group_create': repo_group_create,
123 'default_fork_create': fork_create,
123 'default_fork_create': fork_create,
124 'default_inherit_default_permissions': inherit_default_permissions,
124 'default_inherit_default_permissions': inherit_default_permissions,
125
125
126 '_method': 'put',
126 '_method': 'put',
127 'csrf_token': self.csrf_token,
127 'csrf_token': self.csrf_token,
128 }
128 }
129 response = self.app.post(
129 response = self.app.post(
130 url('edit_user_group_global_perms',
130 url('edit_user_group_global_perms',
131 user_group_id=ug.users_group_id),
131 user_group_id=ug.users_group_id),
132 params=perm_params)
132 params=perm_params)
133
133
134 if expect_form_error:
134 if expect_form_error:
135 assert response.status_int == 200
135 assert response.status_int == 200
136 response.mustcontain('Value must be one of')
136 response.mustcontain('Value must be one of')
137 else:
137 else:
138 if expect_error:
138 if expect_error:
139 msg = 'An error occurred during permissions saving'
139 msg = 'An error occurred during permissions saving'
140 else:
140 else:
141 msg = 'User Group global permissions updated successfully'
141 msg = 'User Group global permissions updated successfully'
142 ug = UserGroup.get_by_group_name(users_group_name)
142 ug = UserGroup.get_by_group_name(users_group_name)
143 del perm_params['_method']
143 del perm_params['_method']
144 del perm_params['csrf_token']
144 del perm_params['csrf_token']
145 del perm_params['inherit_default_permissions']
145 del perm_params['inherit_default_permissions']
146 assert perm_params == ug.get_default_perms()
146 assert perm_params == ug.get_default_perms()
147 assert_session_flash(response, msg)
147 assert_session_flash(response, msg)
148
148
149 fixture.destroy_user_group(users_group_name)
149 fixture.destroy_user_group(users_group_name)
150
150
151 def test_edit(self):
151 def test_edit_autocomplete(self):
152 self.log_user()
152 self.log_user()
153 ug = fixture.create_user_group(TEST_USER_GROUP, skip_if_exists=True)
153 ug = fixture.create_user_group(TEST_USER_GROUP, skip_if_exists=True)
154 response = self.app.get(
154 response = self.app.get(
155 url('edit_users_group', user_group_id=ug.users_group_id))
155 url('edit_users_group', user_group_id=ug.users_group_id))
156 fixture.destroy_user_group(TEST_USER_GROUP)
156 fixture.destroy_user_group(TEST_USER_GROUP)
157
157
158 def test_edit_user_group_members(self):
158 def test_edit_user_group_autocomplete_members(self, xhr_header):
159 self.log_user()
159 self.log_user()
160 ug = fixture.create_user_group(TEST_USER_GROUP, skip_if_exists=True)
160 ug = fixture.create_user_group(TEST_USER_GROUP, skip_if_exists=True)
161 response = self.app.get(
161 response = self.app.get(
162 url('edit_user_group_members', user_group_id=ug.users_group_id))
162 url('edit_user_group_members', user_group_id=ug.users_group_id),
163 response.mustcontain('No members yet')
163 extra_environ=xhr_header)
164
165 assert response.body == '{"members": []}'
164 fixture.destroy_user_group(TEST_USER_GROUP)
166 fixture.destroy_user_group(TEST_USER_GROUP)
165
167
166 def test_usergroup_escape(self):
168 def test_usergroup_escape(self):
167 user = User.get_by_username('test_admin')
169 user = User.get_by_username('test_admin')
168 user.name = '<img src="/image1" onload="alert(\'Hello, World!\');">'
170 user.name = '<img src="/image1" onload="alert(\'Hello, World!\');">'
169 user.lastname = (
171 user.lastname = (
170 '<img src="/image2" onload="alert(\'Hello, World!\');">')
172 '<img src="/image2" onload="alert(\'Hello, World!\');">')
171 Session().add(user)
173 Session().add(user)
172 Session().commit()
174 Session().commit()
173
175
174 self.log_user()
176 self.log_user()
175 users_group_name = 'samplegroup'
177 users_group_name = 'samplegroup'
176 data = {
178 data = {
177 'users_group_name': users_group_name,
179 'users_group_name': users_group_name,
178 'user_group_description': (
180 'user_group_description': (
179 '<strong onload="alert();">DESC</strong>'),
181 '<strong onload="alert();">DESC</strong>'),
180 'active': True,
182 'active': True,
181 'csrf_token': self.csrf_token
183 'csrf_token': self.csrf_token
182 }
184 }
183
185
184 response = self.app.post(url('users_groups'), data)
186 self.app.post(url('users_groups'), data)
185 response = self.app.get(url('users_groups'))
187 response = self.app.get(url('users_groups'))
186
188
187 response.mustcontain(
189 response.mustcontain(
188 '&lt;strong onload=&#34;alert();&#34;&gt;'
190 '&lt;strong onload=&#34;alert();&#34;&gt;'
189 'DESC&lt;/strong&gt;')
191 'DESC&lt;/strong&gt;')
190 response.mustcontain(
192 response.mustcontain(
191 '&lt;img src=&#34;/image2&#34; onload=&#34;'
193 '&lt;img src=&#34;/image2&#34; onload=&#34;'
192 'alert(&#39;Hello, World!&#39;);&#34;&gt;')
194 'alert(&#39;Hello, World!&#39;);&#34;&gt;')
195
196 def test_update_members_from_user_ids(self, user_regular):
197 uid = user_regular.user_id
198 username = user_regular.username
199 self.log_user()
200
201 user_group = fixture.create_user_group('test_gr_ids')
202 assert user_group.members == []
203 assert user_group.user != user_regular
204 expected_active_state = not user_group.users_group_active
205
206 form_data = [
207 ('csrf_token', self.csrf_token),
208 ('_method', 'put'),
209 ('user', username),
210 ('users_group_name', 'changed_name'),
211 ('users_group_active', expected_active_state),
212 ('user_group_description', 'changed_description'),
213
214 ('__start__', 'user_group_members:sequence'),
215 ('__start__', 'member:mapping'),
216 ('member_user_id', uid),
217 ('type', 'existing'),
218 ('__end__', 'member:mapping'),
219 ('__end__', 'user_group_members:sequence'),
220 ]
221 ugid = user_group.users_group_id
222 self.app.post(url('update_users_group', user_group_id=ugid), form_data)
223
224 user_group = UserGroup.get(ugid)
225 assert user_group
226
227 assert user_group.members[0].user_id == uid
228 assert user_group.user_id == uid
229 assert 'changed_name' in user_group.users_group_name
230 assert 'changed_description' in user_group.user_group_description
231 assert user_group.users_group_active == expected_active_state
232
233 fixture.destroy_user_group(user_group)
@@ -1,181 +1,161 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.db import User
24 from rhodecode.model.db import User
25 from rhodecode.tests import TEST_USER_REGULAR_LOGIN
25 from rhodecode.tests import TEST_USER_REGULAR_LOGIN
26 from rhodecode.tests.fixture import Fixture
26 from rhodecode.tests.fixture import Fixture
27 from rhodecode.model.user_group import UserGroupModel
27 from rhodecode.model.user_group import UserGroupModel
28 from rhodecode.model.meta import Session
28 from rhodecode.model.meta import Session
29
29
30
30
31 fixture = Fixture()
31 fixture = Fixture()
32
32
33
33
34 def teardown_module(self):
34 def teardown_module(self):
35 _delete_all_user_groups()
35 _delete_all_user_groups()
36
36
37
37
38 @pytest.mark.parametrize(
38 @pytest.mark.parametrize(
39 "pre_existing, regular_should_be, external_should_be, groups, "
39 "pre_existing, regular_should_be, external_should_be, groups, "
40 "expected", [
40 "expected", [
41 ([], [], [], [], []),
41 ([], [], [], [], []),
42 # no changes of regular
42 # no changes of regular
43 ([], ['regular'], [], [], ['regular']),
43 ([], ['regular'], [], [], ['regular']),
44 # not added to regular group
44 # not added to regular group
45 (['some_other'], [], [], ['some_other'], []),
45 (['some_other'], [], [], ['some_other'], []),
46 (
46 (
47 [], ['regular'], ['container'], ['container'],
47 [], ['regular'], ['container'], ['container'],
48 ['regular', 'container']
48 ['regular', 'container']
49 ),
49 ),
50 (
50 (
51 [], ['regular'], [], ['container', 'container2'],
51 [], ['regular'], [], ['container', 'container2'],
52 ['regular', 'container', 'container2']
52 ['regular', 'container', 'container2']
53 ),
53 ),
54 # remove not used
54 # remove not used
55 ([], ['regular'], ['other'], [], ['regular']),
55 ([], ['regular'], ['other'], [], ['regular']),
56 (
56 (
57 ['some_other'], ['regular'], ['other', 'container'],
57 ['some_other'], ['regular'], ['other', 'container'],
58 ['container', 'container2'],
58 ['container', 'container2'],
59 ['regular', 'container', 'container2']
59 ['regular', 'container', 'container2']
60 ),
60 ),
61 ])
61 ])
62 def test_enforce_groups(pre_existing, regular_should_be,
62 def test_enforce_groups(pre_existing, regular_should_be,
63 external_should_be, groups, expected, backend_hg):
63 external_should_be, groups, expected, backend_hg):
64 # TODO: anderson: adding backend_hg fixture so it sets up the database
64 # TODO: anderson: adding backend_hg fixture so it sets up the database
65 # for when running this file alone
65 # for when running this file alone
66 _delete_all_user_groups()
66 _delete_all_user_groups()
67
67
68 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
68 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
69 for gr in pre_existing:
69 for gr in pre_existing:
70 gr = fixture.create_user_group(gr)
70 gr = fixture.create_user_group(gr)
71 Session().commit()
71 Session().commit()
72
72
73 # make sure use is just in those groups
73 # make sure use is just in those groups
74 for gr in regular_should_be:
74 for gr in regular_should_be:
75 gr = fixture.create_user_group(gr)
75 gr = fixture.create_user_group(gr)
76 Session().commit()
76 Session().commit()
77 UserGroupModel().add_user_to_group(gr, user)
77 UserGroupModel().add_user_to_group(gr, user)
78 Session().commit()
78 Session().commit()
79
79
80 # now special external groups created by auth plugins
80 # now special external groups created by auth plugins
81 for gr in external_should_be:
81 for gr in external_should_be:
82 gr = fixture.create_user_group(
82 gr = fixture.create_user_group(
83 gr, user_group_data={'extern_type': 'container'})
83 gr, user_group_data={'extern_type': 'container'})
84 Session().commit()
84 Session().commit()
85 UserGroupModel().add_user_to_group(gr, user)
85 UserGroupModel().add_user_to_group(gr, user)
86 Session().commit()
86 Session().commit()
87
87
88 UserGroupModel().enforce_groups(user, groups, 'container')
88 UserGroupModel().enforce_groups(user, groups, 'container')
89 Session().commit()
89 Session().commit()
90
90
91 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
91 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
92 in_groups = user.group_member
92 in_groups = user.group_member
93
93
94 expected.sort()
94 expected.sort()
95 assert (
95 assert (
96 expected == sorted(x.users_group.users_group_name for x in in_groups))
96 expected == sorted(x.users_group.users_group_name for x in in_groups))
97
97
98
98
99 def _delete_all_user_groups():
99 def _delete_all_user_groups():
100 for gr in UserGroupModel.get_all():
100 for gr in UserGroupModel.get_all():
101 fixture.destroy_user_group(gr)
101 fixture.destroy_user_group(gr)
102 Session().commit()
102 Session().commit()
103
103
104
104
105 def test_add_and_remove_user_from_group(user_regular, user_util):
105 def test_add_and_remove_user_from_group(user_regular, user_util):
106 user_group = user_util.create_user_group()
106 user_group = user_util.create_user_group()
107 assert user_group.members == []
107 assert user_group.members == []
108 UserGroupModel().add_user_to_group(user_group, user_regular)
108 UserGroupModel().add_user_to_group(user_group, user_regular)
109 Session().commit()
109 Session().commit()
110 assert user_group.members[0].user == user_regular
110 assert user_group.members[0].user == user_regular
111 UserGroupModel().remove_user_from_group(user_group, user_regular)
111 UserGroupModel().remove_user_from_group(user_group, user_regular)
112 Session().commit()
112 Session().commit()
113 assert user_group.members == []
113 assert user_group.members == []
114
114
115
115
116 @pytest.mark.parametrize(
116 @pytest.mark.parametrize('data, expected', [
117 'data, expected', [
117 ([], []),
118 ("1", [1]), (["1", "2"], [1, 2])
118 ([{"member_user_id": 1, "type": "new"}], [1]),
119 ]
119 ([{"member_user_id": 1, "type": "new"},
120 )
120 {"member_user_id": 1, "type": "existing"}], [1]),
121 ([{"member_user_id": 1, "type": "new"},
122 {"member_user_id": 2, "type": "new"},
123 {"member_user_id": 3, "type": "remove"}], [1, 2])
124 ])
121 def test_clean_members_data(data, expected):
125 def test_clean_members_data(data, expected):
122 cleaned = UserGroupModel()._clean_members_data(data)
126 cleaned = UserGroupModel()._clean_members_data(data)
123 assert cleaned == expected
127 assert cleaned == expected
124
128
125
129
126 def test_update_members_from_user_ids(user_regular, user_util):
127 user_group = user_util.create_user_group()
128 assert user_group.members == []
129 assert user_group.user != user_regular
130 expected_active_state = not user_group.users_group_active
131
132 form_data = {
133 'users_group_members': str(user_regular.user_id),
134 'user': str(user_regular.username),
135 'users_group_name': 'changed_name',
136 'users_group_active': expected_active_state,
137 'user_group_description': 'changed_description'
138 }
139
140 UserGroupModel().update(user_group, form_data)
141 assert user_group.members[0].user_id == user_regular.user_id
142 assert user_group.user_id == user_regular.user_id
143 assert 'changed_name' in user_group.users_group_name
144 assert 'changed_description' in user_group.user_group_description
145 assert user_group.users_group_active == expected_active_state
146 # Ignore changes on the test
147 Session().rollback()
148
149
150 def _create_test_members():
130 def _create_test_members():
151 members = []
131 members = []
152 for member_number in range(3):
132 for member_number in range(3):
153 member = mock.Mock()
133 member = mock.Mock()
154 member.user_id = member_number + 1
134 member.user_id = member_number + 1
155 member.user.user_id = member_number + 1
135 member.user.user_id = member_number + 1
156 members.append(member)
136 members.append(member)
157 return members
137 return members
158
138
159
139
160 def test_get_added_and_removed_users():
140 def test_get_added_and_removed_users():
161 members = _create_test_members()
141 members = _create_test_members()
162 mock_user_group = mock.Mock()
142 mock_user_group = mock.Mock()
163 mock_user_group.members = [members[0], members[1]]
143 mock_user_group.members = [members[0], members[1]]
164 new_users_list = [members[1].user.user_id, members[2].user.user_id]
144 new_users_list = [members[1].user.user_id, members[2].user.user_id]
165 model = UserGroupModel()
145 model = UserGroupModel()
166
146
167 added, removed = model._get_added_and_removed_user_ids(
147 added, removed = model._get_added_and_removed_user_ids(
168 mock_user_group, new_users_list)
148 mock_user_group, new_users_list)
169
149
170 assert added == [members[2].user.user_id]
150 assert added == [members[2].user.user_id]
171 assert removed == [members[0].user.user_id]
151 assert removed == [members[0].user.user_id]
172
152
173
153
174 def test_set_users_as_members_and_find_user_in_group(
154 def test_set_users_as_members_and_find_user_in_group(
175 user_util, user_regular, user_admin):
155 user_util, user_regular, user_admin):
176 user_group = user_util.create_user_group()
156 user_group = user_util.create_user_group()
177 assert len(user_group.members) == 0
157 assert len(user_group.members) == 0
178 user_list = [user_regular.user_id, user_admin.user_id]
158 user_list = [user_regular.user_id, user_admin.user_id]
179 UserGroupModel()._set_users_as_members(user_group, user_list)
159 UserGroupModel()._set_users_as_members(user_group, user_list)
180 assert len(user_group.members) == 2
160 assert len(user_group.members) == 2
181 assert UserGroupModel()._find_user_in_group(user_regular, user_group)
161 assert UserGroupModel()._find_user_in_group(user_regular, user_group)
@@ -1,1791 +1,1792 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import collections
21 import collections
22 import datetime
22 import datetime
23 import hashlib
23 import hashlib
24 import os
24 import os
25 import re
25 import re
26 import pprint
26 import pprint
27 import shutil
27 import shutil
28 import socket
28 import socket
29 import subprocess32
29 import subprocess32
30 import time
30 import time
31 import uuid
31 import uuid
32
32
33 import mock
33 import mock
34 import pyramid.testing
34 import pyramid.testing
35 import pytest
35 import pytest
36 import colander
36 import colander
37 import requests
37 import requests
38 from webtest.app import TestApp
38 from webtest.app import TestApp
39
39
40 import rhodecode
40 import rhodecode
41 from rhodecode.lib.utils2 import AttributeDict
41 from rhodecode.model.changeset_status import ChangesetStatusModel
42 from rhodecode.model.changeset_status import ChangesetStatusModel
42 from rhodecode.model.comment import ChangesetCommentsModel
43 from rhodecode.model.comment import ChangesetCommentsModel
43 from rhodecode.model.db import (
44 from rhodecode.model.db import (
44 PullRequest, Repository, RhodeCodeSetting, ChangesetStatus, RepoGroup,
45 PullRequest, Repository, RhodeCodeSetting, ChangesetStatus, RepoGroup,
45 UserGroup, RepoRhodeCodeUi, RepoRhodeCodeSetting, RhodeCodeUi)
46 UserGroup, RepoRhodeCodeUi, RepoRhodeCodeSetting, RhodeCodeUi)
46 from rhodecode.model.meta import Session
47 from rhodecode.model.meta import Session
47 from rhodecode.model.pull_request import PullRequestModel
48 from rhodecode.model.pull_request import PullRequestModel
48 from rhodecode.model.repo import RepoModel
49 from rhodecode.model.repo import RepoModel
49 from rhodecode.model.repo_group import RepoGroupModel
50 from rhodecode.model.repo_group import RepoGroupModel
50 from rhodecode.model.user import UserModel
51 from rhodecode.model.user import UserModel
51 from rhodecode.model.settings import VcsSettingsModel
52 from rhodecode.model.settings import VcsSettingsModel
52 from rhodecode.model.user_group import UserGroupModel
53 from rhodecode.model.user_group import UserGroupModel
53 from rhodecode.model.integration import IntegrationModel
54 from rhodecode.model.integration import IntegrationModel
54 from rhodecode.integrations import integration_type_registry
55 from rhodecode.integrations import integration_type_registry
55 from rhodecode.integrations.types.base import IntegrationTypeBase
56 from rhodecode.integrations.types.base import IntegrationTypeBase
56 from rhodecode.lib.utils import repo2db_mapper
57 from rhodecode.lib.utils import repo2db_mapper
57 from rhodecode.lib.vcs import create_vcsserver_proxy
58 from rhodecode.lib.vcs import create_vcsserver_proxy
58 from rhodecode.lib.vcs.backends import get_backend
59 from rhodecode.lib.vcs.backends import get_backend
59 from rhodecode.lib.vcs.nodes import FileNode
60 from rhodecode.lib.vcs.nodes import FileNode
60 from rhodecode.tests import (
61 from rhodecode.tests import (
61 login_user_session, get_new_dir, utils, TESTS_TMP_PATH,
62 login_user_session, get_new_dir, utils, TESTS_TMP_PATH,
62 TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR2_LOGIN,
63 TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR2_LOGIN,
63 TEST_USER_REGULAR_PASS)
64 TEST_USER_REGULAR_PASS)
64 from rhodecode.tests.fixture import Fixture
65 from rhodecode.tests.fixture import Fixture
65
66
66
67
67 def _split_comma(value):
68 def _split_comma(value):
68 return value.split(',')
69 return value.split(',')
69
70
70
71
71 def pytest_addoption(parser):
72 def pytest_addoption(parser):
72 parser.addoption(
73 parser.addoption(
73 '--keep-tmp-path', action='store_true',
74 '--keep-tmp-path', action='store_true',
74 help="Keep the test temporary directories")
75 help="Keep the test temporary directories")
75 parser.addoption(
76 parser.addoption(
76 '--backends', action='store', type=_split_comma,
77 '--backends', action='store', type=_split_comma,
77 default=['git', 'hg', 'svn'],
78 default=['git', 'hg', 'svn'],
78 help="Select which backends to test for backend specific tests.")
79 help="Select which backends to test for backend specific tests.")
79 parser.addoption(
80 parser.addoption(
80 '--dbs', action='store', type=_split_comma,
81 '--dbs', action='store', type=_split_comma,
81 default=['sqlite'],
82 default=['sqlite'],
82 help="Select which database to test for database specific tests. "
83 help="Select which database to test for database specific tests. "
83 "Possible options are sqlite,postgres,mysql")
84 "Possible options are sqlite,postgres,mysql")
84 parser.addoption(
85 parser.addoption(
85 '--appenlight', '--ae', action='store_true',
86 '--appenlight', '--ae', action='store_true',
86 help="Track statistics in appenlight.")
87 help="Track statistics in appenlight.")
87 parser.addoption(
88 parser.addoption(
88 '--appenlight-api-key', '--ae-key',
89 '--appenlight-api-key', '--ae-key',
89 help="API key for Appenlight.")
90 help="API key for Appenlight.")
90 parser.addoption(
91 parser.addoption(
91 '--appenlight-url', '--ae-url',
92 '--appenlight-url', '--ae-url',
92 default="https://ae.rhodecode.com",
93 default="https://ae.rhodecode.com",
93 help="Appenlight service URL, defaults to https://ae.rhodecode.com")
94 help="Appenlight service URL, defaults to https://ae.rhodecode.com")
94 parser.addoption(
95 parser.addoption(
95 '--sqlite-connection-string', action='store',
96 '--sqlite-connection-string', action='store',
96 default='', help="Connection string for the dbs tests with SQLite")
97 default='', help="Connection string for the dbs tests with SQLite")
97 parser.addoption(
98 parser.addoption(
98 '--postgres-connection-string', action='store',
99 '--postgres-connection-string', action='store',
99 default='', help="Connection string for the dbs tests with Postgres")
100 default='', help="Connection string for the dbs tests with Postgres")
100 parser.addoption(
101 parser.addoption(
101 '--mysql-connection-string', action='store',
102 '--mysql-connection-string', action='store',
102 default='', help="Connection string for the dbs tests with MySQL")
103 default='', help="Connection string for the dbs tests with MySQL")
103 parser.addoption(
104 parser.addoption(
104 '--repeat', type=int, default=100,
105 '--repeat', type=int, default=100,
105 help="Number of repetitions in performance tests.")
106 help="Number of repetitions in performance tests.")
106
107
107
108
108 def pytest_configure(config):
109 def pytest_configure(config):
109 # Appy the kombu patch early on, needed for test discovery on Python 2.7.11
110 # Appy the kombu patch early on, needed for test discovery on Python 2.7.11
110 from rhodecode.config import patches
111 from rhodecode.config import patches
111 patches.kombu_1_5_1_python_2_7_11()
112 patches.kombu_1_5_1_python_2_7_11()
112
113
113
114
114 def pytest_collection_modifyitems(session, config, items):
115 def pytest_collection_modifyitems(session, config, items):
115 # nottest marked, compare nose, used for transition from nose to pytest
116 # nottest marked, compare nose, used for transition from nose to pytest
116 remaining = [
117 remaining = [
117 i for i in items if getattr(i.obj, '__test__', True)]
118 i for i in items if getattr(i.obj, '__test__', True)]
118 items[:] = remaining
119 items[:] = remaining
119
120
120
121
121 def pytest_generate_tests(metafunc):
122 def pytest_generate_tests(metafunc):
122 # Support test generation based on --backend parameter
123 # Support test generation based on --backend parameter
123 if 'backend_alias' in metafunc.fixturenames:
124 if 'backend_alias' in metafunc.fixturenames:
124 backends = get_backends_from_metafunc(metafunc)
125 backends = get_backends_from_metafunc(metafunc)
125 scope = None
126 scope = None
126 if not backends:
127 if not backends:
127 pytest.skip("Not enabled for any of selected backends")
128 pytest.skip("Not enabled for any of selected backends")
128 metafunc.parametrize('backend_alias', backends, scope=scope)
129 metafunc.parametrize('backend_alias', backends, scope=scope)
129 elif hasattr(metafunc.function, 'backends'):
130 elif hasattr(metafunc.function, 'backends'):
130 backends = get_backends_from_metafunc(metafunc)
131 backends = get_backends_from_metafunc(metafunc)
131 if not backends:
132 if not backends:
132 pytest.skip("Not enabled for any of selected backends")
133 pytest.skip("Not enabled for any of selected backends")
133
134
134
135
135 def get_backends_from_metafunc(metafunc):
136 def get_backends_from_metafunc(metafunc):
136 requested_backends = set(metafunc.config.getoption('--backends'))
137 requested_backends = set(metafunc.config.getoption('--backends'))
137 if hasattr(metafunc.function, 'backends'):
138 if hasattr(metafunc.function, 'backends'):
138 # Supported backends by this test function, created from
139 # Supported backends by this test function, created from
139 # pytest.mark.backends
140 # pytest.mark.backends
140 backends = metafunc.function.backends.args
141 backends = metafunc.function.backends.args
141 elif hasattr(metafunc.cls, 'backend_alias'):
142 elif hasattr(metafunc.cls, 'backend_alias'):
142 # Support class attribute "backend_alias", this is mainly
143 # Support class attribute "backend_alias", this is mainly
143 # for legacy reasons for tests not yet using pytest.mark.backends
144 # for legacy reasons for tests not yet using pytest.mark.backends
144 backends = [metafunc.cls.backend_alias]
145 backends = [metafunc.cls.backend_alias]
145 else:
146 else:
146 backends = metafunc.config.getoption('--backends')
147 backends = metafunc.config.getoption('--backends')
147 return requested_backends.intersection(backends)
148 return requested_backends.intersection(backends)
148
149
149
150
150 @pytest.fixture(scope='session', autouse=True)
151 @pytest.fixture(scope='session', autouse=True)
151 def activate_example_rcextensions(request):
152 def activate_example_rcextensions(request):
152 """
153 """
153 Patch in an example rcextensions module which verifies passed in kwargs.
154 Patch in an example rcextensions module which verifies passed in kwargs.
154 """
155 """
155 from rhodecode.tests.other import example_rcextensions
156 from rhodecode.tests.other import example_rcextensions
156
157
157 old_extensions = rhodecode.EXTENSIONS
158 old_extensions = rhodecode.EXTENSIONS
158 rhodecode.EXTENSIONS = example_rcextensions
159 rhodecode.EXTENSIONS = example_rcextensions
159
160
160 @request.addfinalizer
161 @request.addfinalizer
161 def cleanup():
162 def cleanup():
162 rhodecode.EXTENSIONS = old_extensions
163 rhodecode.EXTENSIONS = old_extensions
163
164
164
165
165 @pytest.fixture
166 @pytest.fixture
166 def capture_rcextensions():
167 def capture_rcextensions():
167 """
168 """
168 Returns the recorded calls to entry points in rcextensions.
169 Returns the recorded calls to entry points in rcextensions.
169 """
170 """
170 calls = rhodecode.EXTENSIONS.calls
171 calls = rhodecode.EXTENSIONS.calls
171 calls.clear()
172 calls.clear()
172 # Note: At this moment, it is still the empty dict, but that will
173 # Note: At this moment, it is still the empty dict, but that will
173 # be filled during the test run and since it is a reference this
174 # be filled during the test run and since it is a reference this
174 # is enough to make it work.
175 # is enough to make it work.
175 return calls
176 return calls
176
177
177
178
178 @pytest.fixture(scope='session')
179 @pytest.fixture(scope='session')
179 def http_environ_session():
180 def http_environ_session():
180 """
181 """
181 Allow to use "http_environ" in session scope.
182 Allow to use "http_environ" in session scope.
182 """
183 """
183 return http_environ(
184 return http_environ(
184 http_host_stub=http_host_stub())
185 http_host_stub=http_host_stub())
185
186
186
187
187 @pytest.fixture
188 @pytest.fixture
188 def http_host_stub():
189 def http_host_stub():
189 """
190 """
190 Value of HTTP_HOST in the test run.
191 Value of HTTP_HOST in the test run.
191 """
192 """
192 return 'test.example.com:80'
193 return 'test.example.com:80'
193
194
194
195
195 @pytest.fixture
196 @pytest.fixture
196 def http_environ(http_host_stub):
197 def http_environ(http_host_stub):
197 """
198 """
198 HTTP extra environ keys.
199 HTTP extra environ keys.
199
200
200 User by the test application and as well for setting up the pylons
201 User by the test application and as well for setting up the pylons
201 environment. In the case of the fixture "app" it should be possible
202 environment. In the case of the fixture "app" it should be possible
202 to override this for a specific test case.
203 to override this for a specific test case.
203 """
204 """
204 return {
205 return {
205 'SERVER_NAME': http_host_stub.split(':')[0],
206 'SERVER_NAME': http_host_stub.split(':')[0],
206 'SERVER_PORT': http_host_stub.split(':')[1],
207 'SERVER_PORT': http_host_stub.split(':')[1],
207 'HTTP_HOST': http_host_stub,
208 'HTTP_HOST': http_host_stub,
208 }
209 }
209
210
210
211
211 @pytest.fixture(scope='function')
212 @pytest.fixture(scope='function')
212 def app(request, pylonsapp, http_environ):
213 def app(request, pylonsapp, http_environ):
213 app = TestApp(
214 app = TestApp(
214 pylonsapp,
215 pylonsapp,
215 extra_environ=http_environ)
216 extra_environ=http_environ)
216 if request.cls:
217 if request.cls:
217 request.cls.app = app
218 request.cls.app = app
218 return app
219 return app
219
220
220
221
221 @pytest.fixture(scope='session')
222 @pytest.fixture(scope='session')
222 def app_settings(pylonsapp, pylons_config):
223 def app_settings(pylonsapp, pylons_config):
223 """
224 """
224 Settings dictionary used to create the app.
225 Settings dictionary used to create the app.
225
226
226 Parses the ini file and passes the result through the sanitize and apply
227 Parses the ini file and passes the result through the sanitize and apply
227 defaults mechanism in `rhodecode.config.middleware`.
228 defaults mechanism in `rhodecode.config.middleware`.
228 """
229 """
229 from paste.deploy.loadwsgi import loadcontext, APP
230 from paste.deploy.loadwsgi import loadcontext, APP
230 from rhodecode.config.middleware import (
231 from rhodecode.config.middleware import (
231 sanitize_settings_and_apply_defaults)
232 sanitize_settings_and_apply_defaults)
232 context = loadcontext(APP, 'config:' + pylons_config)
233 context = loadcontext(APP, 'config:' + pylons_config)
233 settings = sanitize_settings_and_apply_defaults(context.config())
234 settings = sanitize_settings_and_apply_defaults(context.config())
234 return settings
235 return settings
235
236
236
237
237 @pytest.fixture(scope='session')
238 @pytest.fixture(scope='session')
238 def db(app_settings):
239 def db(app_settings):
239 """
240 """
240 Initializes the database connection.
241 Initializes the database connection.
241
242
242 It uses the same settings which are used to create the ``pylonsapp`` or
243 It uses the same settings which are used to create the ``pylonsapp`` or
243 ``app`` fixtures.
244 ``app`` fixtures.
244 """
245 """
245 from rhodecode.config.utils import initialize_database
246 from rhodecode.config.utils import initialize_database
246 initialize_database(app_settings)
247 initialize_database(app_settings)
247
248
248
249
249 LoginData = collections.namedtuple('LoginData', ('csrf_token', 'user'))
250 LoginData = collections.namedtuple('LoginData', ('csrf_token', 'user'))
250
251
251
252
252 def _autologin_user(app, *args):
253 def _autologin_user(app, *args):
253 session = login_user_session(app, *args)
254 session = login_user_session(app, *args)
254 csrf_token = rhodecode.lib.auth.get_csrf_token(session)
255 csrf_token = rhodecode.lib.auth.get_csrf_token(session)
255 return LoginData(csrf_token, session['rhodecode_user'])
256 return LoginData(csrf_token, session['rhodecode_user'])
256
257
257
258
258 @pytest.fixture
259 @pytest.fixture
259 def autologin_user(app):
260 def autologin_user(app):
260 """
261 """
261 Utility fixture which makes sure that the admin user is logged in
262 Utility fixture which makes sure that the admin user is logged in
262 """
263 """
263 return _autologin_user(app)
264 return _autologin_user(app)
264
265
265
266
266 @pytest.fixture
267 @pytest.fixture
267 def autologin_regular_user(app):
268 def autologin_regular_user(app):
268 """
269 """
269 Utility fixture which makes sure that the regular user is logged in
270 Utility fixture which makes sure that the regular user is logged in
270 """
271 """
271 return _autologin_user(
272 return _autologin_user(
272 app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
273 app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
273
274
274
275
275 @pytest.fixture(scope='function')
276 @pytest.fixture(scope='function')
276 def csrf_token(request, autologin_user):
277 def csrf_token(request, autologin_user):
277 return autologin_user.csrf_token
278 return autologin_user.csrf_token
278
279
279
280
280 @pytest.fixture(scope='function')
281 @pytest.fixture(scope='function')
281 def xhr_header(request):
282 def xhr_header(request):
282 return {'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'}
283 return {'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'}
283
284
284
285
285 @pytest.fixture
286 @pytest.fixture
286 def real_crypto_backend(monkeypatch):
287 def real_crypto_backend(monkeypatch):
287 """
288 """
288 Switch the production crypto backend on for this test.
289 Switch the production crypto backend on for this test.
289
290
290 During the test run the crypto backend is replaced with a faster
291 During the test run the crypto backend is replaced with a faster
291 implementation based on the MD5 algorithm.
292 implementation based on the MD5 algorithm.
292 """
293 """
293 monkeypatch.setattr(rhodecode, 'is_test', False)
294 monkeypatch.setattr(rhodecode, 'is_test', False)
294
295
295
296
296 @pytest.fixture(scope='class')
297 @pytest.fixture(scope='class')
297 def index_location(request, pylonsapp):
298 def index_location(request, pylonsapp):
298 index_location = pylonsapp.config['app_conf']['search.location']
299 index_location = pylonsapp.config['app_conf']['search.location']
299 if request.cls:
300 if request.cls:
300 request.cls.index_location = index_location
301 request.cls.index_location = index_location
301 return index_location
302 return index_location
302
303
303
304
304 @pytest.fixture(scope='session', autouse=True)
305 @pytest.fixture(scope='session', autouse=True)
305 def tests_tmp_path(request):
306 def tests_tmp_path(request):
306 """
307 """
307 Create temporary directory to be used during the test session.
308 Create temporary directory to be used during the test session.
308 """
309 """
309 if not os.path.exists(TESTS_TMP_PATH):
310 if not os.path.exists(TESTS_TMP_PATH):
310 os.makedirs(TESTS_TMP_PATH)
311 os.makedirs(TESTS_TMP_PATH)
311
312
312 if not request.config.getoption('--keep-tmp-path'):
313 if not request.config.getoption('--keep-tmp-path'):
313 @request.addfinalizer
314 @request.addfinalizer
314 def remove_tmp_path():
315 def remove_tmp_path():
315 shutil.rmtree(TESTS_TMP_PATH)
316 shutil.rmtree(TESTS_TMP_PATH)
316
317
317 return TESTS_TMP_PATH
318 return TESTS_TMP_PATH
318
319
319
320
320 @pytest.fixture(scope='session', autouse=True)
321 @pytest.fixture(scope='session', autouse=True)
321 def patch_pyro_request_scope_proxy_factory(request):
322 def patch_pyro_request_scope_proxy_factory(request):
322 """
323 """
323 Patch the pyro proxy factory to always use the same dummy request object
324 Patch the pyro proxy factory to always use the same dummy request object
324 when under test. This will return the same pyro proxy on every call.
325 when under test. This will return the same pyro proxy on every call.
325 """
326 """
326 dummy_request = pyramid.testing.DummyRequest()
327 dummy_request = pyramid.testing.DummyRequest()
327
328
328 def mocked_call(self, request=None):
329 def mocked_call(self, request=None):
329 return self.getProxy(request=dummy_request)
330 return self.getProxy(request=dummy_request)
330
331
331 patcher = mock.patch(
332 patcher = mock.patch(
332 'rhodecode.lib.vcs.client.RequestScopeProxyFactory.__call__',
333 'rhodecode.lib.vcs.client.RequestScopeProxyFactory.__call__',
333 new=mocked_call)
334 new=mocked_call)
334 patcher.start()
335 patcher.start()
335
336
336 @request.addfinalizer
337 @request.addfinalizer
337 def undo_patching():
338 def undo_patching():
338 patcher.stop()
339 patcher.stop()
339
340
340
341
341 @pytest.fixture
342 @pytest.fixture
342 def test_repo_group(request):
343 def test_repo_group(request):
343 """
344 """
344 Create a temporary repository group, and destroy it after
345 Create a temporary repository group, and destroy it after
345 usage automatically
346 usage automatically
346 """
347 """
347 fixture = Fixture()
348 fixture = Fixture()
348 repogroupid = 'test_repo_group_%s' % int(time.time())
349 repogroupid = 'test_repo_group_%s' % int(time.time())
349 repo_group = fixture.create_repo_group(repogroupid)
350 repo_group = fixture.create_repo_group(repogroupid)
350
351
351 def _cleanup():
352 def _cleanup():
352 fixture.destroy_repo_group(repogroupid)
353 fixture.destroy_repo_group(repogroupid)
353
354
354 request.addfinalizer(_cleanup)
355 request.addfinalizer(_cleanup)
355 return repo_group
356 return repo_group
356
357
357
358
358 @pytest.fixture
359 @pytest.fixture
359 def test_user_group(request):
360 def test_user_group(request):
360 """
361 """
361 Create a temporary user group, and destroy it after
362 Create a temporary user group, and destroy it after
362 usage automatically
363 usage automatically
363 """
364 """
364 fixture = Fixture()
365 fixture = Fixture()
365 usergroupid = 'test_user_group_%s' % int(time.time())
366 usergroupid = 'test_user_group_%s' % int(time.time())
366 user_group = fixture.create_user_group(usergroupid)
367 user_group = fixture.create_user_group(usergroupid)
367
368
368 def _cleanup():
369 def _cleanup():
369 fixture.destroy_user_group(user_group)
370 fixture.destroy_user_group(user_group)
370
371
371 request.addfinalizer(_cleanup)
372 request.addfinalizer(_cleanup)
372 return user_group
373 return user_group
373
374
374
375
375 @pytest.fixture(scope='session')
376 @pytest.fixture(scope='session')
376 def test_repo(request):
377 def test_repo(request):
377 container = TestRepoContainer()
378 container = TestRepoContainer()
378 request.addfinalizer(container._cleanup)
379 request.addfinalizer(container._cleanup)
379 return container
380 return container
380
381
381
382
382 class TestRepoContainer(object):
383 class TestRepoContainer(object):
383 """
384 """
384 Container for test repositories which are used read only.
385 Container for test repositories which are used read only.
385
386
386 Repositories will be created on demand and re-used during the lifetime
387 Repositories will be created on demand and re-used during the lifetime
387 of this object.
388 of this object.
388
389
389 Usage to get the svn test repository "minimal"::
390 Usage to get the svn test repository "minimal"::
390
391
391 test_repo = TestContainer()
392 test_repo = TestContainer()
392 repo = test_repo('minimal', 'svn')
393 repo = test_repo('minimal', 'svn')
393
394
394 """
395 """
395
396
396 dump_extractors = {
397 dump_extractors = {
397 'git': utils.extract_git_repo_from_dump,
398 'git': utils.extract_git_repo_from_dump,
398 'hg': utils.extract_hg_repo_from_dump,
399 'hg': utils.extract_hg_repo_from_dump,
399 'svn': utils.extract_svn_repo_from_dump,
400 'svn': utils.extract_svn_repo_from_dump,
400 }
401 }
401
402
402 def __init__(self):
403 def __init__(self):
403 self._cleanup_repos = []
404 self._cleanup_repos = []
404 self._fixture = Fixture()
405 self._fixture = Fixture()
405 self._repos = {}
406 self._repos = {}
406
407
407 def __call__(self, dump_name, backend_alias):
408 def __call__(self, dump_name, backend_alias):
408 key = (dump_name, backend_alias)
409 key = (dump_name, backend_alias)
409 if key not in self._repos:
410 if key not in self._repos:
410 repo = self._create_repo(dump_name, backend_alias)
411 repo = self._create_repo(dump_name, backend_alias)
411 self._repos[key] = repo.repo_id
412 self._repos[key] = repo.repo_id
412 return Repository.get(self._repos[key])
413 return Repository.get(self._repos[key])
413
414
414 def _create_repo(self, dump_name, backend_alias):
415 def _create_repo(self, dump_name, backend_alias):
415 repo_name = '%s-%s' % (backend_alias, dump_name)
416 repo_name = '%s-%s' % (backend_alias, dump_name)
416 backend_class = get_backend(backend_alias)
417 backend_class = get_backend(backend_alias)
417 dump_extractor = self.dump_extractors[backend_alias]
418 dump_extractor = self.dump_extractors[backend_alias]
418 repo_path = dump_extractor(dump_name, repo_name)
419 repo_path = dump_extractor(dump_name, repo_name)
419 vcs_repo = backend_class(repo_path)
420 vcs_repo = backend_class(repo_path)
420 repo2db_mapper({repo_name: vcs_repo})
421 repo2db_mapper({repo_name: vcs_repo})
421 repo = RepoModel().get_by_repo_name(repo_name)
422 repo = RepoModel().get_by_repo_name(repo_name)
422 self._cleanup_repos.append(repo_name)
423 self._cleanup_repos.append(repo_name)
423 return repo
424 return repo
424
425
425 def _cleanup(self):
426 def _cleanup(self):
426 for repo_name in reversed(self._cleanup_repos):
427 for repo_name in reversed(self._cleanup_repos):
427 self._fixture.destroy_repo(repo_name)
428 self._fixture.destroy_repo(repo_name)
428
429
429
430
430 @pytest.fixture
431 @pytest.fixture
431 def backend(request, backend_alias, pylonsapp, test_repo):
432 def backend(request, backend_alias, pylonsapp, test_repo):
432 """
433 """
433 Parametrized fixture which represents a single backend implementation.
434 Parametrized fixture which represents a single backend implementation.
434
435
435 It respects the option `--backends` to focus the test run on specific
436 It respects the option `--backends` to focus the test run on specific
436 backend implementations.
437 backend implementations.
437
438
438 It also supports `pytest.mark.xfail_backends` to mark tests as failing
439 It also supports `pytest.mark.xfail_backends` to mark tests as failing
439 for specific backends. This is intended as a utility for incremental
440 for specific backends. This is intended as a utility for incremental
440 development of a new backend implementation.
441 development of a new backend implementation.
441 """
442 """
442 if backend_alias not in request.config.getoption('--backends'):
443 if backend_alias not in request.config.getoption('--backends'):
443 pytest.skip("Backend %s not selected." % (backend_alias, ))
444 pytest.skip("Backend %s not selected." % (backend_alias, ))
444
445
445 utils.check_xfail_backends(request.node, backend_alias)
446 utils.check_xfail_backends(request.node, backend_alias)
446 utils.check_skip_backends(request.node, backend_alias)
447 utils.check_skip_backends(request.node, backend_alias)
447
448
448 repo_name = 'vcs_test_%s' % (backend_alias, )
449 repo_name = 'vcs_test_%s' % (backend_alias, )
449 backend = Backend(
450 backend = Backend(
450 alias=backend_alias,
451 alias=backend_alias,
451 repo_name=repo_name,
452 repo_name=repo_name,
452 test_name=request.node.name,
453 test_name=request.node.name,
453 test_repo_container=test_repo)
454 test_repo_container=test_repo)
454 request.addfinalizer(backend.cleanup)
455 request.addfinalizer(backend.cleanup)
455 return backend
456 return backend
456
457
457
458
458 @pytest.fixture
459 @pytest.fixture
459 def backend_git(request, pylonsapp, test_repo):
460 def backend_git(request, pylonsapp, test_repo):
460 return backend(request, 'git', pylonsapp, test_repo)
461 return backend(request, 'git', pylonsapp, test_repo)
461
462
462
463
463 @pytest.fixture
464 @pytest.fixture
464 def backend_hg(request, pylonsapp, test_repo):
465 def backend_hg(request, pylonsapp, test_repo):
465 return backend(request, 'hg', pylonsapp, test_repo)
466 return backend(request, 'hg', pylonsapp, test_repo)
466
467
467
468
468 @pytest.fixture
469 @pytest.fixture
469 def backend_svn(request, pylonsapp, test_repo):
470 def backend_svn(request, pylonsapp, test_repo):
470 return backend(request, 'svn', pylonsapp, test_repo)
471 return backend(request, 'svn', pylonsapp, test_repo)
471
472
472
473
473 @pytest.fixture
474 @pytest.fixture
474 def backend_random(backend_git):
475 def backend_random(backend_git):
475 """
476 """
476 Use this to express that your tests need "a backend.
477 Use this to express that your tests need "a backend.
477
478
478 A few of our tests need a backend, so that we can run the code. This
479 A few of our tests need a backend, so that we can run the code. This
479 fixture is intended to be used for such cases. It will pick one of the
480 fixture is intended to be used for such cases. It will pick one of the
480 backends and run the tests.
481 backends and run the tests.
481
482
482 The fixture `backend` would run the test multiple times for each
483 The fixture `backend` would run the test multiple times for each
483 available backend which is a pure waste of time if the test is
484 available backend which is a pure waste of time if the test is
484 independent of the backend type.
485 independent of the backend type.
485 """
486 """
486 # TODO: johbo: Change this to pick a random backend
487 # TODO: johbo: Change this to pick a random backend
487 return backend_git
488 return backend_git
488
489
489
490
490 @pytest.fixture
491 @pytest.fixture
491 def backend_stub(backend_git):
492 def backend_stub(backend_git):
492 """
493 """
493 Use this to express that your tests need a backend stub
494 Use this to express that your tests need a backend stub
494
495
495 TODO: mikhail: Implement a real stub logic instead of returning
496 TODO: mikhail: Implement a real stub logic instead of returning
496 a git backend
497 a git backend
497 """
498 """
498 return backend_git
499 return backend_git
499
500
500
501
501 @pytest.fixture
502 @pytest.fixture
502 def repo_stub(backend_stub):
503 def repo_stub(backend_stub):
503 """
504 """
504 Use this to express that your tests need a repository stub
505 Use this to express that your tests need a repository stub
505 """
506 """
506 return backend_stub.create_repo()
507 return backend_stub.create_repo()
507
508
508
509
509 class Backend(object):
510 class Backend(object):
510 """
511 """
511 Represents the test configuration for one supported backend
512 Represents the test configuration for one supported backend
512
513
513 Provides easy access to different test repositories based on
514 Provides easy access to different test repositories based on
514 `__getitem__`. Such repositories will only be created once per test
515 `__getitem__`. Such repositories will only be created once per test
515 session.
516 session.
516 """
517 """
517
518
518 invalid_repo_name = re.compile(r'[^0-9a-zA-Z]+')
519 invalid_repo_name = re.compile(r'[^0-9a-zA-Z]+')
519 _master_repo = None
520 _master_repo = None
520 _commit_ids = {}
521 _commit_ids = {}
521
522
522 def __init__(self, alias, repo_name, test_name, test_repo_container):
523 def __init__(self, alias, repo_name, test_name, test_repo_container):
523 self.alias = alias
524 self.alias = alias
524 self.repo_name = repo_name
525 self.repo_name = repo_name
525 self._cleanup_repos = []
526 self._cleanup_repos = []
526 self._test_name = test_name
527 self._test_name = test_name
527 self._test_repo_container = test_repo_container
528 self._test_repo_container = test_repo_container
528 # TODO: johbo: Used as a delegate interim. Not yet sure if Backend or
529 # TODO: johbo: Used as a delegate interim. Not yet sure if Backend or
529 # Fixture will survive in the end.
530 # Fixture will survive in the end.
530 self._fixture = Fixture()
531 self._fixture = Fixture()
531
532
532 def __getitem__(self, key):
533 def __getitem__(self, key):
533 return self._test_repo_container(key, self.alias)
534 return self._test_repo_container(key, self.alias)
534
535
535 @property
536 @property
536 def repo(self):
537 def repo(self):
537 """
538 """
538 Returns the "current" repository. This is the vcs_test repo or the
539 Returns the "current" repository. This is the vcs_test repo or the
539 last repo which has been created with `create_repo`.
540 last repo which has been created with `create_repo`.
540 """
541 """
541 from rhodecode.model.db import Repository
542 from rhodecode.model.db import Repository
542 return Repository.get_by_repo_name(self.repo_name)
543 return Repository.get_by_repo_name(self.repo_name)
543
544
544 @property
545 @property
545 def default_branch_name(self):
546 def default_branch_name(self):
546 VcsRepository = get_backend(self.alias)
547 VcsRepository = get_backend(self.alias)
547 return VcsRepository.DEFAULT_BRANCH_NAME
548 return VcsRepository.DEFAULT_BRANCH_NAME
548
549
549 @property
550 @property
550 def default_head_id(self):
551 def default_head_id(self):
551 """
552 """
552 Returns the default head id of the underlying backend.
553 Returns the default head id of the underlying backend.
553
554
554 This will be the default branch name in case the backend does have a
555 This will be the default branch name in case the backend does have a
555 default branch. In the other cases it will point to a valid head
556 default branch. In the other cases it will point to a valid head
556 which can serve as the base to create a new commit on top of it.
557 which can serve as the base to create a new commit on top of it.
557 """
558 """
558 vcsrepo = self.repo.scm_instance()
559 vcsrepo = self.repo.scm_instance()
559 head_id = (
560 head_id = (
560 vcsrepo.DEFAULT_BRANCH_NAME or
561 vcsrepo.DEFAULT_BRANCH_NAME or
561 vcsrepo.commit_ids[-1])
562 vcsrepo.commit_ids[-1])
562 return head_id
563 return head_id
563
564
564 @property
565 @property
565 def commit_ids(self):
566 def commit_ids(self):
566 """
567 """
567 Returns the list of commits for the last created repository
568 Returns the list of commits for the last created repository
568 """
569 """
569 return self._commit_ids
570 return self._commit_ids
570
571
571 def create_master_repo(self, commits):
572 def create_master_repo(self, commits):
572 """
573 """
573 Create a repository and remember it as a template.
574 Create a repository and remember it as a template.
574
575
575 This allows to easily create derived repositories to construct
576 This allows to easily create derived repositories to construct
576 more complex scenarios for diff, compare and pull requests.
577 more complex scenarios for diff, compare and pull requests.
577
578
578 Returns a commit map which maps from commit message to raw_id.
579 Returns a commit map which maps from commit message to raw_id.
579 """
580 """
580 self._master_repo = self.create_repo(commits=commits)
581 self._master_repo = self.create_repo(commits=commits)
581 return self._commit_ids
582 return self._commit_ids
582
583
583 def create_repo(
584 def create_repo(
584 self, commits=None, number_of_commits=0, heads=None,
585 self, commits=None, number_of_commits=0, heads=None,
585 name_suffix=u'', **kwargs):
586 name_suffix=u'', **kwargs):
586 """
587 """
587 Create a repository and record it for later cleanup.
588 Create a repository and record it for later cleanup.
588
589
589 :param commits: Optional. A sequence of dict instances.
590 :param commits: Optional. A sequence of dict instances.
590 Will add a commit per entry to the new repository.
591 Will add a commit per entry to the new repository.
591 :param number_of_commits: Optional. If set to a number, this number of
592 :param number_of_commits: Optional. If set to a number, this number of
592 commits will be added to the new repository.
593 commits will be added to the new repository.
593 :param heads: Optional. Can be set to a sequence of of commit
594 :param heads: Optional. Can be set to a sequence of of commit
594 names which shall be pulled in from the master repository.
595 names which shall be pulled in from the master repository.
595
596
596 """
597 """
597 self.repo_name = self._next_repo_name() + name_suffix
598 self.repo_name = self._next_repo_name() + name_suffix
598 repo = self._fixture.create_repo(
599 repo = self._fixture.create_repo(
599 self.repo_name, repo_type=self.alias, **kwargs)
600 self.repo_name, repo_type=self.alias, **kwargs)
600 self._cleanup_repos.append(repo.repo_name)
601 self._cleanup_repos.append(repo.repo_name)
601
602
602 commits = commits or [
603 commits = commits or [
603 {'message': 'Commit %s of %s' % (x, self.repo_name)}
604 {'message': 'Commit %s of %s' % (x, self.repo_name)}
604 for x in xrange(number_of_commits)]
605 for x in xrange(number_of_commits)]
605 self._add_commits_to_repo(repo.scm_instance(), commits)
606 self._add_commits_to_repo(repo.scm_instance(), commits)
606 if heads:
607 if heads:
607 self.pull_heads(repo, heads)
608 self.pull_heads(repo, heads)
608
609
609 return repo
610 return repo
610
611
611 def pull_heads(self, repo, heads):
612 def pull_heads(self, repo, heads):
612 """
613 """
613 Make sure that repo contains all commits mentioned in `heads`
614 Make sure that repo contains all commits mentioned in `heads`
614 """
615 """
615 vcsmaster = self._master_repo.scm_instance()
616 vcsmaster = self._master_repo.scm_instance()
616 vcsrepo = repo.scm_instance()
617 vcsrepo = repo.scm_instance()
617 vcsrepo.config.clear_section('hooks')
618 vcsrepo.config.clear_section('hooks')
618 commit_ids = [self._commit_ids[h] for h in heads]
619 commit_ids = [self._commit_ids[h] for h in heads]
619 vcsrepo.pull(vcsmaster.path, commit_ids=commit_ids)
620 vcsrepo.pull(vcsmaster.path, commit_ids=commit_ids)
620
621
621 def create_fork(self):
622 def create_fork(self):
622 repo_to_fork = self.repo_name
623 repo_to_fork = self.repo_name
623 self.repo_name = self._next_repo_name()
624 self.repo_name = self._next_repo_name()
624 repo = self._fixture.create_fork(repo_to_fork, self.repo_name)
625 repo = self._fixture.create_fork(repo_to_fork, self.repo_name)
625 self._cleanup_repos.append(self.repo_name)
626 self._cleanup_repos.append(self.repo_name)
626 return repo
627 return repo
627
628
628 def new_repo_name(self, suffix=u''):
629 def new_repo_name(self, suffix=u''):
629 self.repo_name = self._next_repo_name() + suffix
630 self.repo_name = self._next_repo_name() + suffix
630 self._cleanup_repos.append(self.repo_name)
631 self._cleanup_repos.append(self.repo_name)
631 return self.repo_name
632 return self.repo_name
632
633
633 def _next_repo_name(self):
634 def _next_repo_name(self):
634 return u"%s_%s" % (
635 return u"%s_%s" % (
635 self.invalid_repo_name.sub(u'_', self._test_name),
636 self.invalid_repo_name.sub(u'_', self._test_name),
636 len(self._cleanup_repos))
637 len(self._cleanup_repos))
637
638
638 def ensure_file(self, filename, content='Test content\n'):
639 def ensure_file(self, filename, content='Test content\n'):
639 assert self._cleanup_repos, "Avoid writing into vcs_test repos"
640 assert self._cleanup_repos, "Avoid writing into vcs_test repos"
640 commits = [
641 commits = [
641 {'added': [
642 {'added': [
642 FileNode(filename, content=content),
643 FileNode(filename, content=content),
643 ]},
644 ]},
644 ]
645 ]
645 self._add_commits_to_repo(self.repo.scm_instance(), commits)
646 self._add_commits_to_repo(self.repo.scm_instance(), commits)
646
647
647 def enable_downloads(self):
648 def enable_downloads(self):
648 repo = self.repo
649 repo = self.repo
649 repo.enable_downloads = True
650 repo.enable_downloads = True
650 Session().add(repo)
651 Session().add(repo)
651 Session().commit()
652 Session().commit()
652
653
653 def cleanup(self):
654 def cleanup(self):
654 for repo_name in reversed(self._cleanup_repos):
655 for repo_name in reversed(self._cleanup_repos):
655 self._fixture.destroy_repo(repo_name)
656 self._fixture.destroy_repo(repo_name)
656
657
657 def _add_commits_to_repo(self, repo, commits):
658 def _add_commits_to_repo(self, repo, commits):
658 commit_ids = _add_commits_to_repo(repo, commits)
659 commit_ids = _add_commits_to_repo(repo, commits)
659 if not commit_ids:
660 if not commit_ids:
660 return
661 return
661 self._commit_ids = commit_ids
662 self._commit_ids = commit_ids
662
663
663 # Creating refs for Git to allow fetching them from remote repository
664 # Creating refs for Git to allow fetching them from remote repository
664 if self.alias == 'git':
665 if self.alias == 'git':
665 refs = {}
666 refs = {}
666 for message in self._commit_ids:
667 for message in self._commit_ids:
667 # TODO: mikhail: do more special chars replacements
668 # TODO: mikhail: do more special chars replacements
668 ref_name = 'refs/test-refs/{}'.format(
669 ref_name = 'refs/test-refs/{}'.format(
669 message.replace(' ', ''))
670 message.replace(' ', ''))
670 refs[ref_name] = self._commit_ids[message]
671 refs[ref_name] = self._commit_ids[message]
671 self._create_refs(repo, refs)
672 self._create_refs(repo, refs)
672
673
673 def _create_refs(self, repo, refs):
674 def _create_refs(self, repo, refs):
674 for ref_name in refs:
675 for ref_name in refs:
675 repo.set_refs(ref_name, refs[ref_name])
676 repo.set_refs(ref_name, refs[ref_name])
676
677
677
678
678 @pytest.fixture
679 @pytest.fixture
679 def vcsbackend(request, backend_alias, tests_tmp_path, pylonsapp, test_repo):
680 def vcsbackend(request, backend_alias, tests_tmp_path, pylonsapp, test_repo):
680 """
681 """
681 Parametrized fixture which represents a single vcs backend implementation.
682 Parametrized fixture which represents a single vcs backend implementation.
682
683
683 See the fixture `backend` for more details. This one implements the same
684 See the fixture `backend` for more details. This one implements the same
684 concept, but on vcs level. So it does not provide model instances etc.
685 concept, but on vcs level. So it does not provide model instances etc.
685
686
686 Parameters are generated dynamically, see :func:`pytest_generate_tests`
687 Parameters are generated dynamically, see :func:`pytest_generate_tests`
687 for how this works.
688 for how this works.
688 """
689 """
689 if backend_alias not in request.config.getoption('--backends'):
690 if backend_alias not in request.config.getoption('--backends'):
690 pytest.skip("Backend %s not selected." % (backend_alias, ))
691 pytest.skip("Backend %s not selected." % (backend_alias, ))
691
692
692 utils.check_xfail_backends(request.node, backend_alias)
693 utils.check_xfail_backends(request.node, backend_alias)
693 utils.check_skip_backends(request.node, backend_alias)
694 utils.check_skip_backends(request.node, backend_alias)
694
695
695 repo_name = 'vcs_test_%s' % (backend_alias, )
696 repo_name = 'vcs_test_%s' % (backend_alias, )
696 repo_path = os.path.join(tests_tmp_path, repo_name)
697 repo_path = os.path.join(tests_tmp_path, repo_name)
697 backend = VcsBackend(
698 backend = VcsBackend(
698 alias=backend_alias,
699 alias=backend_alias,
699 repo_path=repo_path,
700 repo_path=repo_path,
700 test_name=request.node.name,
701 test_name=request.node.name,
701 test_repo_container=test_repo)
702 test_repo_container=test_repo)
702 request.addfinalizer(backend.cleanup)
703 request.addfinalizer(backend.cleanup)
703 return backend
704 return backend
704
705
705
706
706 @pytest.fixture
707 @pytest.fixture
707 def vcsbackend_git(request, tests_tmp_path, pylonsapp, test_repo):
708 def vcsbackend_git(request, tests_tmp_path, pylonsapp, test_repo):
708 return vcsbackend(request, 'git', tests_tmp_path, pylonsapp, test_repo)
709 return vcsbackend(request, 'git', tests_tmp_path, pylonsapp, test_repo)
709
710
710
711
711 @pytest.fixture
712 @pytest.fixture
712 def vcsbackend_hg(request, tests_tmp_path, pylonsapp, test_repo):
713 def vcsbackend_hg(request, tests_tmp_path, pylonsapp, test_repo):
713 return vcsbackend(request, 'hg', tests_tmp_path, pylonsapp, test_repo)
714 return vcsbackend(request, 'hg', tests_tmp_path, pylonsapp, test_repo)
714
715
715
716
716 @pytest.fixture
717 @pytest.fixture
717 def vcsbackend_svn(request, tests_tmp_path, pylonsapp, test_repo):
718 def vcsbackend_svn(request, tests_tmp_path, pylonsapp, test_repo):
718 return vcsbackend(request, 'svn', tests_tmp_path, pylonsapp, test_repo)
719 return vcsbackend(request, 'svn', tests_tmp_path, pylonsapp, test_repo)
719
720
720
721
721 @pytest.fixture
722 @pytest.fixture
722 def vcsbackend_random(vcsbackend_git):
723 def vcsbackend_random(vcsbackend_git):
723 """
724 """
724 Use this to express that your tests need "a vcsbackend".
725 Use this to express that your tests need "a vcsbackend".
725
726
726 The fixture `vcsbackend` would run the test multiple times for each
727 The fixture `vcsbackend` would run the test multiple times for each
727 available vcs backend which is a pure waste of time if the test is
728 available vcs backend which is a pure waste of time if the test is
728 independent of the vcs backend type.
729 independent of the vcs backend type.
729 """
730 """
730 # TODO: johbo: Change this to pick a random backend
731 # TODO: johbo: Change this to pick a random backend
731 return vcsbackend_git
732 return vcsbackend_git
732
733
733
734
734 @pytest.fixture
735 @pytest.fixture
735 def vcsbackend_stub(vcsbackend_git):
736 def vcsbackend_stub(vcsbackend_git):
736 """
737 """
737 Use this to express that your test just needs a stub of a vcsbackend.
738 Use this to express that your test just needs a stub of a vcsbackend.
738
739
739 Plan is to eventually implement an in-memory stub to speed tests up.
740 Plan is to eventually implement an in-memory stub to speed tests up.
740 """
741 """
741 return vcsbackend_git
742 return vcsbackend_git
742
743
743
744
744 class VcsBackend(object):
745 class VcsBackend(object):
745 """
746 """
746 Represents the test configuration for one supported vcs backend.
747 Represents the test configuration for one supported vcs backend.
747 """
748 """
748
749
749 invalid_repo_name = re.compile(r'[^0-9a-zA-Z]+')
750 invalid_repo_name = re.compile(r'[^0-9a-zA-Z]+')
750
751
751 def __init__(self, alias, repo_path, test_name, test_repo_container):
752 def __init__(self, alias, repo_path, test_name, test_repo_container):
752 self.alias = alias
753 self.alias = alias
753 self._repo_path = repo_path
754 self._repo_path = repo_path
754 self._cleanup_repos = []
755 self._cleanup_repos = []
755 self._test_name = test_name
756 self._test_name = test_name
756 self._test_repo_container = test_repo_container
757 self._test_repo_container = test_repo_container
757
758
758 def __getitem__(self, key):
759 def __getitem__(self, key):
759 return self._test_repo_container(key, self.alias).scm_instance()
760 return self._test_repo_container(key, self.alias).scm_instance()
760
761
761 @property
762 @property
762 def repo(self):
763 def repo(self):
763 """
764 """
764 Returns the "current" repository. This is the vcs_test repo of the last
765 Returns the "current" repository. This is the vcs_test repo of the last
765 repo which has been created.
766 repo which has been created.
766 """
767 """
767 Repository = get_backend(self.alias)
768 Repository = get_backend(self.alias)
768 return Repository(self._repo_path)
769 return Repository(self._repo_path)
769
770
770 @property
771 @property
771 def backend(self):
772 def backend(self):
772 """
773 """
773 Returns the backend implementation class.
774 Returns the backend implementation class.
774 """
775 """
775 return get_backend(self.alias)
776 return get_backend(self.alias)
776
777
777 def create_repo(self, commits=None, number_of_commits=0, _clone_repo=None):
778 def create_repo(self, commits=None, number_of_commits=0, _clone_repo=None):
778 repo_name = self._next_repo_name()
779 repo_name = self._next_repo_name()
779 self._repo_path = get_new_dir(repo_name)
780 self._repo_path = get_new_dir(repo_name)
780 repo_class = get_backend(self.alias)
781 repo_class = get_backend(self.alias)
781 src_url = None
782 src_url = None
782 if _clone_repo:
783 if _clone_repo:
783 src_url = _clone_repo.path
784 src_url = _clone_repo.path
784 repo = repo_class(self._repo_path, create=True, src_url=src_url)
785 repo = repo_class(self._repo_path, create=True, src_url=src_url)
785 self._cleanup_repos.append(repo)
786 self._cleanup_repos.append(repo)
786
787
787 commits = commits or [
788 commits = commits or [
788 {'message': 'Commit %s of %s' % (x, repo_name)}
789 {'message': 'Commit %s of %s' % (x, repo_name)}
789 for x in xrange(number_of_commits)]
790 for x in xrange(number_of_commits)]
790 _add_commits_to_repo(repo, commits)
791 _add_commits_to_repo(repo, commits)
791 return repo
792 return repo
792
793
793 def clone_repo(self, repo):
794 def clone_repo(self, repo):
794 return self.create_repo(_clone_repo=repo)
795 return self.create_repo(_clone_repo=repo)
795
796
796 def cleanup(self):
797 def cleanup(self):
797 for repo in self._cleanup_repos:
798 for repo in self._cleanup_repos:
798 shutil.rmtree(repo.path)
799 shutil.rmtree(repo.path)
799
800
800 def new_repo_path(self):
801 def new_repo_path(self):
801 repo_name = self._next_repo_name()
802 repo_name = self._next_repo_name()
802 self._repo_path = get_new_dir(repo_name)
803 self._repo_path = get_new_dir(repo_name)
803 return self._repo_path
804 return self._repo_path
804
805
805 def _next_repo_name(self):
806 def _next_repo_name(self):
806 return "%s_%s" % (
807 return "%s_%s" % (
807 self.invalid_repo_name.sub('_', self._test_name),
808 self.invalid_repo_name.sub('_', self._test_name),
808 len(self._cleanup_repos))
809 len(self._cleanup_repos))
809
810
810 def add_file(self, repo, filename, content='Test content\n'):
811 def add_file(self, repo, filename, content='Test content\n'):
811 imc = repo.in_memory_commit
812 imc = repo.in_memory_commit
812 imc.add(FileNode(filename, content=content))
813 imc.add(FileNode(filename, content=content))
813 imc.commit(
814 imc.commit(
814 message=u'Automatic commit from vcsbackend fixture',
815 message=u'Automatic commit from vcsbackend fixture',
815 author=u'Automatic')
816 author=u'Automatic')
816
817
817 def ensure_file(self, filename, content='Test content\n'):
818 def ensure_file(self, filename, content='Test content\n'):
818 assert self._cleanup_repos, "Avoid writing into vcs_test repos"
819 assert self._cleanup_repos, "Avoid writing into vcs_test repos"
819 self.add_file(self.repo, filename, content)
820 self.add_file(self.repo, filename, content)
820
821
821
822
822 def _add_commits_to_repo(vcs_repo, commits):
823 def _add_commits_to_repo(vcs_repo, commits):
823 commit_ids = {}
824 commit_ids = {}
824 if not commits:
825 if not commits:
825 return commit_ids
826 return commit_ids
826
827
827 imc = vcs_repo.in_memory_commit
828 imc = vcs_repo.in_memory_commit
828 commit = None
829 commit = None
829
830
830 for idx, commit in enumerate(commits):
831 for idx, commit in enumerate(commits):
831 message = unicode(commit.get('message', 'Commit %s' % idx))
832 message = unicode(commit.get('message', 'Commit %s' % idx))
832
833
833 for node in commit.get('added', []):
834 for node in commit.get('added', []):
834 imc.add(FileNode(node.path, content=node.content))
835 imc.add(FileNode(node.path, content=node.content))
835 for node in commit.get('changed', []):
836 for node in commit.get('changed', []):
836 imc.change(FileNode(node.path, content=node.content))
837 imc.change(FileNode(node.path, content=node.content))
837 for node in commit.get('removed', []):
838 for node in commit.get('removed', []):
838 imc.remove(FileNode(node.path))
839 imc.remove(FileNode(node.path))
839
840
840 parents = [
841 parents = [
841 vcs_repo.get_commit(commit_id=commit_ids[p])
842 vcs_repo.get_commit(commit_id=commit_ids[p])
842 for p in commit.get('parents', [])]
843 for p in commit.get('parents', [])]
843
844
844 operations = ('added', 'changed', 'removed')
845 operations = ('added', 'changed', 'removed')
845 if not any((commit.get(o) for o in operations)):
846 if not any((commit.get(o) for o in operations)):
846 imc.add(FileNode('file_%s' % idx, content=message))
847 imc.add(FileNode('file_%s' % idx, content=message))
847
848
848 commit = imc.commit(
849 commit = imc.commit(
849 message=message,
850 message=message,
850 author=unicode(commit.get('author', 'Automatic')),
851 author=unicode(commit.get('author', 'Automatic')),
851 date=commit.get('date'),
852 date=commit.get('date'),
852 branch=commit.get('branch'),
853 branch=commit.get('branch'),
853 parents=parents)
854 parents=parents)
854
855
855 commit_ids[commit.message] = commit.raw_id
856 commit_ids[commit.message] = commit.raw_id
856
857
857 return commit_ids
858 return commit_ids
858
859
859
860
860 @pytest.fixture
861 @pytest.fixture
861 def reposerver(request):
862 def reposerver(request):
862 """
863 """
863 Allows to serve a backend repository
864 Allows to serve a backend repository
864 """
865 """
865
866
866 repo_server = RepoServer()
867 repo_server = RepoServer()
867 request.addfinalizer(repo_server.cleanup)
868 request.addfinalizer(repo_server.cleanup)
868 return repo_server
869 return repo_server
869
870
870
871
871 class RepoServer(object):
872 class RepoServer(object):
872 """
873 """
873 Utility to serve a local repository for the duration of a test case.
874 Utility to serve a local repository for the duration of a test case.
874
875
875 Supports only Subversion so far.
876 Supports only Subversion so far.
876 """
877 """
877
878
878 url = None
879 url = None
879
880
880 def __init__(self):
881 def __init__(self):
881 self._cleanup_servers = []
882 self._cleanup_servers = []
882
883
883 def serve(self, vcsrepo):
884 def serve(self, vcsrepo):
884 if vcsrepo.alias != 'svn':
885 if vcsrepo.alias != 'svn':
885 raise TypeError("Backend %s not supported" % vcsrepo.alias)
886 raise TypeError("Backend %s not supported" % vcsrepo.alias)
886
887
887 proc = subprocess32.Popen(
888 proc = subprocess32.Popen(
888 ['svnserve', '-d', '--foreground', '--listen-host', 'localhost',
889 ['svnserve', '-d', '--foreground', '--listen-host', 'localhost',
889 '--root', vcsrepo.path])
890 '--root', vcsrepo.path])
890 self._cleanup_servers.append(proc)
891 self._cleanup_servers.append(proc)
891 self.url = 'svn://localhost'
892 self.url = 'svn://localhost'
892
893
893 def cleanup(self):
894 def cleanup(self):
894 for proc in self._cleanup_servers:
895 for proc in self._cleanup_servers:
895 proc.terminate()
896 proc.terminate()
896
897
897
898
898 @pytest.fixture
899 @pytest.fixture
899 def pr_util(backend, request):
900 def pr_util(backend, request):
900 """
901 """
901 Utility for tests of models and for functional tests around pull requests.
902 Utility for tests of models and for functional tests around pull requests.
902
903
903 It gives an instance of :class:`PRTestUtility` which provides various
904 It gives an instance of :class:`PRTestUtility` which provides various
904 utility methods around one pull request.
905 utility methods around one pull request.
905
906
906 This fixture uses `backend` and inherits its parameterization.
907 This fixture uses `backend` and inherits its parameterization.
907 """
908 """
908
909
909 util = PRTestUtility(backend)
910 util = PRTestUtility(backend)
910
911
911 @request.addfinalizer
912 @request.addfinalizer
912 def cleanup():
913 def cleanup():
913 util.cleanup()
914 util.cleanup()
914
915
915 return util
916 return util
916
917
917
918
918 class PRTestUtility(object):
919 class PRTestUtility(object):
919
920
920 pull_request = None
921 pull_request = None
921 pull_request_id = None
922 pull_request_id = None
922 mergeable_patcher = None
923 mergeable_patcher = None
923 mergeable_mock = None
924 mergeable_mock = None
924 notification_patcher = None
925 notification_patcher = None
925
926
926 def __init__(self, backend):
927 def __init__(self, backend):
927 self.backend = backend
928 self.backend = backend
928
929
929 def create_pull_request(
930 def create_pull_request(
930 self, commits=None, target_head=None, source_head=None,
931 self, commits=None, target_head=None, source_head=None,
931 revisions=None, approved=False, author=None, mergeable=False,
932 revisions=None, approved=False, author=None, mergeable=False,
932 enable_notifications=True, name_suffix=u'', reviewers=None,
933 enable_notifications=True, name_suffix=u'', reviewers=None,
933 title=u"Test", description=u"Description"):
934 title=u"Test", description=u"Description"):
934 self.set_mergeable(mergeable)
935 self.set_mergeable(mergeable)
935 if not enable_notifications:
936 if not enable_notifications:
936 # mock notification side effect
937 # mock notification side effect
937 self.notification_patcher = mock.patch(
938 self.notification_patcher = mock.patch(
938 'rhodecode.model.notification.NotificationModel.create')
939 'rhodecode.model.notification.NotificationModel.create')
939 self.notification_patcher.start()
940 self.notification_patcher.start()
940
941
941 if not self.pull_request:
942 if not self.pull_request:
942 if not commits:
943 if not commits:
943 commits = [
944 commits = [
944 {'message': 'c1'},
945 {'message': 'c1'},
945 {'message': 'c2'},
946 {'message': 'c2'},
946 {'message': 'c3'},
947 {'message': 'c3'},
947 ]
948 ]
948 target_head = 'c1'
949 target_head = 'c1'
949 source_head = 'c2'
950 source_head = 'c2'
950 revisions = ['c2']
951 revisions = ['c2']
951
952
952 self.commit_ids = self.backend.create_master_repo(commits)
953 self.commit_ids = self.backend.create_master_repo(commits)
953 self.target_repository = self.backend.create_repo(
954 self.target_repository = self.backend.create_repo(
954 heads=[target_head], name_suffix=name_suffix)
955 heads=[target_head], name_suffix=name_suffix)
955 self.source_repository = self.backend.create_repo(
956 self.source_repository = self.backend.create_repo(
956 heads=[source_head], name_suffix=name_suffix)
957 heads=[source_head], name_suffix=name_suffix)
957 self.author = author or UserModel().get_by_username(
958 self.author = author or UserModel().get_by_username(
958 TEST_USER_ADMIN_LOGIN)
959 TEST_USER_ADMIN_LOGIN)
959
960
960 model = PullRequestModel()
961 model = PullRequestModel()
961 self.create_parameters = {
962 self.create_parameters = {
962 'created_by': self.author,
963 'created_by': self.author,
963 'source_repo': self.source_repository.repo_name,
964 'source_repo': self.source_repository.repo_name,
964 'source_ref': self._default_branch_reference(source_head),
965 'source_ref': self._default_branch_reference(source_head),
965 'target_repo': self.target_repository.repo_name,
966 'target_repo': self.target_repository.repo_name,
966 'target_ref': self._default_branch_reference(target_head),
967 'target_ref': self._default_branch_reference(target_head),
967 'revisions': [self.commit_ids[r] for r in revisions],
968 'revisions': [self.commit_ids[r] for r in revisions],
968 'reviewers': reviewers or self._get_reviewers(),
969 'reviewers': reviewers or self._get_reviewers(),
969 'title': title,
970 'title': title,
970 'description': description,
971 'description': description,
971 }
972 }
972 self.pull_request = model.create(**self.create_parameters)
973 self.pull_request = model.create(**self.create_parameters)
973 assert model.get_versions(self.pull_request) == []
974 assert model.get_versions(self.pull_request) == []
974
975
975 self.pull_request_id = self.pull_request.pull_request_id
976 self.pull_request_id = self.pull_request.pull_request_id
976
977
977 if approved:
978 if approved:
978 self.approve()
979 self.approve()
979
980
980 Session().add(self.pull_request)
981 Session().add(self.pull_request)
981 Session().commit()
982 Session().commit()
982
983
983 return self.pull_request
984 return self.pull_request
984
985
985 def approve(self):
986 def approve(self):
986 self.create_status_votes(
987 self.create_status_votes(
987 ChangesetStatus.STATUS_APPROVED,
988 ChangesetStatus.STATUS_APPROVED,
988 *self.pull_request.reviewers)
989 *self.pull_request.reviewers)
989
990
990 def close(self):
991 def close(self):
991 PullRequestModel().close_pull_request(self.pull_request, self.author)
992 PullRequestModel().close_pull_request(self.pull_request, self.author)
992
993
993 def _default_branch_reference(self, commit_message):
994 def _default_branch_reference(self, commit_message):
994 reference = '%s:%s:%s' % (
995 reference = '%s:%s:%s' % (
995 'branch',
996 'branch',
996 self.backend.default_branch_name,
997 self.backend.default_branch_name,
997 self.commit_ids[commit_message])
998 self.commit_ids[commit_message])
998 return reference
999 return reference
999
1000
1000 def _get_reviewers(self):
1001 def _get_reviewers(self):
1001 model = UserModel()
1002 model = UserModel()
1002 return [
1003 return [
1003 model.get_by_username(TEST_USER_REGULAR_LOGIN),
1004 model.get_by_username(TEST_USER_REGULAR_LOGIN),
1004 model.get_by_username(TEST_USER_REGULAR2_LOGIN),
1005 model.get_by_username(TEST_USER_REGULAR2_LOGIN),
1005 ]
1006 ]
1006
1007
1007 def update_source_repository(self, head=None):
1008 def update_source_repository(self, head=None):
1008 heads = [head or 'c3']
1009 heads = [head or 'c3']
1009 self.backend.pull_heads(self.source_repository, heads=heads)
1010 self.backend.pull_heads(self.source_repository, heads=heads)
1010
1011
1011 def add_one_commit(self, head=None):
1012 def add_one_commit(self, head=None):
1012 self.update_source_repository(head=head)
1013 self.update_source_repository(head=head)
1013 old_commit_ids = set(self.pull_request.revisions)
1014 old_commit_ids = set(self.pull_request.revisions)
1014 PullRequestModel().update_commits(self.pull_request)
1015 PullRequestModel().update_commits(self.pull_request)
1015 commit_ids = set(self.pull_request.revisions)
1016 commit_ids = set(self.pull_request.revisions)
1016 new_commit_ids = commit_ids - old_commit_ids
1017 new_commit_ids = commit_ids - old_commit_ids
1017 assert len(new_commit_ids) == 1
1018 assert len(new_commit_ids) == 1
1018 return new_commit_ids.pop()
1019 return new_commit_ids.pop()
1019
1020
1020 def remove_one_commit(self):
1021 def remove_one_commit(self):
1021 assert len(self.pull_request.revisions) == 2
1022 assert len(self.pull_request.revisions) == 2
1022 source_vcs = self.source_repository.scm_instance()
1023 source_vcs = self.source_repository.scm_instance()
1023 removed_commit_id = source_vcs.commit_ids[-1]
1024 removed_commit_id = source_vcs.commit_ids[-1]
1024
1025
1025 # TODO: johbo: Git and Mercurial have an inconsistent vcs api here,
1026 # TODO: johbo: Git and Mercurial have an inconsistent vcs api here,
1026 # remove the if once that's sorted out.
1027 # remove the if once that's sorted out.
1027 if self.backend.alias == "git":
1028 if self.backend.alias == "git":
1028 kwargs = {'branch_name': self.backend.default_branch_name}
1029 kwargs = {'branch_name': self.backend.default_branch_name}
1029 else:
1030 else:
1030 kwargs = {}
1031 kwargs = {}
1031 source_vcs.strip(removed_commit_id, **kwargs)
1032 source_vcs.strip(removed_commit_id, **kwargs)
1032
1033
1033 PullRequestModel().update_commits(self.pull_request)
1034 PullRequestModel().update_commits(self.pull_request)
1034 assert len(self.pull_request.revisions) == 1
1035 assert len(self.pull_request.revisions) == 1
1035 return removed_commit_id
1036 return removed_commit_id
1036
1037
1037 def create_comment(self, linked_to=None):
1038 def create_comment(self, linked_to=None):
1038 comment = ChangesetCommentsModel().create(
1039 comment = ChangesetCommentsModel().create(
1039 text=u"Test comment",
1040 text=u"Test comment",
1040 repo=self.target_repository.repo_name,
1041 repo=self.target_repository.repo_name,
1041 user=self.author,
1042 user=self.author,
1042 pull_request=self.pull_request)
1043 pull_request=self.pull_request)
1043 assert comment.pull_request_version_id is None
1044 assert comment.pull_request_version_id is None
1044
1045
1045 if linked_to:
1046 if linked_to:
1046 PullRequestModel()._link_comments_to_version(linked_to)
1047 PullRequestModel()._link_comments_to_version(linked_to)
1047
1048
1048 return comment
1049 return comment
1049
1050
1050 def create_inline_comment(
1051 def create_inline_comment(
1051 self, linked_to=None, line_no=u'n1', file_path='file_1'):
1052 self, linked_to=None, line_no=u'n1', file_path='file_1'):
1052 comment = ChangesetCommentsModel().create(
1053 comment = ChangesetCommentsModel().create(
1053 text=u"Test comment",
1054 text=u"Test comment",
1054 repo=self.target_repository.repo_name,
1055 repo=self.target_repository.repo_name,
1055 user=self.author,
1056 user=self.author,
1056 line_no=line_no,
1057 line_no=line_no,
1057 f_path=file_path,
1058 f_path=file_path,
1058 pull_request=self.pull_request)
1059 pull_request=self.pull_request)
1059 assert comment.pull_request_version_id is None
1060 assert comment.pull_request_version_id is None
1060
1061
1061 if linked_to:
1062 if linked_to:
1062 PullRequestModel()._link_comments_to_version(linked_to)
1063 PullRequestModel()._link_comments_to_version(linked_to)
1063
1064
1064 return comment
1065 return comment
1065
1066
1066 def create_version_of_pull_request(self):
1067 def create_version_of_pull_request(self):
1067 pull_request = self.create_pull_request()
1068 pull_request = self.create_pull_request()
1068 version = PullRequestModel()._create_version_from_snapshot(
1069 version = PullRequestModel()._create_version_from_snapshot(
1069 pull_request)
1070 pull_request)
1070 return version
1071 return version
1071
1072
1072 def create_status_votes(self, status, *reviewers):
1073 def create_status_votes(self, status, *reviewers):
1073 for reviewer in reviewers:
1074 for reviewer in reviewers:
1074 ChangesetStatusModel().set_status(
1075 ChangesetStatusModel().set_status(
1075 repo=self.pull_request.target_repo,
1076 repo=self.pull_request.target_repo,
1076 status=status,
1077 status=status,
1077 user=reviewer.user_id,
1078 user=reviewer.user_id,
1078 pull_request=self.pull_request)
1079 pull_request=self.pull_request)
1079
1080
1080 def set_mergeable(self, value):
1081 def set_mergeable(self, value):
1081 if not self.mergeable_patcher:
1082 if not self.mergeable_patcher:
1082 self.mergeable_patcher = mock.patch.object(
1083 self.mergeable_patcher = mock.patch.object(
1083 VcsSettingsModel, 'get_general_settings')
1084 VcsSettingsModel, 'get_general_settings')
1084 self.mergeable_mock = self.mergeable_patcher.start()
1085 self.mergeable_mock = self.mergeable_patcher.start()
1085 self.mergeable_mock.return_value = {
1086 self.mergeable_mock.return_value = {
1086 'rhodecode_pr_merge_enabled': value}
1087 'rhodecode_pr_merge_enabled': value}
1087
1088
1088 def cleanup(self):
1089 def cleanup(self):
1089 # In case the source repository is already cleaned up, the pull
1090 # In case the source repository is already cleaned up, the pull
1090 # request will already be deleted.
1091 # request will already be deleted.
1091 pull_request = PullRequest().get(self.pull_request_id)
1092 pull_request = PullRequest().get(self.pull_request_id)
1092 if pull_request:
1093 if pull_request:
1093 PullRequestModel().delete(pull_request)
1094 PullRequestModel().delete(pull_request)
1094 Session().commit()
1095 Session().commit()
1095
1096
1096 if self.notification_patcher:
1097 if self.notification_patcher:
1097 self.notification_patcher.stop()
1098 self.notification_patcher.stop()
1098
1099
1099 if self.mergeable_patcher:
1100 if self.mergeable_patcher:
1100 self.mergeable_patcher.stop()
1101 self.mergeable_patcher.stop()
1101
1102
1102
1103
1103 @pytest.fixture
1104 @pytest.fixture
1104 def user_admin(pylonsapp):
1105 def user_admin(pylonsapp):
1105 """
1106 """
1106 Provides the default admin test user as an instance of `db.User`.
1107 Provides the default admin test user as an instance of `db.User`.
1107 """
1108 """
1108 user = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
1109 user = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
1109 return user
1110 return user
1110
1111
1111
1112
1112 @pytest.fixture
1113 @pytest.fixture
1113 def user_regular(pylonsapp):
1114 def user_regular(pylonsapp):
1114 """
1115 """
1115 Provides the default regular test user as an instance of `db.User`.
1116 Provides the default regular test user as an instance of `db.User`.
1116 """
1117 """
1117 user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN)
1118 user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN)
1118 return user
1119 return user
1119
1120
1120
1121
1121 @pytest.fixture
1122 @pytest.fixture
1122 def user_util(request, pylonsapp):
1123 def user_util(request, pylonsapp):
1123 """
1124 """
1124 Provides a wired instance of `UserUtility` with integrated cleanup.
1125 Provides a wired instance of `UserUtility` with integrated cleanup.
1125 """
1126 """
1126 utility = UserUtility(test_name=request.node.name)
1127 utility = UserUtility(test_name=request.node.name)
1127 request.addfinalizer(utility.cleanup)
1128 request.addfinalizer(utility.cleanup)
1128 return utility
1129 return utility
1129
1130
1130
1131
1131 # TODO: johbo: Split this up into utilities per domain or something similar
1132 # TODO: johbo: Split this up into utilities per domain or something similar
1132 class UserUtility(object):
1133 class UserUtility(object):
1133
1134
1134 def __init__(self, test_name="test"):
1135 def __init__(self, test_name="test"):
1135 self._test_name = test_name
1136 self._test_name = test_name
1136 self.fixture = Fixture()
1137 self.fixture = Fixture()
1137 self.repo_group_ids = []
1138 self.repo_group_ids = []
1138 self.user_ids = []
1139 self.user_ids = []
1139 self.user_group_ids = []
1140 self.user_group_ids = []
1140 self.user_repo_permission_ids = []
1141 self.user_repo_permission_ids = []
1141 self.user_group_repo_permission_ids = []
1142 self.user_group_repo_permission_ids = []
1142 self.user_repo_group_permission_ids = []
1143 self.user_repo_group_permission_ids = []
1143 self.user_group_repo_group_permission_ids = []
1144 self.user_group_repo_group_permission_ids = []
1144 self.user_user_group_permission_ids = []
1145 self.user_user_group_permission_ids = []
1145 self.user_group_user_group_permission_ids = []
1146 self.user_group_user_group_permission_ids = []
1146 self.user_permissions = []
1147 self.user_permissions = []
1147
1148
1148 def create_repo_group(
1149 def create_repo_group(
1149 self, owner=TEST_USER_ADMIN_LOGIN, auto_cleanup=True):
1150 self, owner=TEST_USER_ADMIN_LOGIN, auto_cleanup=True):
1150 group_name = "{prefix}_repogroup_{count}".format(
1151 group_name = "{prefix}_repogroup_{count}".format(
1151 prefix=self._test_name,
1152 prefix=self._test_name,
1152 count=len(self.repo_group_ids))
1153 count=len(self.repo_group_ids))
1153 repo_group = self.fixture.create_repo_group(
1154 repo_group = self.fixture.create_repo_group(
1154 group_name, cur_user=owner)
1155 group_name, cur_user=owner)
1155 if auto_cleanup:
1156 if auto_cleanup:
1156 self.repo_group_ids.append(repo_group.group_id)
1157 self.repo_group_ids.append(repo_group.group_id)
1157 return repo_group
1158 return repo_group
1158
1159
1159 def create_user(self, auto_cleanup=True, **kwargs):
1160 def create_user(self, auto_cleanup=True, **kwargs):
1160 user_name = "{prefix}_user_{count}".format(
1161 user_name = "{prefix}_user_{count}".format(
1161 prefix=self._test_name,
1162 prefix=self._test_name,
1162 count=len(self.user_ids))
1163 count=len(self.user_ids))
1163 user = self.fixture.create_user(user_name, **kwargs)
1164 user = self.fixture.create_user(user_name, **kwargs)
1164 if auto_cleanup:
1165 if auto_cleanup:
1165 self.user_ids.append(user.user_id)
1166 self.user_ids.append(user.user_id)
1166 return user
1167 return user
1167
1168
1168 def create_user_with_group(self):
1169 def create_user_with_group(self):
1169 user = self.create_user()
1170 user = self.create_user()
1170 user_group = self.create_user_group(members=[user])
1171 user_group = self.create_user_group(members=[user])
1171 return user, user_group
1172 return user, user_group
1172
1173
1173 def create_user_group(self, members=None, auto_cleanup=True, **kwargs):
1174 def create_user_group(self, members=None, auto_cleanup=True, **kwargs):
1174 group_name = "{prefix}_usergroup_{count}".format(
1175 group_name = "{prefix}_usergroup_{count}".format(
1175 prefix=self._test_name,
1176 prefix=self._test_name,
1176 count=len(self.user_group_ids))
1177 count=len(self.user_group_ids))
1177 user_group = self.fixture.create_user_group(group_name, **kwargs)
1178 user_group = self.fixture.create_user_group(group_name, **kwargs)
1178 if auto_cleanup:
1179 if auto_cleanup:
1179 self.user_group_ids.append(user_group.users_group_id)
1180 self.user_group_ids.append(user_group.users_group_id)
1180 if members:
1181 if members:
1181 for user in members:
1182 for user in members:
1182 UserGroupModel().add_user_to_group(user_group, user)
1183 UserGroupModel().add_user_to_group(user_group, user)
1183 return user_group
1184 return user_group
1184
1185
1185 def grant_user_permission(self, user_name, permission_name):
1186 def grant_user_permission(self, user_name, permission_name):
1186 self._inherit_default_user_permissions(user_name, False)
1187 self._inherit_default_user_permissions(user_name, False)
1187 self.user_permissions.append((user_name, permission_name))
1188 self.user_permissions.append((user_name, permission_name))
1188
1189
1189 def grant_user_permission_to_repo_group(
1190 def grant_user_permission_to_repo_group(
1190 self, repo_group, user, permission_name):
1191 self, repo_group, user, permission_name):
1191 permission = RepoGroupModel().grant_user_permission(
1192 permission = RepoGroupModel().grant_user_permission(
1192 repo_group, user, permission_name)
1193 repo_group, user, permission_name)
1193 self.user_repo_group_permission_ids.append(
1194 self.user_repo_group_permission_ids.append(
1194 (repo_group.group_id, user.user_id))
1195 (repo_group.group_id, user.user_id))
1195 return permission
1196 return permission
1196
1197
1197 def grant_user_group_permission_to_repo_group(
1198 def grant_user_group_permission_to_repo_group(
1198 self, repo_group, user_group, permission_name):
1199 self, repo_group, user_group, permission_name):
1199 permission = RepoGroupModel().grant_user_group_permission(
1200 permission = RepoGroupModel().grant_user_group_permission(
1200 repo_group, user_group, permission_name)
1201 repo_group, user_group, permission_name)
1201 self.user_group_repo_group_permission_ids.append(
1202 self.user_group_repo_group_permission_ids.append(
1202 (repo_group.group_id, user_group.users_group_id))
1203 (repo_group.group_id, user_group.users_group_id))
1203 return permission
1204 return permission
1204
1205
1205 def grant_user_permission_to_repo(
1206 def grant_user_permission_to_repo(
1206 self, repo, user, permission_name):
1207 self, repo, user, permission_name):
1207 permission = RepoModel().grant_user_permission(
1208 permission = RepoModel().grant_user_permission(
1208 repo, user, permission_name)
1209 repo, user, permission_name)
1209 self.user_repo_permission_ids.append(
1210 self.user_repo_permission_ids.append(
1210 (repo.repo_id, user.user_id))
1211 (repo.repo_id, user.user_id))
1211 return permission
1212 return permission
1212
1213
1213 def grant_user_group_permission_to_repo(
1214 def grant_user_group_permission_to_repo(
1214 self, repo, user_group, permission_name):
1215 self, repo, user_group, permission_name):
1215 permission = RepoModel().grant_user_group_permission(
1216 permission = RepoModel().grant_user_group_permission(
1216 repo, user_group, permission_name)
1217 repo, user_group, permission_name)
1217 self.user_group_repo_permission_ids.append(
1218 self.user_group_repo_permission_ids.append(
1218 (repo.repo_id, user_group.users_group_id))
1219 (repo.repo_id, user_group.users_group_id))
1219 return permission
1220 return permission
1220
1221
1221 def grant_user_permission_to_user_group(
1222 def grant_user_permission_to_user_group(
1222 self, target_user_group, user, permission_name):
1223 self, target_user_group, user, permission_name):
1223 permission = UserGroupModel().grant_user_permission(
1224 permission = UserGroupModel().grant_user_permission(
1224 target_user_group, user, permission_name)
1225 target_user_group, user, permission_name)
1225 self.user_user_group_permission_ids.append(
1226 self.user_user_group_permission_ids.append(
1226 (target_user_group.users_group_id, user.user_id))
1227 (target_user_group.users_group_id, user.user_id))
1227 return permission
1228 return permission
1228
1229
1229 def grant_user_group_permission_to_user_group(
1230 def grant_user_group_permission_to_user_group(
1230 self, target_user_group, user_group, permission_name):
1231 self, target_user_group, user_group, permission_name):
1231 permission = UserGroupModel().grant_user_group_permission(
1232 permission = UserGroupModel().grant_user_group_permission(
1232 target_user_group, user_group, permission_name)
1233 target_user_group, user_group, permission_name)
1233 self.user_group_user_group_permission_ids.append(
1234 self.user_group_user_group_permission_ids.append(
1234 (target_user_group.users_group_id, user_group.users_group_id))
1235 (target_user_group.users_group_id, user_group.users_group_id))
1235 return permission
1236 return permission
1236
1237
1237 def revoke_user_permission(self, user_name, permission_name):
1238 def revoke_user_permission(self, user_name, permission_name):
1238 self._inherit_default_user_permissions(user_name, True)
1239 self._inherit_default_user_permissions(user_name, True)
1239 UserModel().revoke_perm(user_name, permission_name)
1240 UserModel().revoke_perm(user_name, permission_name)
1240
1241
1241 def _inherit_default_user_permissions(self, user_name, value):
1242 def _inherit_default_user_permissions(self, user_name, value):
1242 user = UserModel().get_by_username(user_name)
1243 user = UserModel().get_by_username(user_name)
1243 user.inherit_default_permissions = value
1244 user.inherit_default_permissions = value
1244 Session().add(user)
1245 Session().add(user)
1245 Session().commit()
1246 Session().commit()
1246
1247
1247 def cleanup(self):
1248 def cleanup(self):
1248 self._cleanup_permissions()
1249 self._cleanup_permissions()
1249 self._cleanup_repo_groups()
1250 self._cleanup_repo_groups()
1250 self._cleanup_user_groups()
1251 self._cleanup_user_groups()
1251 self._cleanup_users()
1252 self._cleanup_users()
1252
1253
1253 def _cleanup_permissions(self):
1254 def _cleanup_permissions(self):
1254 if self.user_permissions:
1255 if self.user_permissions:
1255 for user_name, permission_name in self.user_permissions:
1256 for user_name, permission_name in self.user_permissions:
1256 self.revoke_user_permission(user_name, permission_name)
1257 self.revoke_user_permission(user_name, permission_name)
1257
1258
1258 for permission in self.user_repo_permission_ids:
1259 for permission in self.user_repo_permission_ids:
1259 RepoModel().revoke_user_permission(*permission)
1260 RepoModel().revoke_user_permission(*permission)
1260
1261
1261 for permission in self.user_group_repo_permission_ids:
1262 for permission in self.user_group_repo_permission_ids:
1262 RepoModel().revoke_user_group_permission(*permission)
1263 RepoModel().revoke_user_group_permission(*permission)
1263
1264
1264 for permission in self.user_repo_group_permission_ids:
1265 for permission in self.user_repo_group_permission_ids:
1265 RepoGroupModel().revoke_user_permission(*permission)
1266 RepoGroupModel().revoke_user_permission(*permission)
1266
1267
1267 for permission in self.user_group_repo_group_permission_ids:
1268 for permission in self.user_group_repo_group_permission_ids:
1268 RepoGroupModel().revoke_user_group_permission(*permission)
1269 RepoGroupModel().revoke_user_group_permission(*permission)
1269
1270
1270 for permission in self.user_user_group_permission_ids:
1271 for permission in self.user_user_group_permission_ids:
1271 UserGroupModel().revoke_user_permission(*permission)
1272 UserGroupModel().revoke_user_permission(*permission)
1272
1273
1273 for permission in self.user_group_user_group_permission_ids:
1274 for permission in self.user_group_user_group_permission_ids:
1274 UserGroupModel().revoke_user_group_permission(*permission)
1275 UserGroupModel().revoke_user_group_permission(*permission)
1275
1276
1276 def _cleanup_repo_groups(self):
1277 def _cleanup_repo_groups(self):
1277 def _repo_group_compare(first_group_id, second_group_id):
1278 def _repo_group_compare(first_group_id, second_group_id):
1278 """
1279 """
1279 Gives higher priority to the groups with the most complex paths
1280 Gives higher priority to the groups with the most complex paths
1280 """
1281 """
1281 first_group = RepoGroup.get(first_group_id)
1282 first_group = RepoGroup.get(first_group_id)
1282 second_group = RepoGroup.get(second_group_id)
1283 second_group = RepoGroup.get(second_group_id)
1283 first_group_parts = (
1284 first_group_parts = (
1284 len(first_group.group_name.split('/')) if first_group else 0)
1285 len(first_group.group_name.split('/')) if first_group else 0)
1285 second_group_parts = (
1286 second_group_parts = (
1286 len(second_group.group_name.split('/')) if second_group else 0)
1287 len(second_group.group_name.split('/')) if second_group else 0)
1287 return cmp(second_group_parts, first_group_parts)
1288 return cmp(second_group_parts, first_group_parts)
1288
1289
1289 sorted_repo_group_ids = sorted(
1290 sorted_repo_group_ids = sorted(
1290 self.repo_group_ids, cmp=_repo_group_compare)
1291 self.repo_group_ids, cmp=_repo_group_compare)
1291 for repo_group_id in sorted_repo_group_ids:
1292 for repo_group_id in sorted_repo_group_ids:
1292 self.fixture.destroy_repo_group(repo_group_id)
1293 self.fixture.destroy_repo_group(repo_group_id)
1293
1294
1294 def _cleanup_user_groups(self):
1295 def _cleanup_user_groups(self):
1295 def _user_group_compare(first_group_id, second_group_id):
1296 def _user_group_compare(first_group_id, second_group_id):
1296 """
1297 """
1297 Gives higher priority to the groups with the most complex paths
1298 Gives higher priority to the groups with the most complex paths
1298 """
1299 """
1299 first_group = UserGroup.get(first_group_id)
1300 first_group = UserGroup.get(first_group_id)
1300 second_group = UserGroup.get(second_group_id)
1301 second_group = UserGroup.get(second_group_id)
1301 first_group_parts = (
1302 first_group_parts = (
1302 len(first_group.users_group_name.split('/'))
1303 len(first_group.users_group_name.split('/'))
1303 if first_group else 0)
1304 if first_group else 0)
1304 second_group_parts = (
1305 second_group_parts = (
1305 len(second_group.users_group_name.split('/'))
1306 len(second_group.users_group_name.split('/'))
1306 if second_group else 0)
1307 if second_group else 0)
1307 return cmp(second_group_parts, first_group_parts)
1308 return cmp(second_group_parts, first_group_parts)
1308
1309
1309 sorted_user_group_ids = sorted(
1310 sorted_user_group_ids = sorted(
1310 self.user_group_ids, cmp=_user_group_compare)
1311 self.user_group_ids, cmp=_user_group_compare)
1311 for user_group_id in sorted_user_group_ids:
1312 for user_group_id in sorted_user_group_ids:
1312 self.fixture.destroy_user_group(user_group_id)
1313 self.fixture.destroy_user_group(user_group_id)
1313
1314
1314 def _cleanup_users(self):
1315 def _cleanup_users(self):
1315 for user_id in self.user_ids:
1316 for user_id in self.user_ids:
1316 self.fixture.destroy_user(user_id)
1317 self.fixture.destroy_user(user_id)
1317
1318
1318
1319
1319 # TODO: Think about moving this into a pytest-pyro package and make it a
1320 # TODO: Think about moving this into a pytest-pyro package and make it a
1320 # pytest plugin
1321 # pytest plugin
1321 @pytest.hookimpl(tryfirst=True, hookwrapper=True)
1322 @pytest.hookimpl(tryfirst=True, hookwrapper=True)
1322 def pytest_runtest_makereport(item, call):
1323 def pytest_runtest_makereport(item, call):
1323 """
1324 """
1324 Adding the remote traceback if the exception has this information.
1325 Adding the remote traceback if the exception has this information.
1325
1326
1326 Pyro4 attaches this information as the attribute `_pyroTraceback`
1327 Pyro4 attaches this information as the attribute `_pyroTraceback`
1327 to the exception instance.
1328 to the exception instance.
1328 """
1329 """
1329 outcome = yield
1330 outcome = yield
1330 report = outcome.get_result()
1331 report = outcome.get_result()
1331 if call.excinfo:
1332 if call.excinfo:
1332 _add_pyro_remote_traceback(report, call.excinfo.value)
1333 _add_pyro_remote_traceback(report, call.excinfo.value)
1333
1334
1334
1335
1335 def _add_pyro_remote_traceback(report, exc):
1336 def _add_pyro_remote_traceback(report, exc):
1336 pyro_traceback = getattr(exc, '_pyroTraceback', None)
1337 pyro_traceback = getattr(exc, '_pyroTraceback', None)
1337
1338
1338 if pyro_traceback:
1339 if pyro_traceback:
1339 traceback = ''.join(pyro_traceback)
1340 traceback = ''.join(pyro_traceback)
1340 section = 'Pyro4 remote traceback ' + report.when
1341 section = 'Pyro4 remote traceback ' + report.when
1341 report.sections.append((section, traceback))
1342 report.sections.append((section, traceback))
1342
1343
1343
1344
1344 @pytest.fixture(scope='session')
1345 @pytest.fixture(scope='session')
1345 def testrun():
1346 def testrun():
1346 return {
1347 return {
1347 'uuid': uuid.uuid4(),
1348 'uuid': uuid.uuid4(),
1348 'start': datetime.datetime.utcnow().isoformat(),
1349 'start': datetime.datetime.utcnow().isoformat(),
1349 'timestamp': int(time.time()),
1350 'timestamp': int(time.time()),
1350 }
1351 }
1351
1352
1352
1353
1353 @pytest.fixture(autouse=True)
1354 @pytest.fixture(autouse=True)
1354 def collect_appenlight_stats(request, testrun):
1355 def collect_appenlight_stats(request, testrun):
1355 """
1356 """
1356 This fixture reports memory consumtion of single tests.
1357 This fixture reports memory consumtion of single tests.
1357
1358
1358 It gathers data based on `psutil` and sends them to Appenlight. The option
1359 It gathers data based on `psutil` and sends them to Appenlight. The option
1359 ``--ae`` has te be used to enable this fixture and the API key for your
1360 ``--ae`` has te be used to enable this fixture and the API key for your
1360 application has to be provided in ``--ae-key``.
1361 application has to be provided in ``--ae-key``.
1361 """
1362 """
1362 try:
1363 try:
1363 # cygwin cannot have yet psutil support.
1364 # cygwin cannot have yet psutil support.
1364 import psutil
1365 import psutil
1365 except ImportError:
1366 except ImportError:
1366 return
1367 return
1367
1368
1368 if not request.config.getoption('--appenlight'):
1369 if not request.config.getoption('--appenlight'):
1369 return
1370 return
1370 else:
1371 else:
1371 # Only request the pylonsapp fixture if appenlight tracking is
1372 # Only request the pylonsapp fixture if appenlight tracking is
1372 # enabled. This will speed up a test run of unit tests by 2 to 3
1373 # enabled. This will speed up a test run of unit tests by 2 to 3
1373 # seconds if appenlight is not enabled.
1374 # seconds if appenlight is not enabled.
1374 pylonsapp = request.getfuncargvalue("pylonsapp")
1375 pylonsapp = request.getfuncargvalue("pylonsapp")
1375 url = '{}/api/logs'.format(request.config.getoption('--appenlight-url'))
1376 url = '{}/api/logs'.format(request.config.getoption('--appenlight-url'))
1376 client = AppenlightClient(
1377 client = AppenlightClient(
1377 url=url,
1378 url=url,
1378 api_key=request.config.getoption('--appenlight-api-key'),
1379 api_key=request.config.getoption('--appenlight-api-key'),
1379 namespace=request.node.nodeid,
1380 namespace=request.node.nodeid,
1380 request=str(testrun['uuid']),
1381 request=str(testrun['uuid']),
1381 testrun=testrun)
1382 testrun=testrun)
1382
1383
1383 client.collect({
1384 client.collect({
1384 'message': "Starting",
1385 'message': "Starting",
1385 })
1386 })
1386
1387
1387 server_and_port = pylonsapp.config['vcs.server']
1388 server_and_port = pylonsapp.config['vcs.server']
1388 server = create_vcsserver_proxy(server_and_port)
1389 server = create_vcsserver_proxy(server_and_port)
1389 with server:
1390 with server:
1390 vcs_pid = server.get_pid()
1391 vcs_pid = server.get_pid()
1391 server.run_gc()
1392 server.run_gc()
1392 vcs_process = psutil.Process(vcs_pid)
1393 vcs_process = psutil.Process(vcs_pid)
1393 mem = vcs_process.memory_info()
1394 mem = vcs_process.memory_info()
1394 client.tag_before('vcsserver.rss', mem.rss)
1395 client.tag_before('vcsserver.rss', mem.rss)
1395 client.tag_before('vcsserver.vms', mem.vms)
1396 client.tag_before('vcsserver.vms', mem.vms)
1396
1397
1397 test_process = psutil.Process()
1398 test_process = psutil.Process()
1398 mem = test_process.memory_info()
1399 mem = test_process.memory_info()
1399 client.tag_before('test.rss', mem.rss)
1400 client.tag_before('test.rss', mem.rss)
1400 client.tag_before('test.vms', mem.vms)
1401 client.tag_before('test.vms', mem.vms)
1401
1402
1402 client.tag_before('time', time.time())
1403 client.tag_before('time', time.time())
1403
1404
1404 @request.addfinalizer
1405 @request.addfinalizer
1405 def send_stats():
1406 def send_stats():
1406 client.tag_after('time', time.time())
1407 client.tag_after('time', time.time())
1407 with server:
1408 with server:
1408 gc_stats = server.run_gc()
1409 gc_stats = server.run_gc()
1409 for tag, value in gc_stats.items():
1410 for tag, value in gc_stats.items():
1410 client.tag_after(tag, value)
1411 client.tag_after(tag, value)
1411 mem = vcs_process.memory_info()
1412 mem = vcs_process.memory_info()
1412 client.tag_after('vcsserver.rss', mem.rss)
1413 client.tag_after('vcsserver.rss', mem.rss)
1413 client.tag_after('vcsserver.vms', mem.vms)
1414 client.tag_after('vcsserver.vms', mem.vms)
1414
1415
1415 mem = test_process.memory_info()
1416 mem = test_process.memory_info()
1416 client.tag_after('test.rss', mem.rss)
1417 client.tag_after('test.rss', mem.rss)
1417 client.tag_after('test.vms', mem.vms)
1418 client.tag_after('test.vms', mem.vms)
1418
1419
1419 client.collect({
1420 client.collect({
1420 'message': "Finished",
1421 'message': "Finished",
1421 })
1422 })
1422 client.send_stats()
1423 client.send_stats()
1423
1424
1424 return client
1425 return client
1425
1426
1426
1427
1427 class AppenlightClient():
1428 class AppenlightClient():
1428
1429
1429 url_template = '{url}?protocol_version=0.5'
1430 url_template = '{url}?protocol_version=0.5'
1430
1431
1431 def __init__(
1432 def __init__(
1432 self, url, api_key, add_server=True, add_timestamp=True,
1433 self, url, api_key, add_server=True, add_timestamp=True,
1433 namespace=None, request=None, testrun=None):
1434 namespace=None, request=None, testrun=None):
1434 self.url = self.url_template.format(url=url)
1435 self.url = self.url_template.format(url=url)
1435 self.api_key = api_key
1436 self.api_key = api_key
1436 self.add_server = add_server
1437 self.add_server = add_server
1437 self.add_timestamp = add_timestamp
1438 self.add_timestamp = add_timestamp
1438 self.namespace = namespace
1439 self.namespace = namespace
1439 self.request = request
1440 self.request = request
1440 self.server = socket.getfqdn(socket.gethostname())
1441 self.server = socket.getfqdn(socket.gethostname())
1441 self.tags_before = {}
1442 self.tags_before = {}
1442 self.tags_after = {}
1443 self.tags_after = {}
1443 self.stats = []
1444 self.stats = []
1444 self.testrun = testrun or {}
1445 self.testrun = testrun or {}
1445
1446
1446 def tag_before(self, tag, value):
1447 def tag_before(self, tag, value):
1447 self.tags_before[tag] = value
1448 self.tags_before[tag] = value
1448
1449
1449 def tag_after(self, tag, value):
1450 def tag_after(self, tag, value):
1450 self.tags_after[tag] = value
1451 self.tags_after[tag] = value
1451
1452
1452 def collect(self, data):
1453 def collect(self, data):
1453 if self.add_server:
1454 if self.add_server:
1454 data.setdefault('server', self.server)
1455 data.setdefault('server', self.server)
1455 if self.add_timestamp:
1456 if self.add_timestamp:
1456 data.setdefault('date', datetime.datetime.utcnow().isoformat())
1457 data.setdefault('date', datetime.datetime.utcnow().isoformat())
1457 if self.namespace:
1458 if self.namespace:
1458 data.setdefault('namespace', self.namespace)
1459 data.setdefault('namespace', self.namespace)
1459 if self.request:
1460 if self.request:
1460 data.setdefault('request', self.request)
1461 data.setdefault('request', self.request)
1461 self.stats.append(data)
1462 self.stats.append(data)
1462
1463
1463 def send_stats(self):
1464 def send_stats(self):
1464 tags = [
1465 tags = [
1465 ('testrun', self.request),
1466 ('testrun', self.request),
1466 ('testrun.start', self.testrun['start']),
1467 ('testrun.start', self.testrun['start']),
1467 ('testrun.timestamp', self.testrun['timestamp']),
1468 ('testrun.timestamp', self.testrun['timestamp']),
1468 ('test', self.namespace),
1469 ('test', self.namespace),
1469 ]
1470 ]
1470 for key, value in self.tags_before.items():
1471 for key, value in self.tags_before.items():
1471 tags.append((key + '.before', value))
1472 tags.append((key + '.before', value))
1472 try:
1473 try:
1473 delta = self.tags_after[key] - value
1474 delta = self.tags_after[key] - value
1474 tags.append((key + '.delta', delta))
1475 tags.append((key + '.delta', delta))
1475 except Exception:
1476 except Exception:
1476 pass
1477 pass
1477 for key, value in self.tags_after.items():
1478 for key, value in self.tags_after.items():
1478 tags.append((key + '.after', value))
1479 tags.append((key + '.after', value))
1479 self.collect({
1480 self.collect({
1480 'message': "Collected tags",
1481 'message': "Collected tags",
1481 'tags': tags,
1482 'tags': tags,
1482 })
1483 })
1483
1484
1484 response = requests.post(
1485 response = requests.post(
1485 self.url,
1486 self.url,
1486 headers={
1487 headers={
1487 'X-appenlight-api-key': self.api_key},
1488 'X-appenlight-api-key': self.api_key},
1488 json=self.stats,
1489 json=self.stats,
1489 )
1490 )
1490
1491
1491 if not response.status_code == 200:
1492 if not response.status_code == 200:
1492 pprint.pprint(self.stats)
1493 pprint.pprint(self.stats)
1493 print response.headers
1494 print response.headers
1494 print response.text
1495 print response.text
1495 raise Exception('Sending to appenlight failed')
1496 raise Exception('Sending to appenlight failed')
1496
1497
1497
1498
1498 @pytest.fixture
1499 @pytest.fixture
1499 def gist_util(request, pylonsapp):
1500 def gist_util(request, pylonsapp):
1500 """
1501 """
1501 Provides a wired instance of `GistUtility` with integrated cleanup.
1502 Provides a wired instance of `GistUtility` with integrated cleanup.
1502 """
1503 """
1503 utility = GistUtility()
1504 utility = GistUtility()
1504 request.addfinalizer(utility.cleanup)
1505 request.addfinalizer(utility.cleanup)
1505 return utility
1506 return utility
1506
1507
1507
1508
1508 class GistUtility(object):
1509 class GistUtility(object):
1509 def __init__(self):
1510 def __init__(self):
1510 self.fixture = Fixture()
1511 self.fixture = Fixture()
1511 self.gist_ids = []
1512 self.gist_ids = []
1512
1513
1513 def create_gist(self, **kwargs):
1514 def create_gist(self, **kwargs):
1514 gist = self.fixture.create_gist(**kwargs)
1515 gist = self.fixture.create_gist(**kwargs)
1515 self.gist_ids.append(gist.gist_id)
1516 self.gist_ids.append(gist.gist_id)
1516 return gist
1517 return gist
1517
1518
1518 def cleanup(self):
1519 def cleanup(self):
1519 for id_ in self.gist_ids:
1520 for id_ in self.gist_ids:
1520 self.fixture.destroy_gists(str(id_))
1521 self.fixture.destroy_gists(str(id_))
1521
1522
1522
1523
1523 @pytest.fixture
1524 @pytest.fixture
1524 def enabled_backends(request):
1525 def enabled_backends(request):
1525 backends = request.config.option.backends
1526 backends = request.config.option.backends
1526 return backends[:]
1527 return backends[:]
1527
1528
1528
1529
1529 @pytest.fixture
1530 @pytest.fixture
1530 def settings_util(request):
1531 def settings_util(request):
1531 """
1532 """
1532 Provides a wired instance of `SettingsUtility` with integrated cleanup.
1533 Provides a wired instance of `SettingsUtility` with integrated cleanup.
1533 """
1534 """
1534 utility = SettingsUtility()
1535 utility = SettingsUtility()
1535 request.addfinalizer(utility.cleanup)
1536 request.addfinalizer(utility.cleanup)
1536 return utility
1537 return utility
1537
1538
1538
1539
1539 class SettingsUtility(object):
1540 class SettingsUtility(object):
1540 def __init__(self):
1541 def __init__(self):
1541 self.rhodecode_ui_ids = []
1542 self.rhodecode_ui_ids = []
1542 self.rhodecode_setting_ids = []
1543 self.rhodecode_setting_ids = []
1543 self.repo_rhodecode_ui_ids = []
1544 self.repo_rhodecode_ui_ids = []
1544 self.repo_rhodecode_setting_ids = []
1545 self.repo_rhodecode_setting_ids = []
1545
1546
1546 def create_repo_rhodecode_ui(
1547 def create_repo_rhodecode_ui(
1547 self, repo, section, value, key=None, active=True, cleanup=True):
1548 self, repo, section, value, key=None, active=True, cleanup=True):
1548 key = key or hashlib.sha1(
1549 key = key or hashlib.sha1(
1549 '{}{}{}'.format(section, value, repo.repo_id)).hexdigest()
1550 '{}{}{}'.format(section, value, repo.repo_id)).hexdigest()
1550
1551
1551 setting = RepoRhodeCodeUi()
1552 setting = RepoRhodeCodeUi()
1552 setting.repository_id = repo.repo_id
1553 setting.repository_id = repo.repo_id
1553 setting.ui_section = section
1554 setting.ui_section = section
1554 setting.ui_value = value
1555 setting.ui_value = value
1555 setting.ui_key = key
1556 setting.ui_key = key
1556 setting.ui_active = active
1557 setting.ui_active = active
1557 Session().add(setting)
1558 Session().add(setting)
1558 Session().commit()
1559 Session().commit()
1559
1560
1560 if cleanup:
1561 if cleanup:
1561 self.repo_rhodecode_ui_ids.append(setting.ui_id)
1562 self.repo_rhodecode_ui_ids.append(setting.ui_id)
1562 return setting
1563 return setting
1563
1564
1564 def create_rhodecode_ui(
1565 def create_rhodecode_ui(
1565 self, section, value, key=None, active=True, cleanup=True):
1566 self, section, value, key=None, active=True, cleanup=True):
1566 key = key or hashlib.sha1('{}{}'.format(section, value)).hexdigest()
1567 key = key or hashlib.sha1('{}{}'.format(section, value)).hexdigest()
1567
1568
1568 setting = RhodeCodeUi()
1569 setting = RhodeCodeUi()
1569 setting.ui_section = section
1570 setting.ui_section = section
1570 setting.ui_value = value
1571 setting.ui_value = value
1571 setting.ui_key = key
1572 setting.ui_key = key
1572 setting.ui_active = active
1573 setting.ui_active = active
1573 Session().add(setting)
1574 Session().add(setting)
1574 Session().commit()
1575 Session().commit()
1575
1576
1576 if cleanup:
1577 if cleanup:
1577 self.rhodecode_ui_ids.append(setting.ui_id)
1578 self.rhodecode_ui_ids.append(setting.ui_id)
1578 return setting
1579 return setting
1579
1580
1580 def create_repo_rhodecode_setting(
1581 def create_repo_rhodecode_setting(
1581 self, repo, name, value, type_, cleanup=True):
1582 self, repo, name, value, type_, cleanup=True):
1582 setting = RepoRhodeCodeSetting(
1583 setting = RepoRhodeCodeSetting(
1583 repo.repo_id, key=name, val=value, type=type_)
1584 repo.repo_id, key=name, val=value, type=type_)
1584 Session().add(setting)
1585 Session().add(setting)
1585 Session().commit()
1586 Session().commit()
1586
1587
1587 if cleanup:
1588 if cleanup:
1588 self.repo_rhodecode_setting_ids.append(setting.app_settings_id)
1589 self.repo_rhodecode_setting_ids.append(setting.app_settings_id)
1589 return setting
1590 return setting
1590
1591
1591 def create_rhodecode_setting(self, name, value, type_, cleanup=True):
1592 def create_rhodecode_setting(self, name, value, type_, cleanup=True):
1592 setting = RhodeCodeSetting(key=name, val=value, type=type_)
1593 setting = RhodeCodeSetting(key=name, val=value, type=type_)
1593 Session().add(setting)
1594 Session().add(setting)
1594 Session().commit()
1595 Session().commit()
1595
1596
1596 if cleanup:
1597 if cleanup:
1597 self.rhodecode_setting_ids.append(setting.app_settings_id)
1598 self.rhodecode_setting_ids.append(setting.app_settings_id)
1598
1599
1599 return setting
1600 return setting
1600
1601
1601 def cleanup(self):
1602 def cleanup(self):
1602 for id_ in self.rhodecode_ui_ids:
1603 for id_ in self.rhodecode_ui_ids:
1603 setting = RhodeCodeUi.get(id_)
1604 setting = RhodeCodeUi.get(id_)
1604 Session().delete(setting)
1605 Session().delete(setting)
1605
1606
1606 for id_ in self.rhodecode_setting_ids:
1607 for id_ in self.rhodecode_setting_ids:
1607 setting = RhodeCodeSetting.get(id_)
1608 setting = RhodeCodeSetting.get(id_)
1608 Session().delete(setting)
1609 Session().delete(setting)
1609
1610
1610 for id_ in self.repo_rhodecode_ui_ids:
1611 for id_ in self.repo_rhodecode_ui_ids:
1611 setting = RepoRhodeCodeUi.get(id_)
1612 setting = RepoRhodeCodeUi.get(id_)
1612 Session().delete(setting)
1613 Session().delete(setting)
1613
1614
1614 for id_ in self.repo_rhodecode_setting_ids:
1615 for id_ in self.repo_rhodecode_setting_ids:
1615 setting = RepoRhodeCodeSetting.get(id_)
1616 setting = RepoRhodeCodeSetting.get(id_)
1616 Session().delete(setting)
1617 Session().delete(setting)
1617
1618
1618 Session().commit()
1619 Session().commit()
1619
1620
1620
1621
1621 @pytest.fixture
1622 @pytest.fixture
1622 def no_notifications(request):
1623 def no_notifications(request):
1623 notification_patcher = mock.patch(
1624 notification_patcher = mock.patch(
1624 'rhodecode.model.notification.NotificationModel.create')
1625 'rhodecode.model.notification.NotificationModel.create')
1625 notification_patcher.start()
1626 notification_patcher.start()
1626 request.addfinalizer(notification_patcher.stop)
1627 request.addfinalizer(notification_patcher.stop)
1627
1628
1628
1629
1629 @pytest.fixture
1630 @pytest.fixture
1630 def silence_action_logger(request):
1631 def silence_action_logger(request):
1631 notification_patcher = mock.patch(
1632 notification_patcher = mock.patch(
1632 'rhodecode.lib.utils.action_logger')
1633 'rhodecode.lib.utils.action_logger')
1633 notification_patcher.start()
1634 notification_patcher.start()
1634 request.addfinalizer(notification_patcher.stop)
1635 request.addfinalizer(notification_patcher.stop)
1635
1636
1636
1637
1637 @pytest.fixture(scope='session')
1638 @pytest.fixture(scope='session')
1638 def repeat(request):
1639 def repeat(request):
1639 """
1640 """
1640 The number of repetitions is based on this fixture.
1641 The number of repetitions is based on this fixture.
1641
1642
1642 Slower calls may divide it by 10 or 100. It is chosen in a way so that the
1643 Slower calls may divide it by 10 or 100. It is chosen in a way so that the
1643 tests are not too slow in our default test suite.
1644 tests are not too slow in our default test suite.
1644 """
1645 """
1645 return request.config.getoption('--repeat')
1646 return request.config.getoption('--repeat')
1646
1647
1647
1648
1648 @pytest.fixture
1649 @pytest.fixture
1649 def rhodecode_fixtures():
1650 def rhodecode_fixtures():
1650 return Fixture()
1651 return Fixture()
1651
1652
1652
1653
1653 @pytest.fixture
1654 @pytest.fixture
1654 def request_stub():
1655 def request_stub():
1655 """
1656 """
1656 Stub request object.
1657 Stub request object.
1657 """
1658 """
1658 request = pyramid.testing.DummyRequest()
1659 request = pyramid.testing.DummyRequest()
1659 request.scheme = 'https'
1660 request.scheme = 'https'
1660 return request
1661 return request
1661
1662
1662
1663
1663 @pytest.fixture
1664 @pytest.fixture
1664 def config_stub(request, request_stub):
1665 def config_stub(request, request_stub):
1665 """
1666 """
1666 Set up pyramid.testing and return the Configurator.
1667 Set up pyramid.testing and return the Configurator.
1667 """
1668 """
1668 config = pyramid.testing.setUp(request=request_stub)
1669 config = pyramid.testing.setUp(request=request_stub)
1669
1670
1670 @request.addfinalizer
1671 @request.addfinalizer
1671 def cleanup():
1672 def cleanup():
1672 pyramid.testing.tearDown()
1673 pyramid.testing.tearDown()
1673
1674
1674 return config
1675 return config
1675
1676
1676
1677
1677 @pytest.fixture
1678 @pytest.fixture
1678 def StubIntegrationType():
1679 def StubIntegrationType():
1679 class _StubIntegrationType(IntegrationTypeBase):
1680 class _StubIntegrationType(IntegrationTypeBase):
1680 """ Test integration type class """
1681 """ Test integration type class """
1681
1682
1682 key = 'test'
1683 key = 'test'
1683 display_name = 'Test integration type'
1684 display_name = 'Test integration type'
1684 description = 'A test integration type for testing'
1685 description = 'A test integration type for testing'
1685 icon = 'test_icon_html_image'
1686 icon = 'test_icon_html_image'
1686
1687
1687 def __init__(self, settings):
1688 def __init__(self, settings):
1688 super(_StubIntegrationType, self).__init__(settings)
1689 super(_StubIntegrationType, self).__init__(settings)
1689 self.sent_events = [] # for testing
1690 self.sent_events = [] # for testing
1690
1691
1691 def send_event(self, event):
1692 def send_event(self, event):
1692 self.sent_events.append(event)
1693 self.sent_events.append(event)
1693
1694
1694 def settings_schema(self):
1695 def settings_schema(self):
1695 class SettingsSchema(colander.Schema):
1696 class SettingsSchema(colander.Schema):
1696 test_string_field = colander.SchemaNode(
1697 test_string_field = colander.SchemaNode(
1697 colander.String(),
1698 colander.String(),
1698 missing=colander.required,
1699 missing=colander.required,
1699 title='test string field',
1700 title='test string field',
1700 )
1701 )
1701 test_int_field = colander.SchemaNode(
1702 test_int_field = colander.SchemaNode(
1702 colander.Int(),
1703 colander.Int(),
1703 title='some integer setting',
1704 title='some integer setting',
1704 )
1705 )
1705 return SettingsSchema()
1706 return SettingsSchema()
1706
1707
1707
1708
1708 integration_type_registry.register_integration_type(_StubIntegrationType)
1709 integration_type_registry.register_integration_type(_StubIntegrationType)
1709 return _StubIntegrationType
1710 return _StubIntegrationType
1710
1711
1711 @pytest.fixture
1712 @pytest.fixture
1712 def stub_integration_settings():
1713 def stub_integration_settings():
1713 return {
1714 return {
1714 'test_string_field': 'some data',
1715 'test_string_field': 'some data',
1715 'test_int_field': 100,
1716 'test_int_field': 100,
1716 }
1717 }
1717
1718
1718
1719
1719 @pytest.fixture
1720 @pytest.fixture
1720 def repo_integration_stub(request, repo_stub, StubIntegrationType,
1721 def repo_integration_stub(request, repo_stub, StubIntegrationType,
1721 stub_integration_settings):
1722 stub_integration_settings):
1722 integration = IntegrationModel().create(
1723 integration = IntegrationModel().create(
1723 StubIntegrationType, settings=stub_integration_settings, enabled=True,
1724 StubIntegrationType, settings=stub_integration_settings, enabled=True,
1724 name='test repo integration',
1725 name='test repo integration',
1725 repo=repo_stub, repo_group=None, child_repos_only=None)
1726 repo=repo_stub, repo_group=None, child_repos_only=None)
1726
1727
1727 @request.addfinalizer
1728 @request.addfinalizer
1728 def cleanup():
1729 def cleanup():
1729 IntegrationModel().delete(integration)
1730 IntegrationModel().delete(integration)
1730
1731
1731 return integration
1732 return integration
1732
1733
1733
1734
1734 @pytest.fixture
1735 @pytest.fixture
1735 def repogroup_integration_stub(request, test_repo_group, StubIntegrationType,
1736 def repogroup_integration_stub(request, test_repo_group, StubIntegrationType,
1736 stub_integration_settings):
1737 stub_integration_settings):
1737 integration = IntegrationModel().create(
1738 integration = IntegrationModel().create(
1738 StubIntegrationType, settings=stub_integration_settings, enabled=True,
1739 StubIntegrationType, settings=stub_integration_settings, enabled=True,
1739 name='test repogroup integration',
1740 name='test repogroup integration',
1740 repo=None, repo_group=test_repo_group, child_repos_only=True)
1741 repo=None, repo_group=test_repo_group, child_repos_only=True)
1741
1742
1742 @request.addfinalizer
1743 @request.addfinalizer
1743 def cleanup():
1744 def cleanup():
1744 IntegrationModel().delete(integration)
1745 IntegrationModel().delete(integration)
1745
1746
1746 return integration
1747 return integration
1747
1748
1748
1749
1749 @pytest.fixture
1750 @pytest.fixture
1750 def repogroup_recursive_integration_stub(request, test_repo_group,
1751 def repogroup_recursive_integration_stub(request, test_repo_group,
1751 StubIntegrationType, stub_integration_settings):
1752 StubIntegrationType, stub_integration_settings):
1752 integration = IntegrationModel().create(
1753 integration = IntegrationModel().create(
1753 StubIntegrationType, settings=stub_integration_settings, enabled=True,
1754 StubIntegrationType, settings=stub_integration_settings, enabled=True,
1754 name='test recursive repogroup integration',
1755 name='test recursive repogroup integration',
1755 repo=None, repo_group=test_repo_group, child_repos_only=False)
1756 repo=None, repo_group=test_repo_group, child_repos_only=False)
1756
1757
1757 @request.addfinalizer
1758 @request.addfinalizer
1758 def cleanup():
1759 def cleanup():
1759 IntegrationModel().delete(integration)
1760 IntegrationModel().delete(integration)
1760
1761
1761 return integration
1762 return integration
1762
1763
1763
1764
1764 @pytest.fixture
1765 @pytest.fixture
1765 def global_integration_stub(request, StubIntegrationType,
1766 def global_integration_stub(request, StubIntegrationType,
1766 stub_integration_settings):
1767 stub_integration_settings):
1767 integration = IntegrationModel().create(
1768 integration = IntegrationModel().create(
1768 StubIntegrationType, settings=stub_integration_settings, enabled=True,
1769 StubIntegrationType, settings=stub_integration_settings, enabled=True,
1769 name='test global integration',
1770 name='test global integration',
1770 repo=None, repo_group=None, child_repos_only=None)
1771 repo=None, repo_group=None, child_repos_only=None)
1771
1772
1772 @request.addfinalizer
1773 @request.addfinalizer
1773 def cleanup():
1774 def cleanup():
1774 IntegrationModel().delete(integration)
1775 IntegrationModel().delete(integration)
1775
1776
1776 return integration
1777 return integration
1777
1778
1778
1779
1779 @pytest.fixture
1780 @pytest.fixture
1780 def root_repos_integration_stub(request, StubIntegrationType,
1781 def root_repos_integration_stub(request, StubIntegrationType,
1781 stub_integration_settings):
1782 stub_integration_settings):
1782 integration = IntegrationModel().create(
1783 integration = IntegrationModel().create(
1783 StubIntegrationType, settings=stub_integration_settings, enabled=True,
1784 StubIntegrationType, settings=stub_integration_settings, enabled=True,
1784 name='test global integration',
1785 name='test global integration',
1785 repo=None, repo_group=None, child_repos_only=True)
1786 repo=None, repo_group=None, child_repos_only=True)
1786
1787
1787 @request.addfinalizer
1788 @request.addfinalizer
1788 def cleanup():
1789 def cleanup():
1789 IntegrationModel().delete(integration)
1790 IntegrationModel().delete(integration)
1790
1791
1791 return integration
1792 return integration
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now