##// END OF EJS Templates
search: moved search into pyramid views.
marcink -
r1685:0f027159 default
parent child Browse files
Show More
@@ -0,0 +1,44 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 from rhodecode.apps._base import ADMIN_PREFIX
21
22
23 def includeme(config):
24
25 config.add_route(
26 name='search',
27 pattern=ADMIN_PREFIX + '/search')
28
29 config.add_route(
30 name='search_repo',
31 pattern='/{repo_name:.*?[^/]}/search', repo_route=True)
32
33 # Scan module for configuration decorators.
34 config.scan()
35
36
37 # # FULL TEXT SEARCH
38 # rmap.connect('search', '%s/search' % (ADMIN_PREFIX,),
39 # controller='search')
40 # rmap.connect('search_repo_home', '/{repo_name}/search',
41 # controller='search',
42 # action='index',
43 # conditions={'function': check_repo},
44 # requirements=URL_NAME_REQUIREMENTS) No newline at end of file
1 NO CONTENT: new file 100644
@@ -0,0 +1,202 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import os
22
23 import mock
24 import pytest
25 from whoosh import query
26
27 from rhodecode.tests import (
28 TestController, SkipTest, HG_REPO,
29 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
30 from rhodecode.tests.utils import AssertResponse
31
32
33 def route_path(name, **kwargs):
34 from rhodecode.apps._base import ADMIN_PREFIX
35 return {
36 'search':
37 ADMIN_PREFIX + '/search',
38 'search_repo':
39 '/{repo_name}/search',
40
41 }[name].format(**kwargs)
42
43
44 class TestSearchController(TestController):
45
46 def test_index(self):
47 self.log_user()
48 response = self.app.get(route_path('search'))
49 assert_response = AssertResponse(response)
50 assert_response.one_element_exists('input#q')
51
52 def test_search_files_empty_search(self):
53 if os.path.isdir(self.index_location):
54 raise SkipTest('skipped due to existing index')
55 else:
56 self.log_user()
57 response = self.app.get(route_path('search'),
58 {'q': HG_REPO})
59 response.mustcontain('There is no index to search in. '
60 'Please run whoosh indexer')
61
62 def test_search_validation(self):
63 self.log_user()
64 response = self.app.get(route_path('search'),
65 {'q': query, 'type': 'content', 'page_limit': 1000})
66
67 response.mustcontain(
68 'page_limit - 1000 is greater than maximum value 500')
69
70 @pytest.mark.parametrize("query, expected_hits, expected_paths", [
71 ('todo', 23, [
72 'vcs/backends/hg/inmemory.py',
73 'vcs/tests/test_git.py']),
74 ('extension:rst installation', 6, [
75 'docs/index.rst',
76 'docs/installation.rst']),
77 ('def repo', 87, [
78 'vcs/tests/test_git.py',
79 'vcs/tests/test_changesets.py']),
80 ('repository:%s def test' % HG_REPO, 18, [
81 'vcs/tests/test_git.py',
82 'vcs/tests/test_changesets.py']),
83 ('"def main"', 9, [
84 'vcs/__init__.py',
85 'vcs/tests/__init__.py',
86 'vcs/utils/progressbar.py']),
87 ('owner:test_admin', 358, [
88 'vcs/tests/base.py',
89 'MANIFEST.in',
90 'vcs/utils/termcolors.py',
91 'docs/theme/ADC/static/documentation.png']),
92 ('owner:test_admin def main', 72, [
93 'vcs/__init__.py',
94 'vcs/tests/test_utils_filesize.py',
95 'vcs/tests/test_cli.py']),
96 ('owner:michał test', 0, []),
97 ])
98 def test_search_files(self, query, expected_hits, expected_paths):
99 self.log_user()
100 response = self.app.get(route_path('search'),
101 {'q': query, 'type': 'content', 'page_limit': 500})
102
103 response.mustcontain('%s results' % expected_hits)
104 for path in expected_paths:
105 response.mustcontain(path)
106
107 @pytest.mark.parametrize("query, expected_hits, expected_commits", [
108 ('bother to ask where to fetch repo during tests', 3, [
109 ('hg', 'a00c1b6f5d7a6ae678fd553a8b81d92367f7ecf1'),
110 ('git', 'c6eb379775c578a95dad8ddab53f963b80894850'),
111 ('svn', '98')]),
112 ('michał', 0, []),
113 ('changed:tests/utils.py', 36, [
114 ('hg', 'a00c1b6f5d7a6ae678fd553a8b81d92367f7ecf1')]),
115 ('changed:vcs/utils/archivers.py', 11, [
116 ('hg', '25213a5fbb048dff8ba65d21e466a835536e5b70'),
117 ('hg', '47aedd538bf616eedcb0e7d630ea476df0e159c7'),
118 ('hg', 'f5d23247fad4856a1dabd5838afade1e0eed24fb'),
119 ('hg', '04ad456aefd6461aea24f90b63954b6b1ce07b3e'),
120 ('git', 'c994f0de03b2a0aa848a04fc2c0d7e737dba31fc'),
121 ('git', 'd1f898326327e20524fe22417c22d71064fe54a1'),
122 ('git', 'fe568b4081755c12abf6ba673ba777fc02a415f3'),
123 ('git', 'bafe786f0d8c2ff7da5c1dcfcfa577de0b5e92f1')]),
124 ('added:README.rst', 3, [
125 ('hg', '3803844fdbd3b711175fc3da9bdacfcd6d29a6fb'),
126 ('git', 'ff7ca51e58c505fec0dd2491de52c622bb7a806b'),
127 ('svn', '8')]),
128 ('changed:lazy.py', 15, [
129 ('hg', 'eaa291c5e6ae6126a203059de9854ccf7b5baa12'),
130 ('git', '17438a11f72b93f56d0e08e7d1fa79a378578a82'),
131 ('svn', '82'),
132 ('svn', '262'),
133 ('hg', 'f5d23247fad4856a1dabd5838afade1e0eed24fb'),
134 ('git', '33fa3223355104431402a888fa77a4e9956feb3e')
135 ]),
136 ('author:marcin@python-blog.com '
137 'commit_id:b986218ba1c9b0d6a259fac9b050b1724ed8e545', 1, [
138 ('hg', 'b986218ba1c9b0d6a259fac9b050b1724ed8e545')]),
139 ('b986218ba1c9b0d6a259fac9b050b1724ed8e545', 1, [
140 ('hg', 'b986218ba1c9b0d6a259fac9b050b1724ed8e545')]),
141 ('b986218b', 1, [
142 ('hg', 'b986218ba1c9b0d6a259fac9b050b1724ed8e545')]),
143 ])
144 def test_search_commit_messages(
145 self, query, expected_hits, expected_commits, enabled_backends):
146 self.log_user()
147 response = self.app.get(route_path('search'),
148 {'q': query, 'type': 'commit', 'page_limit': 500})
149
150 response.mustcontain('%s results' % expected_hits)
151 for backend, commit_id in expected_commits:
152 if backend in enabled_backends:
153 response.mustcontain(commit_id)
154
155 @pytest.mark.parametrize("query, expected_hits, expected_paths", [
156 ('readme.rst', 3, []),
157 ('test*', 75, []),
158 ('*model*', 1, []),
159 ('extension:rst', 48, []),
160 ('extension:rst api', 24, []),
161 ])
162 def test_search_file_paths(self, query, expected_hits, expected_paths):
163 self.log_user()
164 response = self.app.get(route_path('search'),
165 {'q': query, 'type': 'path', 'page_limit': 500})
166
167 response.mustcontain('%s results' % expected_hits)
168 for path in expected_paths:
169 response.mustcontain(path)
170
171 def test_search_commit_message_specific_repo(self, backend):
172 self.log_user()
173 response = self.app.get(
174 route_path('search_repo',repo_name=backend.repo_name),
175 {'q': 'bother to ask where to fetch repo during tests',
176 'type': 'commit'})
177
178 response.mustcontain('1 results')
179
180 def test_filters_are_not_applied_for_admin_user(self):
181 self.log_user()
182 with mock.patch('whoosh.searching.Searcher.search') as search_mock:
183 self.app.get(route_path('search'),
184 {'q': 'test query', 'type': 'commit'})
185 assert search_mock.call_count == 1
186 _, kwargs = search_mock.call_args
187 assert kwargs['filter'] is None
188
189 def test_filters_are_applied_for_normal_user(self, enabled_backends):
190 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
191 with mock.patch('whoosh.searching.Searcher.search') as search_mock:
192 self.app.get(route_path('search'),
193 {'q': 'test query', 'type': 'commit'})
194 assert search_mock.call_count == 1
195 _, kwargs = search_mock.call_args
196 assert isinstance(kwargs['filter'], query.Or)
197 expected_repositories = [
198 'vcs_test_{}'.format(b) for b in enabled_backends]
199 queried_repositories = [
200 name for type_, name in kwargs['filter'].all_terms()]
201 for repository in expected_repositories:
202 assert repository in queried_repositories
@@ -0,0 +1,133 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import logging
22 import urllib
23 from pyramid.view import view_config
24 from webhelpers.util import update_params
25
26 from rhodecode.apps._base import BaseAppView, RepoAppView
27 from rhodecode.lib.auth import (LoginRequired, HasRepoPermissionAnyDecorator)
28 from rhodecode.lib.helpers import Page
29 from rhodecode.lib.utils2 import safe_str, safe_int
30 from rhodecode.lib.index import searcher_from_config
31 from rhodecode.model import validation_schema
32 from rhodecode.model.validation_schema.schemas import search_schema
33
34 log = logging.getLogger(__name__)
35
36
37 def search(request, tmpl_context, repo_name):
38 searcher = searcher_from_config(request.registry.settings)
39 formatted_results = []
40 execution_time = ''
41
42 schema = search_schema.SearchParamsSchema()
43
44 search_params = {}
45 errors = []
46 try:
47 search_params = schema.deserialize(
48 dict(search_query=request.GET.get('q'),
49 search_type=request.GET.get('type'),
50 search_sort=request.GET.get('sort'),
51 page_limit=request.GET.get('page_limit'),
52 requested_page=request.GET.get('page'))
53 )
54 except validation_schema.Invalid as e:
55 errors = e.children
56
57 def url_generator(**kw):
58 q = urllib.quote(safe_str(search_query))
59 return update_params(
60 "?q=%s&type=%s" % (q, safe_str(search_type)), **kw)
61
62 c = tmpl_context
63 search_query = search_params.get('search_query')
64 search_type = search_params.get('search_type')
65 search_sort = search_params.get('search_sort')
66 if search_params.get('search_query'):
67 page_limit = search_params['page_limit']
68 requested_page = search_params['requested_page']
69
70 try:
71 search_result = searcher.search(
72 search_query, search_type, c.auth_user, repo_name,
73 requested_page, page_limit, search_sort)
74
75 formatted_results = Page(
76 search_result['results'], page=requested_page,
77 item_count=search_result['count'],
78 items_per_page=page_limit, url=url_generator)
79 finally:
80 searcher.cleanup()
81
82 if not search_result['error']:
83 execution_time = '%s results (%.3f seconds)' % (
84 search_result['count'],
85 search_result['runtime'])
86 elif not errors:
87 node = schema['search_query']
88 errors = [
89 validation_schema.Invalid(node, search_result['error'])]
90
91 c.perm_user = c.auth_user
92 c.repo_name = repo_name
93 c.sort = search_sort
94 c.url_generator = url_generator
95 c.errors = errors
96 c.formatted_results = formatted_results
97 c.runtime = execution_time
98 c.cur_query = search_query
99 c.search_type = search_type
100 c.searcher = searcher
101
102
103 class SearchView(BaseAppView):
104 def load_default_context(self):
105 c = self._get_local_tmpl_context()
106 self._register_global_c(c)
107 return c
108
109 @LoginRequired()
110 @view_config(
111 route_name='search', request_method='GET',
112 renderer='rhodecode:templates/search/search.mako')
113 def search(self):
114 c = self.load_default_context()
115 search(self.request, c, repo_name=None)
116 return self._get_template_context(c)
117
118
119 class SearchRepoView(RepoAppView):
120 def load_default_context(self):
121 c = self._get_local_tmpl_context()
122 self._register_global_c(c)
123 return c
124
125 @LoginRequired()
126 @HasRepoPermissionAnyDecorator('repository.admin')
127 @view_config(
128 route_name='search_repo', request_method='GET',
129 renderer='rhodecode:templates/search/search.mako')
130 def search_repo(self):
131 c = self.load_default_context()
132 search(self.request, c, repo_name=self.db_repo_name)
133 return self._get_template_context(c)
@@ -1,513 +1,514 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Pylons middleware initialization
23 23 """
24 24 import logging
25 25 from collections import OrderedDict
26 26
27 27 from paste.registry import RegistryManager
28 28 from paste.gzipper import make_gzip_middleware
29 29 from pylons.wsgiapp import PylonsApp
30 30 from pyramid.authorization import ACLAuthorizationPolicy
31 31 from pyramid.config import Configurator
32 32 from pyramid.settings import asbool, aslist
33 33 from pyramid.wsgi import wsgiapp
34 34 from pyramid.httpexceptions import (
35 35 HTTPException, HTTPError, HTTPInternalServerError, HTTPFound)
36 36 from pyramid.events import ApplicationCreated
37 37 from pyramid.renderers import render_to_response
38 38 from routes.middleware import RoutesMiddleware
39 39 import routes.util
40 40
41 41 import rhodecode
42 42 from rhodecode.model import meta
43 43 from rhodecode.config import patches
44 44 from rhodecode.config.routing import STATIC_FILE_PREFIX
45 45 from rhodecode.config.environment import (
46 46 load_environment, load_pyramid_environment)
47 47 from rhodecode.lib.middleware import csrf
48 48 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
49 49 from rhodecode.lib.middleware.error_handling import (
50 50 PylonsErrorHandlingMiddleware)
51 51 from rhodecode.lib.middleware.https_fixup import HttpsFixup
52 52 from rhodecode.lib.middleware.vcs import VCSMiddleware
53 53 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
54 54 from rhodecode.lib.utils2 import aslist as rhodecode_aslist
55 55 from rhodecode.subscribers import (
56 56 scan_repositories_if_enabled, write_js_routes_if_enabled,
57 57 write_metadata_if_needed)
58 58
59 59
60 60 log = logging.getLogger(__name__)
61 61
62 62
63 63 # this is used to avoid avoid the route lookup overhead in routesmiddleware
64 64 # for certain routes which won't go to pylons to - eg. static files, debugger
65 65 # it is only needed for the pylons migration and can be removed once complete
66 66 class SkippableRoutesMiddleware(RoutesMiddleware):
67 67 """ Routes middleware that allows you to skip prefixes """
68 68
69 69 def __init__(self, *args, **kw):
70 70 self.skip_prefixes = kw.pop('skip_prefixes', [])
71 71 super(SkippableRoutesMiddleware, self).__init__(*args, **kw)
72 72
73 73 def __call__(self, environ, start_response):
74 74 for prefix in self.skip_prefixes:
75 75 if environ['PATH_INFO'].startswith(prefix):
76 76 # added to avoid the case when a missing /_static route falls
77 77 # through to pylons and causes an exception as pylons is
78 78 # expecting wsgiorg.routingargs to be set in the environ
79 79 # by RoutesMiddleware.
80 80 if 'wsgiorg.routing_args' not in environ:
81 81 environ['wsgiorg.routing_args'] = (None, {})
82 82 return self.app(environ, start_response)
83 83
84 84 return super(SkippableRoutesMiddleware, self).__call__(
85 85 environ, start_response)
86 86
87 87
88 88 def make_app(global_conf, static_files=True, **app_conf):
89 89 """Create a Pylons WSGI application and return it
90 90
91 91 ``global_conf``
92 92 The inherited configuration for this application. Normally from
93 93 the [DEFAULT] section of the Paste ini file.
94 94
95 95 ``app_conf``
96 96 The application's local configuration. Normally specified in
97 97 the [app:<name>] section of the Paste ini file (where <name>
98 98 defaults to main).
99 99
100 100 """
101 101 # Apply compatibility patches
102 102 patches.kombu_1_5_1_python_2_7_11()
103 103 patches.inspect_getargspec()
104 104
105 105 # Configure the Pylons environment
106 106 config = load_environment(global_conf, app_conf)
107 107
108 108 # The Pylons WSGI app
109 109 app = PylonsApp(config=config)
110 110 if rhodecode.is_test:
111 111 app = csrf.CSRFDetector(app)
112 112
113 113 expected_origin = config.get('expected_origin')
114 114 if expected_origin:
115 115 # The API can be accessed from other Origins.
116 116 app = csrf.OriginChecker(app, expected_origin,
117 117 skip_urls=[routes.util.url_for('api')])
118 118
119 119 # Establish the Registry for this application
120 120 app = RegistryManager(app)
121 121
122 122 app.config = config
123 123
124 124 return app
125 125
126 126
127 127 def make_pyramid_app(global_config, **settings):
128 128 """
129 129 Constructs the WSGI application based on Pyramid and wraps the Pylons based
130 130 application.
131 131
132 132 Specials:
133 133
134 134 * We migrate from Pylons to Pyramid. While doing this, we keep both
135 135 frameworks functional. This involves moving some WSGI middlewares around
136 136 and providing access to some data internals, so that the old code is
137 137 still functional.
138 138
139 139 * The application can also be integrated like a plugin via the call to
140 140 `includeme`. This is accompanied with the other utility functions which
141 141 are called. Changing this should be done with great care to not break
142 142 cases when these fragments are assembled from another place.
143 143
144 144 """
145 145 # The edition string should be available in pylons too, so we add it here
146 146 # before copying the settings.
147 147 settings.setdefault('rhodecode.edition', 'Community Edition')
148 148
149 149 # As long as our Pylons application does expect "unprepared" settings, make
150 150 # sure that we keep an unmodified copy. This avoids unintentional change of
151 151 # behavior in the old application.
152 152 settings_pylons = settings.copy()
153 153
154 154 sanitize_settings_and_apply_defaults(settings)
155 155 config = Configurator(settings=settings)
156 156 add_pylons_compat_data(config.registry, global_config, settings_pylons)
157 157
158 158 load_pyramid_environment(global_config, settings)
159 159
160 160 includeme_first(config)
161 161 includeme(config)
162 162 pyramid_app = config.make_wsgi_app()
163 163 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
164 164 pyramid_app.config = config
165 165
166 166 # creating the app uses a connection - return it after we are done
167 167 meta.Session.remove()
168 168
169 169 return pyramid_app
170 170
171 171
172 172 def make_not_found_view(config):
173 173 """
174 174 This creates the view which should be registered as not-found-view to
175 175 pyramid. Basically it contains of the old pylons app, converted to a view.
176 176 Additionally it is wrapped by some other middlewares.
177 177 """
178 178 settings = config.registry.settings
179 179 vcs_server_enabled = settings['vcs.server.enable']
180 180
181 181 # Make pylons app from unprepared settings.
182 182 pylons_app = make_app(
183 183 config.registry._pylons_compat_global_config,
184 184 **config.registry._pylons_compat_settings)
185 185 config.registry._pylons_compat_config = pylons_app.config
186 186
187 187 # Appenlight monitoring.
188 188 pylons_app, appenlight_client = wrap_in_appenlight_if_enabled(
189 189 pylons_app, settings)
190 190
191 191 # The pylons app is executed inside of the pyramid 404 exception handler.
192 192 # Exceptions which are raised inside of it are not handled by pyramid
193 193 # again. Therefore we add a middleware that invokes the error handler in
194 194 # case of an exception or error response. This way we return proper error
195 195 # HTML pages in case of an error.
196 196 reraise = (settings.get('debugtoolbar.enabled', False) or
197 197 rhodecode.disable_error_handler)
198 198 pylons_app = PylonsErrorHandlingMiddleware(
199 199 pylons_app, error_handler, reraise)
200 200
201 201 # The VCSMiddleware shall operate like a fallback if pyramid doesn't find a
202 202 # view to handle the request. Therefore it is wrapped around the pylons
203 203 # app. It has to be outside of the error handling otherwise error responses
204 204 # from the vcsserver are converted to HTML error pages. This confuses the
205 205 # command line tools and the user won't get a meaningful error message.
206 206 if vcs_server_enabled:
207 207 pylons_app = VCSMiddleware(
208 208 pylons_app, settings, appenlight_client, registry=config.registry)
209 209
210 210 # Convert WSGI app to pyramid view and return it.
211 211 return wsgiapp(pylons_app)
212 212
213 213
214 214 def add_pylons_compat_data(registry, global_config, settings):
215 215 """
216 216 Attach data to the registry to support the Pylons integration.
217 217 """
218 218 registry._pylons_compat_global_config = global_config
219 219 registry._pylons_compat_settings = settings
220 220
221 221
222 222 def error_handler(exception, request):
223 223 import rhodecode
224 224 from rhodecode.lib.utils2 import AttributeDict
225 225
226 226 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
227 227
228 228 base_response = HTTPInternalServerError()
229 229 # prefer original exception for the response since it may have headers set
230 230 if isinstance(exception, HTTPException):
231 231 base_response = exception
232 232
233 233 def is_http_error(response):
234 234 # error which should have traceback
235 235 return response.status_code > 499
236 236
237 237 if is_http_error(base_response):
238 238 log.exception(
239 239 'error occurred handling this request for path: %s', request.path)
240 240
241 241 c = AttributeDict()
242 242 c.error_message = base_response.status
243 243 c.error_explanation = base_response.explanation or str(base_response)
244 244 c.visual = AttributeDict()
245 245
246 246 c.visual.rhodecode_support_url = (
247 247 request.registry.settings.get('rhodecode_support_url') or
248 248 request.route_url('rhodecode_support')
249 249 )
250 250 c.redirect_time = 0
251 251 c.rhodecode_name = rhodecode_title
252 252 if not c.rhodecode_name:
253 253 c.rhodecode_name = 'Rhodecode'
254 254
255 255 c.causes = []
256 256 if hasattr(base_response, 'causes'):
257 257 c.causes = base_response.causes
258 258
259 259 response = render_to_response(
260 260 '/errors/error_document.mako', {'c': c}, request=request,
261 261 response=base_response)
262 262
263 263 return response
264 264
265 265
266 266 def includeme(config):
267 267 settings = config.registry.settings
268 268
269 269 # plugin information
270 270 config.registry.rhodecode_plugins = OrderedDict()
271 271
272 272 config.add_directive(
273 273 'register_rhodecode_plugin', register_rhodecode_plugin)
274 274
275 275 if asbool(settings.get('appenlight', 'false')):
276 276 config.include('appenlight_client.ext.pyramid_tween')
277 277
278 278 # Includes which are required. The application would fail without them.
279 279 config.include('pyramid_mako')
280 280 config.include('pyramid_beaker')
281 281
282 282 config.include('rhodecode.authentication')
283 283 config.include('rhodecode.integrations')
284 284
285 285 # apps
286 286 config.include('rhodecode.apps._base')
287 287 config.include('rhodecode.apps.ops')
288 288
289 289 config.include('rhodecode.apps.admin')
290 290 config.include('rhodecode.apps.channelstream')
291 291 config.include('rhodecode.apps.login')
292 292 config.include('rhodecode.apps.home')
293 293 config.include('rhodecode.apps.repository')
294 config.include('rhodecode.apps.search')
294 295 config.include('rhodecode.apps.user_profile')
295 296 config.include('rhodecode.apps.my_account')
296 297 config.include('rhodecode.apps.svn_support')
297 298
298 299 config.include('rhodecode.tweens')
299 300 config.include('rhodecode.api')
300 301
301 302 config.add_route(
302 303 'rhodecode_support', 'https://rhodecode.com/help/', static=True)
303 304
304 305 config.add_translation_dirs('rhodecode:i18n/')
305 306 settings['default_locale_name'] = settings.get('lang', 'en')
306 307
307 308 # Add subscribers.
308 309 config.add_subscriber(scan_repositories_if_enabled, ApplicationCreated)
309 310 config.add_subscriber(write_metadata_if_needed, ApplicationCreated)
310 311 config.add_subscriber(write_js_routes_if_enabled, ApplicationCreated)
311 312
312 313 # Set the authorization policy.
313 314 authz_policy = ACLAuthorizationPolicy()
314 315 config.set_authorization_policy(authz_policy)
315 316
316 317 # Set the default renderer for HTML templates to mako.
317 318 config.add_mako_renderer('.html')
318 319
319 320 config.add_renderer(
320 321 name='json_ext',
321 322 factory='rhodecode.lib.ext_json_renderer.pyramid_ext_json')
322 323
323 324 # include RhodeCode plugins
324 325 includes = aslist(settings.get('rhodecode.includes', []))
325 326 for inc in includes:
326 327 config.include(inc)
327 328
328 329 # This is the glue which allows us to migrate in chunks. By registering the
329 330 # pylons based application as the "Not Found" view in Pyramid, we will
330 331 # fallback to the old application each time the new one does not yet know
331 332 # how to handle a request.
332 333 config.add_notfound_view(make_not_found_view(config))
333 334
334 335 if not settings.get('debugtoolbar.enabled', False):
335 336 # if no toolbar, then any exception gets caught and rendered
336 337 config.add_view(error_handler, context=Exception)
337 338
338 339 config.add_view(error_handler, context=HTTPError)
339 340
340 341
341 342 def includeme_first(config):
342 343 # redirect automatic browser favicon.ico requests to correct place
343 344 def favicon_redirect(context, request):
344 345 return HTTPFound(
345 346 request.static_path('rhodecode:public/images/favicon.ico'))
346 347
347 348 config.add_view(favicon_redirect, route_name='favicon')
348 349 config.add_route('favicon', '/favicon.ico')
349 350
350 351 def robots_redirect(context, request):
351 352 return HTTPFound(
352 353 request.static_path('rhodecode:public/robots.txt'))
353 354
354 355 config.add_view(robots_redirect, route_name='robots')
355 356 config.add_route('robots', '/robots.txt')
356 357
357 358 config.add_static_view(
358 359 '_static/deform', 'deform:static')
359 360 config.add_static_view(
360 361 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
361 362
362 363
363 364 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
364 365 """
365 366 Apply outer WSGI middlewares around the application.
366 367
367 368 Part of this has been moved up from the Pylons layer, so that the
368 369 data is also available if old Pylons code is hit through an already ported
369 370 view.
370 371 """
371 372 settings = config.registry.settings
372 373
373 374 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
374 375 pyramid_app = HttpsFixup(pyramid_app, settings)
375 376
376 377 # Add RoutesMiddleware to support the pylons compatibility tween during
377 378 # migration to pyramid.
378 379 pyramid_app = SkippableRoutesMiddleware(
379 380 pyramid_app, config.registry._pylons_compat_config['routes.map'],
380 381 skip_prefixes=(STATIC_FILE_PREFIX, '/_debug_toolbar'))
381 382
382 383 pyramid_app, _ = wrap_in_appenlight_if_enabled(pyramid_app, settings)
383 384
384 385 if settings['gzip_responses']:
385 386 pyramid_app = make_gzip_middleware(
386 387 pyramid_app, settings, compress_level=1)
387 388
388 389 # this should be the outer most middleware in the wsgi stack since
389 390 # middleware like Routes make database calls
390 391 def pyramid_app_with_cleanup(environ, start_response):
391 392 try:
392 393 return pyramid_app(environ, start_response)
393 394 finally:
394 395 # Dispose current database session and rollback uncommitted
395 396 # transactions.
396 397 meta.Session.remove()
397 398
398 399 # In a single threaded mode server, on non sqlite db we should have
399 400 # '0 Current Checked out connections' at the end of a request,
400 401 # if not, then something, somewhere is leaving a connection open
401 402 pool = meta.Base.metadata.bind.engine.pool
402 403 log.debug('sa pool status: %s', pool.status())
403 404
404 405
405 406 return pyramid_app_with_cleanup
406 407
407 408
408 409 def sanitize_settings_and_apply_defaults(settings):
409 410 """
410 411 Applies settings defaults and does all type conversion.
411 412
412 413 We would move all settings parsing and preparation into this place, so that
413 414 we have only one place left which deals with this part. The remaining parts
414 415 of the application would start to rely fully on well prepared settings.
415 416
416 417 This piece would later be split up per topic to avoid a big fat monster
417 418 function.
418 419 """
419 420
420 421 # Pyramid's mako renderer has to search in the templates folder so that the
421 422 # old templates still work. Ported and new templates are expected to use
422 423 # real asset specifications for the includes.
423 424 mako_directories = settings.setdefault('mako.directories', [
424 425 # Base templates of the original Pylons application
425 426 'rhodecode:templates',
426 427 ])
427 428 log.debug(
428 429 "Using the following Mako template directories: %s",
429 430 mako_directories)
430 431
431 432 # Default includes, possible to change as a user
432 433 pyramid_includes = settings.setdefault('pyramid.includes', [
433 434 'rhodecode.lib.middleware.request_wrapper',
434 435 ])
435 436 log.debug(
436 437 "Using the following pyramid.includes: %s",
437 438 pyramid_includes)
438 439
439 440 # TODO: johbo: Re-think this, usually the call to config.include
440 441 # should allow to pass in a prefix.
441 442 settings.setdefault('rhodecode.api.url', '/_admin/api')
442 443
443 444 # Sanitize generic settings.
444 445 _list_setting(settings, 'default_encoding', 'UTF-8')
445 446 _bool_setting(settings, 'is_test', 'false')
446 447 _bool_setting(settings, 'gzip_responses', 'false')
447 448
448 449 # Call split out functions that sanitize settings for each topic.
449 450 _sanitize_appenlight_settings(settings)
450 451 _sanitize_vcs_settings(settings)
451 452
452 453 return settings
453 454
454 455
455 456 def _sanitize_appenlight_settings(settings):
456 457 _bool_setting(settings, 'appenlight', 'false')
457 458
458 459
459 460 def _sanitize_vcs_settings(settings):
460 461 """
461 462 Applies settings defaults and does type conversion for all VCS related
462 463 settings.
463 464 """
464 465 _string_setting(settings, 'vcs.svn.compatible_version', '')
465 466 _string_setting(settings, 'git_rev_filter', '--all')
466 467 _string_setting(settings, 'vcs.hooks.protocol', 'http')
467 468 _string_setting(settings, 'vcs.scm_app_implementation', 'http')
468 469 _string_setting(settings, 'vcs.server', '')
469 470 _string_setting(settings, 'vcs.server.log_level', 'debug')
470 471 _string_setting(settings, 'vcs.server.protocol', 'http')
471 472 _bool_setting(settings, 'startup.import_repos', 'false')
472 473 _bool_setting(settings, 'vcs.hooks.direct_calls', 'false')
473 474 _bool_setting(settings, 'vcs.server.enable', 'true')
474 475 _bool_setting(settings, 'vcs.start_server', 'false')
475 476 _list_setting(settings, 'vcs.backends', 'hg, git, svn')
476 477 _int_setting(settings, 'vcs.connection_timeout', 3600)
477 478
478 479 # Support legacy values of vcs.scm_app_implementation. Legacy
479 480 # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http'
480 481 # which is now mapped to 'http'.
481 482 scm_app_impl = settings['vcs.scm_app_implementation']
482 483 if scm_app_impl == 'rhodecode.lib.middleware.utils.scm_app_http':
483 484 settings['vcs.scm_app_implementation'] = 'http'
484 485
485 486
486 487 def _int_setting(settings, name, default):
487 488 settings[name] = int(settings.get(name, default))
488 489
489 490
490 491 def _bool_setting(settings, name, default):
491 492 input = settings.get(name, default)
492 493 if isinstance(input, unicode):
493 494 input = input.encode('utf8')
494 495 settings[name] = asbool(input)
495 496
496 497
497 498 def _list_setting(settings, name, default):
498 499 raw_value = settings.get(name, default)
499 500
500 501 old_separator = ','
501 502 if old_separator in raw_value:
502 503 # If we get a comma separated list, pass it to our own function.
503 504 settings[name] = rhodecode_aslist(raw_value, sep=old_separator)
504 505 else:
505 506 # Otherwise we assume it uses pyramids space/newline separation.
506 507 settings[name] = aslist(raw_value)
507 508
508 509
509 510 def _string_setting(settings, name, default, lower=True):
510 511 value = settings.get(name, default)
511 512 if lower:
512 513 value = value.lower()
513 514 settings[name] = value
@@ -1,1125 +1,1116 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Routes configuration
23 23
24 24 The more specific and detailed routes should be defined first so they
25 25 may take precedent over the more generic routes. For more information
26 26 refer to the routes manual at http://routes.groovie.org/docs/
27 27
28 28 IMPORTANT: if you change any routing here, make sure to take a look at lib/base.py
29 29 and _route_name variable which uses some of stored naming here to do redirects.
30 30 """
31 31 import os
32 32 import re
33 33 from routes import Mapper
34 34
35 35 # prefix for non repository related links needs to be prefixed with `/`
36 36 ADMIN_PREFIX = '/_admin'
37 37 STATIC_FILE_PREFIX = '/_static'
38 38
39 39 # Default requirements for URL parts
40 40 URL_NAME_REQUIREMENTS = {
41 41 # group name can have a slash in them, but they must not end with a slash
42 42 'group_name': r'.*?[^/]',
43 43 'repo_group_name': r'.*?[^/]',
44 44 # repo names can have a slash in them, but they must not end with a slash
45 45 'repo_name': r'.*?[^/]',
46 46 # file path eats up everything at the end
47 47 'f_path': r'.*',
48 48 # reference types
49 49 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
50 50 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
51 51 }
52 52
53 53
54 54 def add_route_requirements(route_path, requirements):
55 55 """
56 56 Adds regex requirements to pyramid routes using a mapping dict
57 57
58 58 >>> add_route_requirements('/{action}/{id}', {'id': r'\d+'})
59 59 '/{action}/{id:\d+}'
60 60
61 61 """
62 62 for key, regex in requirements.items():
63 63 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
64 64 return route_path
65 65
66 66
67 67 class JSRoutesMapper(Mapper):
68 68 """
69 69 Wrapper for routes.Mapper to make pyroutes compatible url definitions
70 70 """
71 71 _named_route_regex = re.compile(r'^[a-z-_0-9A-Z]+$')
72 72 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
73 73 def __init__(self, *args, **kw):
74 74 super(JSRoutesMapper, self).__init__(*args, **kw)
75 75 self._jsroutes = []
76 76
77 77 def connect(self, *args, **kw):
78 78 """
79 79 Wrapper for connect to take an extra argument jsroute=True
80 80
81 81 :param jsroute: boolean, if True will add the route to the pyroutes list
82 82 """
83 83 if kw.pop('jsroute', False):
84 84 if not self._named_route_regex.match(args[0]):
85 85 raise Exception('only named routes can be added to pyroutes')
86 86 self._jsroutes.append(args[0])
87 87
88 88 super(JSRoutesMapper, self).connect(*args, **kw)
89 89
90 90 def _extract_route_information(self, route):
91 91 """
92 92 Convert a route into tuple(name, path, args), eg:
93 93 ('show_user', '/profile/%(username)s', ['username'])
94 94 """
95 95 routepath = route.routepath
96 96 def replace(matchobj):
97 97 if matchobj.group(1):
98 98 return "%%(%s)s" % matchobj.group(1).split(':')[0]
99 99 else:
100 100 return "%%(%s)s" % matchobj.group(2)
101 101
102 102 routepath = self._argument_prog.sub(replace, routepath)
103 103 return (
104 104 route.name,
105 105 routepath,
106 106 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
107 107 for arg in self._argument_prog.findall(route.routepath)]
108 108 )
109 109
110 110 def jsroutes(self):
111 111 """
112 112 Return a list of pyroutes.js compatible routes
113 113 """
114 114 for route_name in self._jsroutes:
115 115 yield self._extract_route_information(self._routenames[route_name])
116 116
117 117
118 118 def make_map(config):
119 119 """Create, configure and return the routes Mapper"""
120 120 rmap = JSRoutesMapper(directory=config['pylons.paths']['controllers'],
121 121 always_scan=config['debug'])
122 122 rmap.minimization = False
123 123 rmap.explicit = False
124 124
125 125 from rhodecode.lib.utils2 import str2bool
126 126 from rhodecode.model import repo, repo_group
127 127
128 128 def check_repo(environ, match_dict):
129 129 """
130 130 check for valid repository for proper 404 handling
131 131
132 132 :param environ:
133 133 :param match_dict:
134 134 """
135 135 repo_name = match_dict.get('repo_name')
136 136
137 137 if match_dict.get('f_path'):
138 138 # fix for multiple initial slashes that causes errors
139 139 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
140 140 repo_model = repo.RepoModel()
141 141 by_name_match = repo_model.get_by_repo_name(repo_name)
142 142 # if we match quickly from database, short circuit the operation,
143 143 # and validate repo based on the type.
144 144 if by_name_match:
145 145 return True
146 146
147 147 by_id_match = repo_model.get_repo_by_id(repo_name)
148 148 if by_id_match:
149 149 repo_name = by_id_match.repo_name
150 150 match_dict['repo_name'] = repo_name
151 151 return True
152 152
153 153 return False
154 154
155 155 def check_group(environ, match_dict):
156 156 """
157 157 check for valid repository group path for proper 404 handling
158 158
159 159 :param environ:
160 160 :param match_dict:
161 161 """
162 162 repo_group_name = match_dict.get('group_name')
163 163 repo_group_model = repo_group.RepoGroupModel()
164 164 by_name_match = repo_group_model.get_by_group_name(repo_group_name)
165 165 if by_name_match:
166 166 return True
167 167
168 168 return False
169 169
170 170 def check_user_group(environ, match_dict):
171 171 """
172 172 check for valid user group for proper 404 handling
173 173
174 174 :param environ:
175 175 :param match_dict:
176 176 """
177 177 return True
178 178
179 179 def check_int(environ, match_dict):
180 180 return match_dict.get('id').isdigit()
181 181
182 182
183 183 #==========================================================================
184 184 # CUSTOM ROUTES HERE
185 185 #==========================================================================
186 186
187 187 # MAIN PAGE
188 188 rmap.connect('home', '/', controller='home', action='index', jsroute=True)
189 189
190 190 # ping and pylons error test
191 191 rmap.connect('ping', '%s/ping' % (ADMIN_PREFIX,), controller='home', action='ping')
192 192 rmap.connect('error_test', '%s/error_test' % (ADMIN_PREFIX,), controller='home', action='error_test')
193 193
194 194 # ADMIN REPOSITORY ROUTES
195 195 with rmap.submapper(path_prefix=ADMIN_PREFIX,
196 196 controller='admin/repos') as m:
197 197 m.connect('repos', '/repos',
198 198 action='create', conditions={'method': ['POST']})
199 199 m.connect('repos', '/repos',
200 200 action='index', conditions={'method': ['GET']})
201 201 m.connect('new_repo', '/create_repository', jsroute=True,
202 202 action='create_repository', conditions={'method': ['GET']})
203 203 m.connect('/repos/{repo_name}',
204 204 action='update', conditions={'method': ['PUT'],
205 205 'function': check_repo},
206 206 requirements=URL_NAME_REQUIREMENTS)
207 207 m.connect('delete_repo', '/repos/{repo_name}',
208 208 action='delete', conditions={'method': ['DELETE']},
209 209 requirements=URL_NAME_REQUIREMENTS)
210 210 m.connect('repo', '/repos/{repo_name}',
211 211 action='show', conditions={'method': ['GET'],
212 212 'function': check_repo},
213 213 requirements=URL_NAME_REQUIREMENTS)
214 214
215 215 # ADMIN REPOSITORY GROUPS ROUTES
216 216 with rmap.submapper(path_prefix=ADMIN_PREFIX,
217 217 controller='admin/repo_groups') as m:
218 218 m.connect('repo_groups', '/repo_groups',
219 219 action='create', conditions={'method': ['POST']})
220 220 m.connect('repo_groups', '/repo_groups',
221 221 action='index', conditions={'method': ['GET']})
222 222 m.connect('new_repo_group', '/repo_groups/new',
223 223 action='new', conditions={'method': ['GET']})
224 224 m.connect('update_repo_group', '/repo_groups/{group_name}',
225 225 action='update', conditions={'method': ['PUT'],
226 226 'function': check_group},
227 227 requirements=URL_NAME_REQUIREMENTS)
228 228
229 229 # EXTRAS REPO GROUP ROUTES
230 230 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
231 231 action='edit',
232 232 conditions={'method': ['GET'], 'function': check_group},
233 233 requirements=URL_NAME_REQUIREMENTS)
234 234 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
235 235 action='edit',
236 236 conditions={'method': ['PUT'], 'function': check_group},
237 237 requirements=URL_NAME_REQUIREMENTS)
238 238
239 239 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
240 240 action='edit_repo_group_advanced',
241 241 conditions={'method': ['GET'], 'function': check_group},
242 242 requirements=URL_NAME_REQUIREMENTS)
243 243 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
244 244 action='edit_repo_group_advanced',
245 245 conditions={'method': ['PUT'], 'function': check_group},
246 246 requirements=URL_NAME_REQUIREMENTS)
247 247
248 248 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
249 249 action='edit_repo_group_perms',
250 250 conditions={'method': ['GET'], 'function': check_group},
251 251 requirements=URL_NAME_REQUIREMENTS)
252 252 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
253 253 action='update_perms',
254 254 conditions={'method': ['PUT'], 'function': check_group},
255 255 requirements=URL_NAME_REQUIREMENTS)
256 256
257 257 m.connect('delete_repo_group', '/repo_groups/{group_name}',
258 258 action='delete', conditions={'method': ['DELETE'],
259 259 'function': check_group},
260 260 requirements=URL_NAME_REQUIREMENTS)
261 261
262 262 # ADMIN USER ROUTES
263 263 with rmap.submapper(path_prefix=ADMIN_PREFIX,
264 264 controller='admin/users') as m:
265 265 m.connect('users', '/users',
266 266 action='create', conditions={'method': ['POST']})
267 267 m.connect('new_user', '/users/new',
268 268 action='new', conditions={'method': ['GET']})
269 269 m.connect('update_user', '/users/{user_id}',
270 270 action='update', conditions={'method': ['PUT']})
271 271 m.connect('delete_user', '/users/{user_id}',
272 272 action='delete', conditions={'method': ['DELETE']})
273 273 m.connect('edit_user', '/users/{user_id}/edit',
274 274 action='edit', conditions={'method': ['GET']}, jsroute=True)
275 275 m.connect('user', '/users/{user_id}',
276 276 action='show', conditions={'method': ['GET']})
277 277 m.connect('force_password_reset_user', '/users/{user_id}/password_reset',
278 278 action='reset_password', conditions={'method': ['POST']})
279 279 m.connect('create_personal_repo_group', '/users/{user_id}/create_repo_group',
280 280 action='create_personal_repo_group', conditions={'method': ['POST']})
281 281
282 282 # EXTRAS USER ROUTES
283 283 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
284 284 action='edit_advanced', conditions={'method': ['GET']})
285 285 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
286 286 action='update_advanced', conditions={'method': ['PUT']})
287 287
288 288 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
289 289 action='edit_global_perms', conditions={'method': ['GET']})
290 290 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
291 291 action='update_global_perms', conditions={'method': ['PUT']})
292 292
293 293 m.connect('edit_user_perms_summary', '/users/{user_id}/edit/permissions_summary',
294 294 action='edit_perms_summary', conditions={'method': ['GET']})
295 295
296 296 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
297 297 action='edit_emails', conditions={'method': ['GET']})
298 298 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
299 299 action='add_email', conditions={'method': ['PUT']})
300 300 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
301 301 action='delete_email', conditions={'method': ['DELETE']})
302 302
303 303 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
304 304 action='edit_ips', conditions={'method': ['GET']})
305 305 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
306 306 action='add_ip', conditions={'method': ['PUT']})
307 307 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
308 308 action='delete_ip', conditions={'method': ['DELETE']})
309 309
310 310 # ADMIN USER GROUPS REST ROUTES
311 311 with rmap.submapper(path_prefix=ADMIN_PREFIX,
312 312 controller='admin/user_groups') as m:
313 313 m.connect('users_groups', '/user_groups',
314 314 action='create', conditions={'method': ['POST']})
315 315 m.connect('users_groups', '/user_groups',
316 316 action='index', conditions={'method': ['GET']})
317 317 m.connect('new_users_group', '/user_groups/new',
318 318 action='new', conditions={'method': ['GET']})
319 319 m.connect('update_users_group', '/user_groups/{user_group_id}',
320 320 action='update', conditions={'method': ['PUT']})
321 321 m.connect('delete_users_group', '/user_groups/{user_group_id}',
322 322 action='delete', conditions={'method': ['DELETE']})
323 323 m.connect('edit_users_group', '/user_groups/{user_group_id}/edit',
324 324 action='edit', conditions={'method': ['GET']},
325 325 function=check_user_group)
326 326
327 327 # EXTRAS USER GROUP ROUTES
328 328 m.connect('edit_user_group_global_perms',
329 329 '/user_groups/{user_group_id}/edit/global_permissions',
330 330 action='edit_global_perms', conditions={'method': ['GET']})
331 331 m.connect('edit_user_group_global_perms',
332 332 '/user_groups/{user_group_id}/edit/global_permissions',
333 333 action='update_global_perms', conditions={'method': ['PUT']})
334 334 m.connect('edit_user_group_perms_summary',
335 335 '/user_groups/{user_group_id}/edit/permissions_summary',
336 336 action='edit_perms_summary', conditions={'method': ['GET']})
337 337
338 338 m.connect('edit_user_group_perms',
339 339 '/user_groups/{user_group_id}/edit/permissions',
340 340 action='edit_perms', conditions={'method': ['GET']})
341 341 m.connect('edit_user_group_perms',
342 342 '/user_groups/{user_group_id}/edit/permissions',
343 343 action='update_perms', conditions={'method': ['PUT']})
344 344
345 345 m.connect('edit_user_group_advanced',
346 346 '/user_groups/{user_group_id}/edit/advanced',
347 347 action='edit_advanced', conditions={'method': ['GET']})
348 348
349 349 m.connect('edit_user_group_advanced_sync',
350 350 '/user_groups/{user_group_id}/edit/advanced/sync',
351 351 action='edit_advanced_set_synchronization', conditions={'method': ['POST']})
352 352
353 353 m.connect('edit_user_group_members',
354 354 '/user_groups/{user_group_id}/edit/members', jsroute=True,
355 355 action='user_group_members', conditions={'method': ['GET']})
356 356
357 357 # ADMIN PERMISSIONS ROUTES
358 358 with rmap.submapper(path_prefix=ADMIN_PREFIX,
359 359 controller='admin/permissions') as m:
360 360 m.connect('admin_permissions_application', '/permissions/application',
361 361 action='permission_application_update', conditions={'method': ['POST']})
362 362 m.connect('admin_permissions_application', '/permissions/application',
363 363 action='permission_application', conditions={'method': ['GET']})
364 364
365 365 m.connect('admin_permissions_global', '/permissions/global',
366 366 action='permission_global_update', conditions={'method': ['POST']})
367 367 m.connect('admin_permissions_global', '/permissions/global',
368 368 action='permission_global', conditions={'method': ['GET']})
369 369
370 370 m.connect('admin_permissions_object', '/permissions/object',
371 371 action='permission_objects_update', conditions={'method': ['POST']})
372 372 m.connect('admin_permissions_object', '/permissions/object',
373 373 action='permission_objects', conditions={'method': ['GET']})
374 374
375 375 m.connect('admin_permissions_ips', '/permissions/ips',
376 376 action='permission_ips', conditions={'method': ['POST']})
377 377 m.connect('admin_permissions_ips', '/permissions/ips',
378 378 action='permission_ips', conditions={'method': ['GET']})
379 379
380 380 m.connect('admin_permissions_overview', '/permissions/overview',
381 381 action='permission_perms', conditions={'method': ['GET']})
382 382
383 383 # ADMIN DEFAULTS REST ROUTES
384 384 with rmap.submapper(path_prefix=ADMIN_PREFIX,
385 385 controller='admin/defaults') as m:
386 386 m.connect('admin_defaults_repositories', '/defaults/repositories',
387 387 action='update_repository_defaults', conditions={'method': ['POST']})
388 388 m.connect('admin_defaults_repositories', '/defaults/repositories',
389 389 action='index', conditions={'method': ['GET']})
390 390
391 391 # ADMIN DEBUG STYLE ROUTES
392 392 if str2bool(config.get('debug_style')):
393 393 with rmap.submapper(path_prefix=ADMIN_PREFIX + '/debug_style',
394 394 controller='debug_style') as m:
395 395 m.connect('debug_style_home', '',
396 396 action='index', conditions={'method': ['GET']})
397 397 m.connect('debug_style_template', '/t/{t_path}',
398 398 action='template', conditions={'method': ['GET']})
399 399
400 400 # ADMIN SETTINGS ROUTES
401 401 with rmap.submapper(path_prefix=ADMIN_PREFIX,
402 402 controller='admin/settings') as m:
403 403
404 404 # default
405 405 m.connect('admin_settings', '/settings',
406 406 action='settings_global_update',
407 407 conditions={'method': ['POST']})
408 408 m.connect('admin_settings', '/settings',
409 409 action='settings_global', conditions={'method': ['GET']})
410 410
411 411 m.connect('admin_settings_vcs', '/settings/vcs',
412 412 action='settings_vcs_update',
413 413 conditions={'method': ['POST']})
414 414 m.connect('admin_settings_vcs', '/settings/vcs',
415 415 action='settings_vcs',
416 416 conditions={'method': ['GET']})
417 417 m.connect('admin_settings_vcs', '/settings/vcs',
418 418 action='delete_svn_pattern',
419 419 conditions={'method': ['DELETE']})
420 420
421 421 m.connect('admin_settings_mapping', '/settings/mapping',
422 422 action='settings_mapping_update',
423 423 conditions={'method': ['POST']})
424 424 m.connect('admin_settings_mapping', '/settings/mapping',
425 425 action='settings_mapping', conditions={'method': ['GET']})
426 426
427 427 m.connect('admin_settings_global', '/settings/global',
428 428 action='settings_global_update',
429 429 conditions={'method': ['POST']})
430 430 m.connect('admin_settings_global', '/settings/global',
431 431 action='settings_global', conditions={'method': ['GET']})
432 432
433 433 m.connect('admin_settings_visual', '/settings/visual',
434 434 action='settings_visual_update',
435 435 conditions={'method': ['POST']})
436 436 m.connect('admin_settings_visual', '/settings/visual',
437 437 action='settings_visual', conditions={'method': ['GET']})
438 438
439 439 m.connect('admin_settings_issuetracker',
440 440 '/settings/issue-tracker', action='settings_issuetracker',
441 441 conditions={'method': ['GET']})
442 442 m.connect('admin_settings_issuetracker_save',
443 443 '/settings/issue-tracker/save',
444 444 action='settings_issuetracker_save',
445 445 conditions={'method': ['POST']})
446 446 m.connect('admin_issuetracker_test', '/settings/issue-tracker/test',
447 447 action='settings_issuetracker_test',
448 448 conditions={'method': ['POST']})
449 449 m.connect('admin_issuetracker_delete',
450 450 '/settings/issue-tracker/delete',
451 451 action='settings_issuetracker_delete',
452 452 conditions={'method': ['DELETE']})
453 453
454 454 m.connect('admin_settings_email', '/settings/email',
455 455 action='settings_email_update',
456 456 conditions={'method': ['POST']})
457 457 m.connect('admin_settings_email', '/settings/email',
458 458 action='settings_email', conditions={'method': ['GET']})
459 459
460 460 m.connect('admin_settings_hooks', '/settings/hooks',
461 461 action='settings_hooks_update',
462 462 conditions={'method': ['POST', 'DELETE']})
463 463 m.connect('admin_settings_hooks', '/settings/hooks',
464 464 action='settings_hooks', conditions={'method': ['GET']})
465 465
466 466 m.connect('admin_settings_search', '/settings/search',
467 467 action='settings_search', conditions={'method': ['GET']})
468 468
469 469 m.connect('admin_settings_supervisor', '/settings/supervisor',
470 470 action='settings_supervisor', conditions={'method': ['GET']})
471 471 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
472 472 action='settings_supervisor_log', conditions={'method': ['GET']})
473 473
474 474 m.connect('admin_settings_labs', '/settings/labs',
475 475 action='settings_labs_update',
476 476 conditions={'method': ['POST']})
477 477 m.connect('admin_settings_labs', '/settings/labs',
478 478 action='settings_labs', conditions={'method': ['GET']})
479 479
480 480 # ADMIN MY ACCOUNT
481 481 with rmap.submapper(path_prefix=ADMIN_PREFIX,
482 482 controller='admin/my_account') as m:
483 483
484 484 m.connect('my_account_edit', '/my_account/edit',
485 485 action='my_account_edit', conditions={'method': ['GET']})
486 486 m.connect('my_account', '/my_account/update',
487 487 action='my_account_update', conditions={'method': ['POST']})
488 488
489 489 # NOTE(marcink): this needs to be kept for password force flag to be
490 490 # handler, remove after migration to pyramid
491 491 m.connect('my_account_password', '/my_account/password',
492 492 action='my_account_password', conditions={'method': ['GET']})
493 493
494 494 m.connect('my_account_repos', '/my_account/repos',
495 495 action='my_account_repos', conditions={'method': ['GET']})
496 496
497 497 m.connect('my_account_watched', '/my_account/watched',
498 498 action='my_account_watched', conditions={'method': ['GET']})
499 499
500 500 m.connect('my_account_pullrequests', '/my_account/pull_requests',
501 501 action='my_account_pullrequests', conditions={'method': ['GET']})
502 502
503 503 m.connect('my_account_perms', '/my_account/perms',
504 504 action='my_account_perms', conditions={'method': ['GET']})
505 505
506 506 m.connect('my_account_emails', '/my_account/emails',
507 507 action='my_account_emails', conditions={'method': ['GET']})
508 508 m.connect('my_account_emails', '/my_account/emails',
509 509 action='my_account_emails_add', conditions={'method': ['POST']})
510 510 m.connect('my_account_emails', '/my_account/emails',
511 511 action='my_account_emails_delete', conditions={'method': ['DELETE']})
512 512
513 513 m.connect('my_account_notifications', '/my_account/notifications',
514 514 action='my_notifications',
515 515 conditions={'method': ['GET']})
516 516 m.connect('my_account_notifications_toggle_visibility',
517 517 '/my_account/toggle_visibility',
518 518 action='my_notifications_toggle_visibility',
519 519 conditions={'method': ['POST']})
520 520 m.connect('my_account_notifications_test_channelstream',
521 521 '/my_account/test_channelstream',
522 522 action='my_account_notifications_test_channelstream',
523 523 conditions={'method': ['POST']})
524 524
525 525 # NOTIFICATION REST ROUTES
526 526 with rmap.submapper(path_prefix=ADMIN_PREFIX,
527 527 controller='admin/notifications') as m:
528 528 m.connect('notifications', '/notifications',
529 529 action='index', conditions={'method': ['GET']})
530 530 m.connect('notifications_mark_all_read', '/notifications/mark_all_read',
531 531 action='mark_all_read', conditions={'method': ['POST']})
532 532 m.connect('/notifications/{notification_id}',
533 533 action='update', conditions={'method': ['PUT']})
534 534 m.connect('/notifications/{notification_id}',
535 535 action='delete', conditions={'method': ['DELETE']})
536 536 m.connect('notification', '/notifications/{notification_id}',
537 537 action='show', conditions={'method': ['GET']})
538 538
539 539 # ADMIN GIST
540 540 with rmap.submapper(path_prefix=ADMIN_PREFIX,
541 541 controller='admin/gists') as m:
542 542 m.connect('gists', '/gists',
543 543 action='create', conditions={'method': ['POST']})
544 544 m.connect('gists', '/gists', jsroute=True,
545 545 action='index', conditions={'method': ['GET']})
546 546 m.connect('new_gist', '/gists/new', jsroute=True,
547 547 action='new', conditions={'method': ['GET']})
548 548
549 549 m.connect('/gists/{gist_id}',
550 550 action='delete', conditions={'method': ['DELETE']})
551 551 m.connect('edit_gist', '/gists/{gist_id}/edit',
552 552 action='edit_form', conditions={'method': ['GET']})
553 553 m.connect('edit_gist', '/gists/{gist_id}/edit',
554 554 action='edit', conditions={'method': ['POST']})
555 555 m.connect(
556 556 'edit_gist_check_revision', '/gists/{gist_id}/edit/check_revision',
557 557 action='check_revision', conditions={'method': ['GET']})
558 558
559 559 m.connect('gist', '/gists/{gist_id}',
560 560 action='show', conditions={'method': ['GET']})
561 561 m.connect('gist_rev', '/gists/{gist_id}/{revision}',
562 562 revision='tip',
563 563 action='show', conditions={'method': ['GET']})
564 564 m.connect('formatted_gist', '/gists/{gist_id}/{revision}/{format}',
565 565 revision='tip',
566 566 action='show', conditions={'method': ['GET']})
567 567 m.connect('formatted_gist_file', '/gists/{gist_id}/{revision}/{format}/{f_path}',
568 568 revision='tip',
569 569 action='show', conditions={'method': ['GET']},
570 570 requirements=URL_NAME_REQUIREMENTS)
571 571
572 572 # ADMIN MAIN PAGES
573 573 with rmap.submapper(path_prefix=ADMIN_PREFIX,
574 574 controller='admin/admin') as m:
575 575 m.connect('admin_home', '', action='index')
576 576 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
577 577 action='add_repo')
578 578 m.connect(
579 579 'pull_requests_global_0', '/pull_requests/{pull_request_id:[0-9]+}',
580 580 action='pull_requests')
581 581 m.connect(
582 582 'pull_requests_global_1', '/pull-requests/{pull_request_id:[0-9]+}',
583 583 action='pull_requests')
584 584 m.connect(
585 585 'pull_requests_global', '/pull-request/{pull_request_id:[0-9]+}',
586 586 action='pull_requests')
587 587
588 588 # USER JOURNAL
589 589 rmap.connect('journal', '%s/journal' % (ADMIN_PREFIX,),
590 590 controller='journal', action='index')
591 591 rmap.connect('journal_rss', '%s/journal/rss' % (ADMIN_PREFIX,),
592 592 controller='journal', action='journal_rss')
593 593 rmap.connect('journal_atom', '%s/journal/atom' % (ADMIN_PREFIX,),
594 594 controller='journal', action='journal_atom')
595 595
596 596 rmap.connect('public_journal', '%s/public_journal' % (ADMIN_PREFIX,),
597 597 controller='journal', action='public_journal')
598 598
599 599 rmap.connect('public_journal_rss', '%s/public_journal/rss' % (ADMIN_PREFIX,),
600 600 controller='journal', action='public_journal_rss')
601 601
602 602 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % (ADMIN_PREFIX,),
603 603 controller='journal', action='public_journal_rss')
604 604
605 605 rmap.connect('public_journal_atom',
606 606 '%s/public_journal/atom' % (ADMIN_PREFIX,), controller='journal',
607 607 action='public_journal_atom')
608 608
609 609 rmap.connect('public_journal_atom_old',
610 610 '%s/public_journal_atom' % (ADMIN_PREFIX,), controller='journal',
611 611 action='public_journal_atom')
612 612
613 613 rmap.connect('toggle_following', '%s/toggle_following' % (ADMIN_PREFIX,),
614 614 controller='journal', action='toggle_following', jsroute=True,
615 615 conditions={'method': ['POST']})
616 616
617 # FULL TEXT SEARCH
618 rmap.connect('search', '%s/search' % (ADMIN_PREFIX,),
619 controller='search')
620 rmap.connect('search_repo_home', '/{repo_name}/search',
621 controller='search',
622 action='index',
623 conditions={'function': check_repo},
624 requirements=URL_NAME_REQUIREMENTS)
625
626 617 # FEEDS
627 618 rmap.connect('rss_feed_home', '/{repo_name}/feed/rss',
628 619 controller='feed', action='rss',
629 620 conditions={'function': check_repo},
630 621 requirements=URL_NAME_REQUIREMENTS)
631 622
632 623 rmap.connect('atom_feed_home', '/{repo_name}/feed/atom',
633 624 controller='feed', action='atom',
634 625 conditions={'function': check_repo},
635 626 requirements=URL_NAME_REQUIREMENTS)
636 627
637 628 #==========================================================================
638 629 # REPOSITORY ROUTES
639 630 #==========================================================================
640 631
641 632 rmap.connect('repo_creating_home', '/{repo_name}/repo_creating',
642 633 controller='admin/repos', action='repo_creating',
643 634 requirements=URL_NAME_REQUIREMENTS)
644 635 rmap.connect('repo_check_home', '/{repo_name}/crepo_check',
645 636 controller='admin/repos', action='repo_check',
646 637 requirements=URL_NAME_REQUIREMENTS)
647 638
648 639 rmap.connect('repo_stats', '/{repo_name}/repo_stats/{commit_id}',
649 640 controller='summary', action='repo_stats',
650 641 conditions={'function': check_repo},
651 642 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
652 643
653 644 rmap.connect('repo_refs_data', '/{repo_name}/refs-data',
654 645 controller='summary', action='repo_refs_data',
655 646 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
656 647 rmap.connect('repo_refs_changelog_data', '/{repo_name}/refs-data-changelog',
657 648 controller='summary', action='repo_refs_changelog_data',
658 649 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
659 650 rmap.connect('repo_default_reviewers_data', '/{repo_name}/default-reviewers',
660 651 controller='summary', action='repo_default_reviewers_data',
661 652 jsroute=True, requirements=URL_NAME_REQUIREMENTS)
662 653
663 654 rmap.connect('changeset_home', '/{repo_name}/changeset/{revision}',
664 655 controller='changeset', revision='tip',
665 656 conditions={'function': check_repo},
666 657 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
667 658 rmap.connect('changeset_children', '/{repo_name}/changeset_children/{revision}',
668 659 controller='changeset', revision='tip', action='changeset_children',
669 660 conditions={'function': check_repo},
670 661 requirements=URL_NAME_REQUIREMENTS)
671 662 rmap.connect('changeset_parents', '/{repo_name}/changeset_parents/{revision}',
672 663 controller='changeset', revision='tip', action='changeset_parents',
673 664 conditions={'function': check_repo},
674 665 requirements=URL_NAME_REQUIREMENTS)
675 666
676 667 # repo edit options
677 668 rmap.connect('edit_repo', '/{repo_name}/settings', jsroute=True,
678 669 controller='admin/repos', action='edit',
679 670 conditions={'method': ['GET'], 'function': check_repo},
680 671 requirements=URL_NAME_REQUIREMENTS)
681 672
682 673 rmap.connect('edit_repo_perms', '/{repo_name}/settings/permissions',
683 674 jsroute=True,
684 675 controller='admin/repos', action='edit_permissions',
685 676 conditions={'method': ['GET'], 'function': check_repo},
686 677 requirements=URL_NAME_REQUIREMENTS)
687 678 rmap.connect('edit_repo_perms_update', '/{repo_name}/settings/permissions',
688 679 controller='admin/repos', action='edit_permissions_update',
689 680 conditions={'method': ['PUT'], 'function': check_repo},
690 681 requirements=URL_NAME_REQUIREMENTS)
691 682
692 683 rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields',
693 684 controller='admin/repos', action='edit_fields',
694 685 conditions={'method': ['GET'], 'function': check_repo},
695 686 requirements=URL_NAME_REQUIREMENTS)
696 687 rmap.connect('create_repo_fields', '/{repo_name}/settings/fields/new',
697 688 controller='admin/repos', action='create_repo_field',
698 689 conditions={'method': ['PUT'], 'function': check_repo},
699 690 requirements=URL_NAME_REQUIREMENTS)
700 691 rmap.connect('delete_repo_fields', '/{repo_name}/settings/fields/{field_id}',
701 692 controller='admin/repos', action='delete_repo_field',
702 693 conditions={'method': ['DELETE'], 'function': check_repo},
703 694 requirements=URL_NAME_REQUIREMENTS)
704 695
705 696 rmap.connect('edit_repo_advanced', '/{repo_name}/settings/advanced',
706 697 controller='admin/repos', action='edit_advanced',
707 698 conditions={'method': ['GET'], 'function': check_repo},
708 699 requirements=URL_NAME_REQUIREMENTS)
709 700
710 701 rmap.connect('edit_repo_advanced_locking', '/{repo_name}/settings/advanced/locking',
711 702 controller='admin/repos', action='edit_advanced_locking',
712 703 conditions={'method': ['PUT'], 'function': check_repo},
713 704 requirements=URL_NAME_REQUIREMENTS)
714 705 rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle',
715 706 controller='admin/repos', action='toggle_locking',
716 707 conditions={'method': ['GET'], 'function': check_repo},
717 708 requirements=URL_NAME_REQUIREMENTS)
718 709
719 710 rmap.connect('edit_repo_advanced_journal', '/{repo_name}/settings/advanced/journal',
720 711 controller='admin/repos', action='edit_advanced_journal',
721 712 conditions={'method': ['PUT'], 'function': check_repo},
722 713 requirements=URL_NAME_REQUIREMENTS)
723 714
724 715 rmap.connect('edit_repo_advanced_fork', '/{repo_name}/settings/advanced/fork',
725 716 controller='admin/repos', action='edit_advanced_fork',
726 717 conditions={'method': ['PUT'], 'function': check_repo},
727 718 requirements=URL_NAME_REQUIREMENTS)
728 719
729 720 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
730 721 controller='admin/repos', action='edit_caches_form',
731 722 conditions={'method': ['GET'], 'function': check_repo},
732 723 requirements=URL_NAME_REQUIREMENTS)
733 724 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
734 725 controller='admin/repos', action='edit_caches',
735 726 conditions={'method': ['PUT'], 'function': check_repo},
736 727 requirements=URL_NAME_REQUIREMENTS)
737 728
738 729 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
739 730 controller='admin/repos', action='edit_remote_form',
740 731 conditions={'method': ['GET'], 'function': check_repo},
741 732 requirements=URL_NAME_REQUIREMENTS)
742 733 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
743 734 controller='admin/repos', action='edit_remote',
744 735 conditions={'method': ['PUT'], 'function': check_repo},
745 736 requirements=URL_NAME_REQUIREMENTS)
746 737
747 738 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
748 739 controller='admin/repos', action='edit_statistics_form',
749 740 conditions={'method': ['GET'], 'function': check_repo},
750 741 requirements=URL_NAME_REQUIREMENTS)
751 742 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
752 743 controller='admin/repos', action='edit_statistics',
753 744 conditions={'method': ['PUT'], 'function': check_repo},
754 745 requirements=URL_NAME_REQUIREMENTS)
755 746 rmap.connect('repo_settings_issuetracker',
756 747 '/{repo_name}/settings/issue-tracker',
757 748 controller='admin/repos', action='repo_issuetracker',
758 749 conditions={'method': ['GET'], 'function': check_repo},
759 750 requirements=URL_NAME_REQUIREMENTS)
760 751 rmap.connect('repo_issuetracker_test',
761 752 '/{repo_name}/settings/issue-tracker/test',
762 753 controller='admin/repos', action='repo_issuetracker_test',
763 754 conditions={'method': ['POST'], 'function': check_repo},
764 755 requirements=URL_NAME_REQUIREMENTS)
765 756 rmap.connect('repo_issuetracker_delete',
766 757 '/{repo_name}/settings/issue-tracker/delete',
767 758 controller='admin/repos', action='repo_issuetracker_delete',
768 759 conditions={'method': ['DELETE'], 'function': check_repo},
769 760 requirements=URL_NAME_REQUIREMENTS)
770 761 rmap.connect('repo_issuetracker_save',
771 762 '/{repo_name}/settings/issue-tracker/save',
772 763 controller='admin/repos', action='repo_issuetracker_save',
773 764 conditions={'method': ['POST'], 'function': check_repo},
774 765 requirements=URL_NAME_REQUIREMENTS)
775 766 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
776 767 controller='admin/repos', action='repo_settings_vcs_update',
777 768 conditions={'method': ['POST'], 'function': check_repo},
778 769 requirements=URL_NAME_REQUIREMENTS)
779 770 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
780 771 controller='admin/repos', action='repo_settings_vcs',
781 772 conditions={'method': ['GET'], 'function': check_repo},
782 773 requirements=URL_NAME_REQUIREMENTS)
783 774 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
784 775 controller='admin/repos', action='repo_delete_svn_pattern',
785 776 conditions={'method': ['DELETE'], 'function': check_repo},
786 777 requirements=URL_NAME_REQUIREMENTS)
787 778 rmap.connect('repo_pullrequest_settings', '/{repo_name}/settings/pullrequest',
788 779 controller='admin/repos', action='repo_settings_pullrequest',
789 780 conditions={'method': ['GET', 'POST'], 'function': check_repo},
790 781 requirements=URL_NAME_REQUIREMENTS)
791 782
792 783 # still working url for backward compat.
793 784 rmap.connect('raw_changeset_home_depraced',
794 785 '/{repo_name}/raw-changeset/{revision}',
795 786 controller='changeset', action='changeset_raw',
796 787 revision='tip', conditions={'function': check_repo},
797 788 requirements=URL_NAME_REQUIREMENTS)
798 789
799 790 # new URLs
800 791 rmap.connect('changeset_raw_home',
801 792 '/{repo_name}/changeset-diff/{revision}',
802 793 controller='changeset', action='changeset_raw',
803 794 revision='tip', conditions={'function': check_repo},
804 795 requirements=URL_NAME_REQUIREMENTS)
805 796
806 797 rmap.connect('changeset_patch_home',
807 798 '/{repo_name}/changeset-patch/{revision}',
808 799 controller='changeset', action='changeset_patch',
809 800 revision='tip', conditions={'function': check_repo},
810 801 requirements=URL_NAME_REQUIREMENTS)
811 802
812 803 rmap.connect('changeset_download_home',
813 804 '/{repo_name}/changeset-download/{revision}',
814 805 controller='changeset', action='changeset_download',
815 806 revision='tip', conditions={'function': check_repo},
816 807 requirements=URL_NAME_REQUIREMENTS)
817 808
818 809 rmap.connect('changeset_comment',
819 810 '/{repo_name}/changeset/{revision}/comment', jsroute=True,
820 811 controller='changeset', revision='tip', action='comment',
821 812 conditions={'function': check_repo},
822 813 requirements=URL_NAME_REQUIREMENTS)
823 814
824 815 rmap.connect('changeset_comment_preview',
825 816 '/{repo_name}/changeset/comment/preview', jsroute=True,
826 817 controller='changeset', action='preview_comment',
827 818 conditions={'function': check_repo, 'method': ['POST']},
828 819 requirements=URL_NAME_REQUIREMENTS)
829 820
830 821 rmap.connect('changeset_comment_delete',
831 822 '/{repo_name}/changeset/comment/{comment_id}/delete',
832 823 controller='changeset', action='delete_comment',
833 824 conditions={'function': check_repo, 'method': ['DELETE']},
834 825 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
835 826
836 827 rmap.connect('changeset_info', '/{repo_name}/changeset_info/{revision}',
837 828 controller='changeset', action='changeset_info',
838 829 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
839 830
840 831 rmap.connect('compare_home',
841 832 '/{repo_name}/compare',
842 833 controller='compare', action='index',
843 834 conditions={'function': check_repo},
844 835 requirements=URL_NAME_REQUIREMENTS)
845 836
846 837 rmap.connect('compare_url',
847 838 '/{repo_name}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}',
848 839 controller='compare', action='compare',
849 840 conditions={'function': check_repo},
850 841 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
851 842
852 843 rmap.connect('pullrequest_home',
853 844 '/{repo_name}/pull-request/new', controller='pullrequests',
854 845 action='index', conditions={'function': check_repo,
855 846 'method': ['GET']},
856 847 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
857 848
858 849 rmap.connect('pullrequest',
859 850 '/{repo_name}/pull-request/new', controller='pullrequests',
860 851 action='create', conditions={'function': check_repo,
861 852 'method': ['POST']},
862 853 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
863 854
864 855 rmap.connect('pullrequest_repo_refs',
865 856 '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
866 857 controller='pullrequests',
867 858 action='get_repo_refs',
868 859 conditions={'function': check_repo, 'method': ['GET']},
869 860 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
870 861
871 862 rmap.connect('pullrequest_repo_destinations',
872 863 '/{repo_name}/pull-request/repo-destinations',
873 864 controller='pullrequests',
874 865 action='get_repo_destinations',
875 866 conditions={'function': check_repo, 'method': ['GET']},
876 867 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
877 868
878 869 rmap.connect('pullrequest_show',
879 870 '/{repo_name}/pull-request/{pull_request_id}',
880 871 controller='pullrequests',
881 872 action='show', conditions={'function': check_repo,
882 873 'method': ['GET']},
883 874 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
884 875
885 876 rmap.connect('pullrequest_update',
886 877 '/{repo_name}/pull-request/{pull_request_id}',
887 878 controller='pullrequests',
888 879 action='update', conditions={'function': check_repo,
889 880 'method': ['PUT']},
890 881 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
891 882
892 883 rmap.connect('pullrequest_merge',
893 884 '/{repo_name}/pull-request/{pull_request_id}',
894 885 controller='pullrequests',
895 886 action='merge', conditions={'function': check_repo,
896 887 'method': ['POST']},
897 888 requirements=URL_NAME_REQUIREMENTS)
898 889
899 890 rmap.connect('pullrequest_delete',
900 891 '/{repo_name}/pull-request/{pull_request_id}',
901 892 controller='pullrequests',
902 893 action='delete', conditions={'function': check_repo,
903 894 'method': ['DELETE']},
904 895 requirements=URL_NAME_REQUIREMENTS)
905 896
906 897 rmap.connect('pullrequest_show_all',
907 898 '/{repo_name}/pull-request',
908 899 controller='pullrequests',
909 900 action='show_all', conditions={'function': check_repo,
910 901 'method': ['GET']},
911 902 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
912 903
913 904 rmap.connect('pullrequest_comment',
914 905 '/{repo_name}/pull-request-comment/{pull_request_id}',
915 906 controller='pullrequests',
916 907 action='comment', conditions={'function': check_repo,
917 908 'method': ['POST']},
918 909 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
919 910
920 911 rmap.connect('pullrequest_comment_delete',
921 912 '/{repo_name}/pull-request-comment/{comment_id}/delete',
922 913 controller='pullrequests', action='delete_comment',
923 914 conditions={'function': check_repo, 'method': ['DELETE']},
924 915 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
925 916
926 917 rmap.connect('summary_home_explicit', '/{repo_name}/summary',
927 918 controller='summary', conditions={'function': check_repo},
928 919 requirements=URL_NAME_REQUIREMENTS)
929 920
930 921 rmap.connect('branches_home', '/{repo_name}/branches',
931 922 controller='branches', conditions={'function': check_repo},
932 923 requirements=URL_NAME_REQUIREMENTS)
933 924
934 925 rmap.connect('tags_home', '/{repo_name}/tags',
935 926 controller='tags', conditions={'function': check_repo},
936 927 requirements=URL_NAME_REQUIREMENTS)
937 928
938 929 rmap.connect('bookmarks_home', '/{repo_name}/bookmarks',
939 930 controller='bookmarks', conditions={'function': check_repo},
940 931 requirements=URL_NAME_REQUIREMENTS)
941 932
942 933 rmap.connect('changelog_home', '/{repo_name}/changelog', jsroute=True,
943 934 controller='changelog', conditions={'function': check_repo},
944 935 requirements=URL_NAME_REQUIREMENTS)
945 936
946 937 rmap.connect('changelog_summary_home', '/{repo_name}/changelog_summary',
947 938 controller='changelog', action='changelog_summary',
948 939 conditions={'function': check_repo},
949 940 requirements=URL_NAME_REQUIREMENTS)
950 941
951 942 rmap.connect('changelog_file_home',
952 943 '/{repo_name}/changelog/{revision}/{f_path}',
953 944 controller='changelog', f_path=None,
954 945 conditions={'function': check_repo},
955 946 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
956 947
957 948 rmap.connect('changelog_elements', '/{repo_name}/changelog_details',
958 949 controller='changelog', action='changelog_elements',
959 950 conditions={'function': check_repo},
960 951 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
961 952
962 953 rmap.connect('files_home', '/{repo_name}/files/{revision}/{f_path}',
963 954 controller='files', revision='tip', f_path='',
964 955 conditions={'function': check_repo},
965 956 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
966 957
967 958 rmap.connect('files_home_simple_catchrev',
968 959 '/{repo_name}/files/{revision}',
969 960 controller='files', revision='tip', f_path='',
970 961 conditions={'function': check_repo},
971 962 requirements=URL_NAME_REQUIREMENTS)
972 963
973 964 rmap.connect('files_home_simple_catchall',
974 965 '/{repo_name}/files',
975 966 controller='files', revision='tip', f_path='',
976 967 conditions={'function': check_repo},
977 968 requirements=URL_NAME_REQUIREMENTS)
978 969
979 970 rmap.connect('files_history_home',
980 971 '/{repo_name}/history/{revision}/{f_path}',
981 972 controller='files', action='history', revision='tip', f_path='',
982 973 conditions={'function': check_repo},
983 974 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
984 975
985 976 rmap.connect('files_authors_home',
986 977 '/{repo_name}/authors/{revision}/{f_path}',
987 978 controller='files', action='authors', revision='tip', f_path='',
988 979 conditions={'function': check_repo},
989 980 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
990 981
991 982 rmap.connect('files_diff_home', '/{repo_name}/diff/{f_path}',
992 983 controller='files', action='diff', f_path='',
993 984 conditions={'function': check_repo},
994 985 requirements=URL_NAME_REQUIREMENTS)
995 986
996 987 rmap.connect('files_diff_2way_home',
997 988 '/{repo_name}/diff-2way/{f_path}',
998 989 controller='files', action='diff_2way', f_path='',
999 990 conditions={'function': check_repo},
1000 991 requirements=URL_NAME_REQUIREMENTS)
1001 992
1002 993 rmap.connect('files_rawfile_home',
1003 994 '/{repo_name}/rawfile/{revision}/{f_path}',
1004 995 controller='files', action='rawfile', revision='tip',
1005 996 f_path='', conditions={'function': check_repo},
1006 997 requirements=URL_NAME_REQUIREMENTS)
1007 998
1008 999 rmap.connect('files_raw_home',
1009 1000 '/{repo_name}/raw/{revision}/{f_path}',
1010 1001 controller='files', action='raw', revision='tip', f_path='',
1011 1002 conditions={'function': check_repo},
1012 1003 requirements=URL_NAME_REQUIREMENTS)
1013 1004
1014 1005 rmap.connect('files_render_home',
1015 1006 '/{repo_name}/render/{revision}/{f_path}',
1016 1007 controller='files', action='index', revision='tip', f_path='',
1017 1008 rendered=True, conditions={'function': check_repo},
1018 1009 requirements=URL_NAME_REQUIREMENTS)
1019 1010
1020 1011 rmap.connect('files_annotate_home',
1021 1012 '/{repo_name}/annotate/{revision}/{f_path}',
1022 1013 controller='files', action='index', revision='tip',
1023 1014 f_path='', annotate=True, conditions={'function': check_repo},
1024 1015 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1025 1016
1026 1017 rmap.connect('files_annotate_previous',
1027 1018 '/{repo_name}/annotate-previous/{revision}/{f_path}',
1028 1019 controller='files', action='annotate_previous', revision='tip',
1029 1020 f_path='', annotate=True, conditions={'function': check_repo},
1030 1021 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1031 1022
1032 1023 rmap.connect('files_edit',
1033 1024 '/{repo_name}/edit/{revision}/{f_path}',
1034 1025 controller='files', action='edit', revision='tip',
1035 1026 f_path='',
1036 1027 conditions={'function': check_repo, 'method': ['POST']},
1037 1028 requirements=URL_NAME_REQUIREMENTS)
1038 1029
1039 1030 rmap.connect('files_edit_home',
1040 1031 '/{repo_name}/edit/{revision}/{f_path}',
1041 1032 controller='files', action='edit_home', revision='tip',
1042 1033 f_path='', conditions={'function': check_repo},
1043 1034 requirements=URL_NAME_REQUIREMENTS)
1044 1035
1045 1036 rmap.connect('files_add',
1046 1037 '/{repo_name}/add/{revision}/{f_path}',
1047 1038 controller='files', action='add', revision='tip',
1048 1039 f_path='',
1049 1040 conditions={'function': check_repo, 'method': ['POST']},
1050 1041 requirements=URL_NAME_REQUIREMENTS)
1051 1042
1052 1043 rmap.connect('files_add_home',
1053 1044 '/{repo_name}/add/{revision}/{f_path}',
1054 1045 controller='files', action='add_home', revision='tip',
1055 1046 f_path='', conditions={'function': check_repo},
1056 1047 requirements=URL_NAME_REQUIREMENTS)
1057 1048
1058 1049 rmap.connect('files_delete',
1059 1050 '/{repo_name}/delete/{revision}/{f_path}',
1060 1051 controller='files', action='delete', revision='tip',
1061 1052 f_path='',
1062 1053 conditions={'function': check_repo, 'method': ['POST']},
1063 1054 requirements=URL_NAME_REQUIREMENTS)
1064 1055
1065 1056 rmap.connect('files_delete_home',
1066 1057 '/{repo_name}/delete/{revision}/{f_path}',
1067 1058 controller='files', action='delete_home', revision='tip',
1068 1059 f_path='', conditions={'function': check_repo},
1069 1060 requirements=URL_NAME_REQUIREMENTS)
1070 1061
1071 1062 rmap.connect('files_archive_home', '/{repo_name}/archive/{fname}',
1072 1063 controller='files', action='archivefile',
1073 1064 conditions={'function': check_repo},
1074 1065 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1075 1066
1076 1067 rmap.connect('files_nodelist_home',
1077 1068 '/{repo_name}/nodelist/{revision}/{f_path}',
1078 1069 controller='files', action='nodelist',
1079 1070 conditions={'function': check_repo},
1080 1071 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1081 1072
1082 1073 rmap.connect('files_nodetree_full',
1083 1074 '/{repo_name}/nodetree_full/{commit_id}/{f_path}',
1084 1075 controller='files', action='nodetree_full',
1085 1076 conditions={'function': check_repo},
1086 1077 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1087 1078
1088 1079 rmap.connect('repo_fork_create_home', '/{repo_name}/fork',
1089 1080 controller='forks', action='fork_create',
1090 1081 conditions={'function': check_repo, 'method': ['POST']},
1091 1082 requirements=URL_NAME_REQUIREMENTS)
1092 1083
1093 1084 rmap.connect('repo_fork_home', '/{repo_name}/fork',
1094 1085 controller='forks', action='fork',
1095 1086 conditions={'function': check_repo},
1096 1087 requirements=URL_NAME_REQUIREMENTS)
1097 1088
1098 1089 rmap.connect('repo_forks_home', '/{repo_name}/forks',
1099 1090 controller='forks', action='forks',
1100 1091 conditions={'function': check_repo},
1101 1092 requirements=URL_NAME_REQUIREMENTS)
1102 1093
1103 1094 # must be here for proper group/repo catching pattern
1104 1095 _connect_with_slash(
1105 1096 rmap, 'repo_group_home', '/{group_name}',
1106 1097 controller='home', action='index_repo_group',
1107 1098 conditions={'function': check_group},
1108 1099 requirements=URL_NAME_REQUIREMENTS)
1109 1100
1110 1101 # catch all, at the end
1111 1102 _connect_with_slash(
1112 1103 rmap, 'summary_home', '/{repo_name}', jsroute=True,
1113 1104 controller='summary', action='index',
1114 1105 conditions={'function': check_repo},
1115 1106 requirements=URL_NAME_REQUIREMENTS)
1116 1107
1117 1108 return rmap
1118 1109
1119 1110
1120 1111 def _connect_with_slash(mapper, name, path, *args, **kwargs):
1121 1112 """
1122 1113 Connect a route with an optional trailing slash in `path`.
1123 1114 """
1124 1115 mapper.connect(name + '_slash', path + '/', *args, **kwargs)
1125 1116 mapper.connect(name, path, *args, **kwargs)
@@ -1,601 +1,601 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="root.mako"/>
3 3
4 4 <div class="outerwrapper">
5 5 <!-- HEADER -->
6 6 <div class="header">
7 7 <div id="header-inner" class="wrapper">
8 8 <div id="logo">
9 9 <div class="logo-wrapper">
10 10 <a href="${h.url('home')}"><img src="${h.asset('images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
11 11 </div>
12 12 %if c.rhodecode_name:
13 13 <div class="branding">- ${h.branding(c.rhodecode_name)}</div>
14 14 %endif
15 15 </div>
16 16 <!-- MENU BAR NAV -->
17 17 ${self.menu_bar_nav()}
18 18 <!-- END MENU BAR NAV -->
19 19 </div>
20 20 </div>
21 21 ${self.menu_bar_subnav()}
22 22 <!-- END HEADER -->
23 23
24 24 <!-- CONTENT -->
25 25 <div id="content" class="wrapper">
26 26
27 27 <rhodecode-toast id="notifications"></rhodecode-toast>
28 28
29 29 <div class="main">
30 30 ${next.main()}
31 31 </div>
32 32 </div>
33 33 <!-- END CONTENT -->
34 34
35 35 </div>
36 36 <!-- FOOTER -->
37 37 <div id="footer">
38 38 <div id="footer-inner" class="title wrapper">
39 39 <div>
40 40 <p class="footer-link-right">
41 41 % if c.visual.show_version:
42 42 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
43 43 % endif
44 44 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
45 45 % if c.visual.rhodecode_support_url:
46 46 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
47 47 % endif
48 48 </p>
49 49 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
50 50 <p class="server-instance" style="display:${sid}">
51 51 ## display hidden instance ID if specially defined
52 52 % if c.rhodecode_instanceid:
53 53 ${_('RhodeCode instance id: %s') % c.rhodecode_instanceid}
54 54 % endif
55 55 </p>
56 56 </div>
57 57 </div>
58 58 </div>
59 59
60 60 <!-- END FOOTER -->
61 61
62 62 ### MAKO DEFS ###
63 63
64 64 <%def name="menu_bar_subnav()">
65 65 </%def>
66 66
67 67 <%def name="breadcrumbs(class_='breadcrumbs')">
68 68 <div class="${class_}">
69 69 ${self.breadcrumbs_links()}
70 70 </div>
71 71 </%def>
72 72
73 73 <%def name="admin_menu()">
74 74 <ul class="admin_menu submenu">
75 75 <li><a href="${h.url('admin_home')}">${_('Admin journal')}</a></li>
76 76 <li><a href="${h.url('repos')}">${_('Repositories')}</a></li>
77 77 <li><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
78 78 <li><a href="${h.route_path('users')}">${_('Users')}</a></li>
79 79 <li><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
80 80 <li><a href="${h.url('admin_permissions_application')}">${_('Permissions')}</a></li>
81 81 <li><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
82 82 <li><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
83 83 <li><a href="${h.url('admin_defaults_repositories')}">${_('Defaults')}</a></li>
84 84 <li class="last"><a href="${h.url('admin_settings')}">${_('Settings')}</a></li>
85 85 </ul>
86 86 </%def>
87 87
88 88
89 89 <%def name="dt_info_panel(elements)">
90 90 <dl class="dl-horizontal">
91 91 %for dt, dd, title, show_items in elements:
92 92 <dt>${dt}:</dt>
93 93 <dd title="${title}">
94 94 %if callable(dd):
95 95 ## allow lazy evaluation of elements
96 96 ${dd()}
97 97 %else:
98 98 ${dd}
99 99 %endif
100 100 %if show_items:
101 101 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
102 102 %endif
103 103 </dd>
104 104
105 105 %if show_items:
106 106 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
107 107 %for item in show_items:
108 108 <dt></dt>
109 109 <dd>${item}</dd>
110 110 %endfor
111 111 </div>
112 112 %endif
113 113
114 114 %endfor
115 115 </dl>
116 116 </%def>
117 117
118 118
119 119 <%def name="gravatar(email, size=16)">
120 120 <%
121 121 if (size > 16):
122 122 gravatar_class = 'gravatar gravatar-large'
123 123 else:
124 124 gravatar_class = 'gravatar'
125 125 %>
126 126 <%doc>
127 127 TODO: johbo: For now we serve double size images to make it smooth
128 128 for retina. This is how it worked until now. Should be replaced
129 129 with a better solution at some point.
130 130 </%doc>
131 131 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
132 132 </%def>
133 133
134 134
135 135 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
136 136 <% email = h.email_or_none(contact) %>
137 137 <div class="rc-user tooltip" title="${h.author_string(email)}">
138 138 ${self.gravatar(email, size)}
139 139 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
140 140 </div>
141 141 </%def>
142 142
143 143
144 144 ## admin menu used for people that have some admin resources
145 145 <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)">
146 146 <ul class="submenu">
147 147 %if repositories:
148 148 <li class="local-admin-repos"><a href="${h.url('repos')}">${_('Repositories')}</a></li>
149 149 %endif
150 150 %if repository_groups:
151 151 <li class="local-admin-repo-groups"><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
152 152 %endif
153 153 %if user_groups:
154 154 <li class="local-admin-user-groups"><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
155 155 %endif
156 156 </ul>
157 157 </%def>
158 158
159 159 <%def name="repo_page_title(repo_instance)">
160 160 <div class="title-content">
161 161 <div class="title-main">
162 162 ## SVN/HG/GIT icons
163 163 %if h.is_hg(repo_instance):
164 164 <i class="icon-hg"></i>
165 165 %endif
166 166 %if h.is_git(repo_instance):
167 167 <i class="icon-git"></i>
168 168 %endif
169 169 %if h.is_svn(repo_instance):
170 170 <i class="icon-svn"></i>
171 171 %endif
172 172
173 173 ## public/private
174 174 %if repo_instance.private:
175 175 <i class="icon-repo-private"></i>
176 176 %else:
177 177 <i class="icon-repo-public"></i>
178 178 %endif
179 179
180 180 ## repo name with group name
181 181 ${h.breadcrumb_repo_link(c.rhodecode_db_repo)}
182 182
183 183 </div>
184 184
185 185 ## FORKED
186 186 %if repo_instance.fork:
187 187 <p>
188 188 <i class="icon-code-fork"></i> ${_('Fork of')}
189 189 <a href="${h.url('summary_home',repo_name=repo_instance.fork.repo_name)}">${repo_instance.fork.repo_name}</a>
190 190 </p>
191 191 %endif
192 192
193 193 ## IMPORTED FROM REMOTE
194 194 %if repo_instance.clone_uri:
195 195 <p>
196 196 <i class="icon-code-fork"></i> ${_('Clone from')}
197 197 <a href="${h.url(h.safe_str(h.hide_credentials(repo_instance.clone_uri)))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
198 198 </p>
199 199 %endif
200 200
201 201 ## LOCKING STATUS
202 202 %if repo_instance.locked[0]:
203 203 <p class="locking_locked">
204 204 <i class="icon-repo-lock"></i>
205 205 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
206 206 </p>
207 207 %elif repo_instance.enable_locking:
208 208 <p class="locking_unlocked">
209 209 <i class="icon-repo-unlock"></i>
210 210 ${_('Repository not locked. Pull repository to lock it.')}
211 211 </p>
212 212 %endif
213 213
214 214 </div>
215 215 </%def>
216 216
217 217 <%def name="repo_menu(active=None)">
218 218 <%
219 219 def is_active(selected):
220 220 if selected == active:
221 221 return "active"
222 222 %>
223 223
224 224 <!--- CONTEXT BAR -->
225 225 <div id="context-bar">
226 226 <div class="wrapper">
227 227 <ul id="context-pages" class="horizontal-list navigation">
228 228 <li class="${is_active('summary')}"><a class="menulink" href="${h.url('summary_home', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
229 229 <li class="${is_active('changelog')}"><a class="menulink" href="${h.url('changelog_home', repo_name=c.repo_name)}"><div class="menulabel">${_('Changelog')}</div></a></li>
230 230 <li class="${is_active('files')}"><a class="menulink" href="${h.url('files_home', repo_name=c.repo_name, revision=c.rhodecode_db_repo.landing_rev[1])}"><div class="menulabel">${_('Files')}</div></a></li>
231 231 <li class="${is_active('compare')}">
232 232 <a class="menulink" href="${h.url('compare_home',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a>
233 233 </li>
234 234 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
235 235 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
236 236 <li class="${is_active('showpullrequest')}">
237 237 <a class="menulink" href="${h.url('pullrequest_show_all',repo_name=c.repo_name)}" title="${_('Show Pull Requests for %s') % c.repo_name}">
238 238 %if c.repository_pull_requests:
239 239 <span class="pr_notifications">${c.repository_pull_requests}</span>
240 240 %endif
241 241 <div class="menulabel">${_('Pull Requests')}</div>
242 242 </a>
243 243 </li>
244 244 %endif
245 245 <li class="${is_active('options')}">
246 246 <a class="menulink" href="#" class="dropdown"><div class="menulabel">${_('Options')} <div class="show_more"></div></div></a>
247 247 <ul class="submenu">
248 248 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
249 249 <li><a href="${h.url('edit_repo',repo_name=c.repo_name)}">${_('Settings')}</a></li>
250 250 %endif
251 251 %if c.rhodecode_db_repo.fork:
252 252 <li><a href="${h.url('compare_url',repo_name=c.rhodecode_db_repo.fork.repo_name,source_ref_type=c.rhodecode_db_repo.landing_rev[0],source_ref=c.rhodecode_db_repo.landing_rev[1], target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1], merge=1)}">
253 253 ${_('Compare fork')}</a></li>
254 254 %endif
255 255
256 <li><a href="${h.url('search_repo_home',repo_name=c.repo_name)}">${_('Search')}</a></li>
256 <li><a href="${h.route_path('search_repo',repo_name=c.repo_name)}">${_('Search')}</a></li>
257 257
258 258 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
259 259 %if c.rhodecode_db_repo.locked[0]:
260 260 <li><a class="locking_del" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Unlock')}</a></li>
261 261 %else:
262 262 <li><a class="locking_add" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Lock')}</a></li>
263 263 %endif
264 264 %endif
265 265 %if c.rhodecode_user.username != h.DEFAULT_USER:
266 266 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
267 267 <li><a href="${h.url('repo_fork_home',repo_name=c.repo_name)}">${_('Fork')}</a></li>
268 268 <li><a href="${h.url('pullrequest_home',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
269 269 %endif
270 270 %endif
271 271 </ul>
272 272 </li>
273 273 </ul>
274 274 </div>
275 275 <div class="clear"></div>
276 276 </div>
277 277 <!--- END CONTEXT BAR -->
278 278
279 279 </%def>
280 280
281 281 <%def name="usermenu(active=False)">
282 282 ## USER MENU
283 283 <li id="quick_login_li" class="${'active' if active else ''}">
284 284 <a id="quick_login_link" class="menulink childs">
285 285 ${gravatar(c.rhodecode_user.email, 20)}
286 286 <span class="user">
287 287 %if c.rhodecode_user.username != h.DEFAULT_USER:
288 288 <span class="menu_link_user">${c.rhodecode_user.username}</span><div class="show_more"></div>
289 289 %else:
290 290 <span>${_('Sign in')}</span>
291 291 %endif
292 292 </span>
293 293 </a>
294 294
295 295 <div class="user-menu submenu">
296 296 <div id="quick_login">
297 297 %if c.rhodecode_user.username == h.DEFAULT_USER:
298 298 <h4>${_('Sign in to your account')}</h4>
299 299 ${h.form(h.route_path('login', _query={'came_from': h.url.current()}), needs_csrf_token=False)}
300 300 <div class="form form-vertical">
301 301 <div class="fields">
302 302 <div class="field">
303 303 <div class="label">
304 304 <label for="username">${_('Username')}:</label>
305 305 </div>
306 306 <div class="input">
307 307 ${h.text('username',class_='focus',tabindex=1)}
308 308 </div>
309 309
310 310 </div>
311 311 <div class="field">
312 312 <div class="label">
313 313 <label for="password">${_('Password')}:</label>
314 314 %if h.HasPermissionAny('hg.password_reset.enabled')():
315 315 <span class="forgot_password">${h.link_to(_('(Forgot password?)'),h.route_path('reset_password'), class_='pwd_reset')}</span>
316 316 %endif
317 317 </div>
318 318 <div class="input">
319 319 ${h.password('password',class_='focus',tabindex=2)}
320 320 </div>
321 321 </div>
322 322 <div class="buttons">
323 323 <div class="register">
324 324 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
325 325 ${h.link_to(_("Don't have an account ?"),h.route_path('register'))}
326 326 %endif
327 327 </div>
328 328 <div class="submit">
329 329 ${h.submit('sign_in',_('Sign In'),class_="btn btn-small",tabindex=3)}
330 330 </div>
331 331 </div>
332 332 </div>
333 333 </div>
334 334 ${h.end_form()}
335 335 %else:
336 336 <div class="">
337 337 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
338 338 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
339 339 <div class="email">${c.rhodecode_user.email}</div>
340 340 </div>
341 341 <div class="">
342 342 <ol class="links">
343 343 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
344 344 % if c.rhodecode_user.personal_repo_group:
345 345 <li>${h.link_to(_(u'My personal group'), h.url('repo_group_home', group_name=c.rhodecode_user.personal_repo_group.group_name))}</li>
346 346 % endif
347 347 <li class="logout">
348 348 ${h.secure_form(h.route_path('logout'))}
349 349 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
350 350 ${h.end_form()}
351 351 </li>
352 352 </ol>
353 353 </div>
354 354 %endif
355 355 </div>
356 356 </div>
357 357 %if c.rhodecode_user.username != h.DEFAULT_USER:
358 358 <div class="pill_container">
359 359 % if c.unread_notifications == 0:
360 360 <a class="menu_link_notifications empty" href="${h.url('notifications')}">${c.unread_notifications}</a>
361 361 % else:
362 362 <a class="menu_link_notifications" href="${h.url('notifications')}">${c.unread_notifications}</a>
363 363 % endif
364 364 </div>
365 365 % endif
366 366 </li>
367 367 </%def>
368 368
369 369 <%def name="menu_items(active=None)">
370 370 <%
371 371 def is_active(selected):
372 372 if selected == active:
373 373 return "active"
374 374 return ""
375 375 %>
376 376 <ul id="quick" class="main_nav navigation horizontal-list">
377 377 <!-- repo switcher -->
378 378 <li class="${is_active('repositories')} repo_switcher_li has_select2">
379 379 <input id="repo_switcher" name="repo_switcher" type="hidden">
380 380 </li>
381 381
382 382 ## ROOT MENU
383 383 %if c.rhodecode_user.username != h.DEFAULT_USER:
384 384 <li class="${is_active('journal')}">
385 385 <a class="menulink" title="${_('Show activity journal')}" href="${h.url('journal')}">
386 386 <div class="menulabel">${_('Journal')}</div>
387 387 </a>
388 388 </li>
389 389 %else:
390 390 <li class="${is_active('journal')}">
391 391 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.url('public_journal')}">
392 392 <div class="menulabel">${_('Public journal')}</div>
393 393 </a>
394 394 </li>
395 395 %endif
396 396 <li class="${is_active('gists')}">
397 397 <a class="menulink childs" title="${_('Show Gists')}" href="${h.url('gists')}">
398 398 <div class="menulabel">${_('Gists')}</div>
399 399 </a>
400 400 </li>
401 401 <li class="${is_active('search')}">
402 <a class="menulink" title="${_('Search in repositories you have access to')}" href="${h.url('search')}">
402 <a class="menulink" title="${_('Search in repositories you have access to')}" href="${h.route_path('search')}">
403 403 <div class="menulabel">${_('Search')}</div>
404 404 </a>
405 405 </li>
406 406 % if h.HasPermissionAll('hg.admin')('access admin main page'):
407 407 <li class="${is_active('admin')}">
408 408 <a class="menulink childs" title="${_('Admin settings')}" href="#" onclick="return false;">
409 409 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
410 410 </a>
411 411 ${admin_menu()}
412 412 </li>
413 413 % elif c.rhodecode_user.repositories_admin or c.rhodecode_user.repository_groups_admin or c.rhodecode_user.user_groups_admin:
414 414 <li class="${is_active('admin')}">
415 415 <a class="menulink childs" title="${_('Delegated Admin settings')}">
416 416 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
417 417 </a>
418 418 ${admin_menu_simple(c.rhodecode_user.repositories_admin,
419 419 c.rhodecode_user.repository_groups_admin,
420 420 c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
421 421 </li>
422 422 % endif
423 423 % if c.debug_style:
424 424 <li class="${is_active('debug_style')}">
425 425 <a class="menulink" title="${_('Style')}" href="${h.url('debug_style_home')}">
426 426 <div class="menulabel">${_('Style')}</div>
427 427 </a>
428 428 </li>
429 429 % endif
430 430 ## render extra user menu
431 431 ${usermenu(active=(active=='my_account'))}
432 432 </ul>
433 433
434 434 <script type="text/javascript">
435 435 var visual_show_public_icon = "${c.visual.show_public_icon}" == "True";
436 436
437 437 /*format the look of items in the list*/
438 438 var format = function(state, escapeMarkup){
439 439 if (!state.id){
440 440 return state.text; // optgroup
441 441 }
442 442 var obj_dict = state.obj;
443 443 var tmpl = '';
444 444
445 445 if(obj_dict && state.type == 'repo'){
446 446 if(obj_dict['repo_type'] === 'hg'){
447 447 tmpl += '<i class="icon-hg"></i> ';
448 448 }
449 449 else if(obj_dict['repo_type'] === 'git'){
450 450 tmpl += '<i class="icon-git"></i> ';
451 451 }
452 452 else if(obj_dict['repo_type'] === 'svn'){
453 453 tmpl += '<i class="icon-svn"></i> ';
454 454 }
455 455 if(obj_dict['private']){
456 456 tmpl += '<i class="icon-lock" ></i> ';
457 457 }
458 458 else if(visual_show_public_icon){
459 459 tmpl += '<i class="icon-unlock-alt"></i> ';
460 460 }
461 461 }
462 462 if(obj_dict && state.type == 'commit') {
463 463 tmpl += '<i class="icon-tag"></i>';
464 464 }
465 465 if(obj_dict && state.type == 'group'){
466 466 tmpl += '<i class="icon-folder-close"></i> ';
467 467 }
468 468 tmpl += escapeMarkup(state.text);
469 469 return tmpl;
470 470 };
471 471
472 472 var formatResult = function(result, container, query, escapeMarkup) {
473 473 return format(result, escapeMarkup);
474 474 };
475 475
476 476 var formatSelection = function(data, container, escapeMarkup) {
477 477 return format(data, escapeMarkup);
478 478 };
479 479
480 480 $("#repo_switcher").select2({
481 481 cachedDataSource: {},
482 482 minimumInputLength: 2,
483 483 placeholder: '<div class="menulabel">${_('Go to')} <div class="show_more"></div></div>',
484 484 dropdownAutoWidth: true,
485 485 formatResult: formatResult,
486 486 formatSelection: formatSelection,
487 487 containerCssClass: "repo-switcher",
488 488 dropdownCssClass: "repo-switcher-dropdown",
489 489 escapeMarkup: function(m){
490 490 // don't escape our custom placeholder
491 491 if(m.substr(0,23) == '<div class="menulabel">'){
492 492 return m;
493 493 }
494 494
495 495 return Select2.util.escapeMarkup(m);
496 496 },
497 497 query: $.debounce(250, function(query){
498 498 self = this;
499 499 var cacheKey = query.term;
500 500 var cachedData = self.cachedDataSource[cacheKey];
501 501
502 502 if (cachedData) {
503 503 query.callback({results: cachedData.results});
504 504 } else {
505 505 $.ajax({
506 506 url: pyroutes.url('goto_switcher_data'),
507 507 data: {'query': query.term},
508 508 dataType: 'json',
509 509 type: 'GET',
510 510 success: function(data) {
511 511 self.cachedDataSource[cacheKey] = data;
512 512 query.callback({results: data.results});
513 513 },
514 514 error: function(data, textStatus, errorThrown) {
515 515 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
516 516 }
517 517 })
518 518 }
519 519 })
520 520 });
521 521
522 522 $("#repo_switcher").on('select2-selecting', function(e){
523 523 e.preventDefault();
524 524 window.location = e.choice.url;
525 525 });
526 526
527 527 </script>
528 528 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
529 529 </%def>
530 530
531 531 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
532 532 <div class="modal-dialog">
533 533 <div class="modal-content">
534 534 <div class="modal-header">
535 535 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
536 536 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
537 537 </div>
538 538 <div class="modal-body">
539 539 <div class="block-left">
540 540 <table class="keyboard-mappings">
541 541 <tbody>
542 542 <tr>
543 543 <th></th>
544 544 <th>${_('Site-wide shortcuts')}</th>
545 545 </tr>
546 546 <%
547 547 elems = [
548 548 ('/', 'Open quick search box'),
549 549 ('g h', 'Goto home page'),
550 550 ('g g', 'Goto my private gists page'),
551 551 ('g G', 'Goto my public gists page'),
552 552 ('n r', 'New repository page'),
553 553 ('n g', 'New gist page'),
554 554 ]
555 555 %>
556 556 %for key, desc in elems:
557 557 <tr>
558 558 <td class="keys">
559 559 <span class="key tag">${key}</span>
560 560 </td>
561 561 <td>${desc}</td>
562 562 </tr>
563 563 %endfor
564 564 </tbody>
565 565 </table>
566 566 </div>
567 567 <div class="block-left">
568 568 <table class="keyboard-mappings">
569 569 <tbody>
570 570 <tr>
571 571 <th></th>
572 572 <th>${_('Repositories')}</th>
573 573 </tr>
574 574 <%
575 575 elems = [
576 576 ('g s', 'Goto summary page'),
577 577 ('g c', 'Goto changelog page'),
578 578 ('g f', 'Goto files page'),
579 579 ('g F', 'Goto files page with file search activated'),
580 580 ('g p', 'Goto pull requests page'),
581 581 ('g o', 'Goto repository settings'),
582 582 ('g O', 'Goto repository permissions settings'),
583 583 ]
584 584 %>
585 585 %for key, desc in elems:
586 586 <tr>
587 587 <td class="keys">
588 588 <span class="key tag">${key}</span>
589 589 </td>
590 590 <td>${desc}</td>
591 591 </tr>
592 592 %endfor
593 593 </tbody>
594 594 </table>
595 595 </div>
596 596 </div>
597 597 <div class="modal-footer">
598 598 </div>
599 599 </div><!-- /.modal-content -->
600 600 </div><!-- /.modal-dialog -->
601 601 </div><!-- /.modal -->
@@ -1,108 +1,108 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3
4 4 <%def name="title()">
5 5 %if c.repo_name:
6 6 ${_('Search inside repository %(repo_name)s') % {'repo_name': c.repo_name}}
7 7 %else:
8 8 ${_('Search inside all accessible repositories')}
9 9 %endif
10 10 %if c.rhodecode_name:
11 11 &middot; ${h.branding(c.rhodecode_name)}
12 12 %endif
13 13 </%def>
14 14
15 15 <%def name="breadcrumbs_links()">
16 16 %if c.repo_name:
17 17 ${_('Search inside repository %(repo_name)s') % {'repo_name': c.repo_name}}
18 18 %else:
19 19 ${_('Search inside all accessible repositories')}
20 20 %endif
21 21 %if c.cur_query:
22 22 &raquo;
23 23 ${c.cur_query}
24 24 %endif
25 25 </%def>
26 26
27 27 <%def name="menu_bar_nav()">
28 28 %if c.repo_name:
29 29 ${self.menu_items(active='repositories')}
30 30 %else:
31 31 ${self.menu_items(active='search')}
32 32 %endif
33 33 </%def>
34 34
35 35 <%def name="menu_bar_subnav()">
36 36 %if c.repo_name:
37 37 ${self.repo_menu(active='options')}
38 38 %endif
39 39 </%def>
40 40
41 41 <%def name="main()">
42 42 <div class="box">
43 43 %if c.repo_name:
44 44 <!-- box / title -->
45 45 <div class="title">
46 46 ${self.repo_page_title(c.rhodecode_db_repo)}
47 47 </div>
48 ${h.form(h.url('search_repo_home',repo_name=c.repo_name),method='get')}
48 ${h.form(h.route_path('search_repo',repo_name=c.repo_name),method='get')}
49 49 %else:
50 50 <!-- box / title -->
51 51 <div class="title">
52 52 ${self.breadcrumbs()}
53 53 <ul class="links">&nbsp;</ul>
54 54 </div>
55 55 <!-- end box / title -->
56 ${h.form(h.url('search'),method='get')}
56 ${h.form(h.route_path('search'), method='get')}
57 57 %endif
58 58 <div class="form search-form">
59 59 <div class="fields">
60 60 <label for="q">${_('Search item')}:</label>
61 61 ${h.text('q', c.cur_query)}
62 62
63 63 ${h.select('type',c.search_type,[('content',_('File contents')), ('commit',_('Commit messages')), ('path',_('File names')),],id='id_search_type')}
64 64 <input type="submit" value="${_('Search')}" class="btn"/>
65 65 <br/>
66 66
67 67 <div class="search-feedback-items">
68 68 % for error in c.errors:
69 69 <span class="error-message">
70 70 % for k,v in error.asdict().items():
71 71 ${k} - ${v}
72 72 % endfor
73 73 </span>
74 74 % endfor
75 75 <div class="field">
76 76 <p class="filterexample" style="position: inherit" onclick="$('#search-help').toggle()">${_('Example Queries')}</p>
77 77 <pre id="search-help" style="display: none">${h.tooltip(h.search_filter_help(c.searcher))}</pre>
78 78 </div>
79 79
80 80 <div class="field">${c.runtime}</div>
81 81 </div>
82 82 </div>
83 83 </div>
84 84
85 85 ${h.end_form()}
86 86 <div class="search">
87 87 % if c.search_type == 'content':
88 88 <%include file='search_content.mako'/>
89 89 % elif c.search_type == 'path':
90 90 <%include file='search_path.mako'/>
91 91 % elif c.search_type == 'commit':
92 92 <%include file='search_commit.mako'/>
93 93 % elif c.search_type == 'repository':
94 94 <%include file='search_repository.mako'/>
95 95 % endif
96 96 </div>
97 97 </div>
98 98 <script>
99 99 $(document).ready(function(){
100 100 $("#id_search_type").select2({
101 101 'containerCssClass': "drop-menu",
102 102 'dropdownCssClass': "drop-menu-dropdown",
103 103 'dropdownAutoWidth': true,
104 104 'minimumResultsForSearch': -1
105 105 });
106 106 })
107 107 </script>
108 108 </%def>
@@ -1,101 +1,101 b''
1 1 <%def name="highlight_text_file(terms, text, url, line_context=3,
2 2 max_lines=10,
3 3 mimetype=None, filepath=None)">
4 4 <%
5 5 lines = text.split('\n')
6 6 lines_of_interest = set()
7 7 matching_lines = h.get_matching_line_offsets(lines, terms)
8 8 shown_matching_lines = 0
9 9
10 10 for line_number in matching_lines:
11 11 if len(lines_of_interest) < max_lines:
12 12 lines_of_interest |= set(range(
13 13 max(line_number - line_context, 0),
14 14 min(line_number + line_context, len(lines) + 1)))
15 15 shown_matching_lines += 1
16 16
17 17 %>
18 18 ${h.code_highlight(
19 19 text,
20 20 h.get_lexer_safe(
21 21 mimetype=mimetype,
22 22 filepath=filepath,
23 23 ),
24 24 h.SearchContentCodeHtmlFormatter(
25 25 linenos=True,
26 26 cssclass="code-highlight",
27 27 url=url,
28 28 query_terms=terms,
29 29 only_line_numbers=lines_of_interest
30 30 ))|n}
31 31 %if len(matching_lines) > shown_matching_lines:
32 32 <a href="${url}">
33 33 ${len(matching_lines) - shown_matching_lines} ${_('more matches in this file')}
34 34 </a>
35 35 %endif
36 36 </%def>
37 37
38 38 <div class="search-results">
39 39 %for entry in c.formatted_results:
40 40 ## search results are additionally filtered, and this check is just a safe gate
41 41 % if h.HasRepoPermissionAny('repository.write','repository.read','repository.admin')(entry['repository'], 'search results content check'):
42 42 <div id="codeblock" class="codeblock">
43 43 <div class="codeblock-header">
44 44 <h2>
45 45 %if h.get_repo_type_by_name(entry.get('repository')) == 'hg':
46 46 <i class="icon-hg"></i>
47 47 %elif h.get_repo_type_by_name(entry.get('repository')) == 'git':
48 48 <i class="icon-git"></i>
49 49 %elif h.get_repo_type_by_name(entry.get('repository')) == 'svn':
50 50 <i class="icon-svn"></i>
51 51 %endif
52 52 ${h.link_to(entry['repository'], h.url('summary_home',repo_name=entry['repository']))}
53 53 </h2>
54 54 <div class="stats">
55 55 ${h.link_to(h.literal(entry['f_path']), h.url('files_home',repo_name=entry['repository'],revision=entry.get('commit_id', 'tip'),f_path=entry['f_path']))}
56 56 %if entry.get('lines'):
57 | ${entry.get('lines', 0.)} ${ungettext('line', 'lines', entry.get('lines', 0.))}
57 | ${entry.get('lines', 0.)} ${_ungettext('line', 'lines', entry.get('lines', 0.))}
58 58 %endif
59 59 %if entry.get('size'):
60 60 | ${h.format_byte_size_binary(entry['size'])}
61 61 %endif
62 62 %if entry.get('mimetype'):
63 63 | ${entry.get('mimetype', "unknown mimetype")}
64 64 %endif
65 65 </div>
66 66 <div class="buttons">
67 67 <a id="file_history_overview_full" href="${h.url('changelog_file_home',repo_name=entry.get('repository',''),revision=entry.get('commit_id', 'tip'),f_path=entry.get('f_path',''))}">
68 68 ${_('Show Full History')}
69 69 </a> |
70 70 ${h.link_to(_('Annotation'), h.url('files_annotate_home', repo_name=entry.get('repository',''),revision=entry.get('commit_id', 'tip'),f_path=entry.get('f_path','')))}
71 71 | ${h.link_to(_('Raw'), h.url('files_raw_home', repo_name=entry.get('repository',''),revision=entry.get('commit_id', 'tip'),f_path=entry.get('f_path','')))}
72 72 | <a href="${h.url('files_rawfile_home',repo_name=entry.get('repository',''),revision=entry.get('commit_id', 'tip'),f_path=entry.get('f_path',''))}">
73 73 ${_('Download')}
74 74 </a>
75 75 </div>
76 76 </div>
77 77 <div class="code-body search-code-body">
78 78 ${highlight_text_file(c.cur_query, entry['content'],
79 79 url=h.url('files_home',repo_name=entry['repository'],revision=entry.get('commit_id', 'tip'),f_path=entry['f_path']),
80 80 mimetype=entry.get('mimetype'), filepath=entry.get('path'))}
81 81 </div>
82 82 </div>
83 83 % endif
84 84 %endfor
85 85 </div>
86 86 %if c.cur_query and c.formatted_results:
87 87 <div class="pagination-wh pagination-left" >
88 88 ${c.formatted_results.pager('$link_previous ~2~ $link_next')}
89 89 </div>
90 90 %endif
91 91
92 92 %if c.cur_query:
93 93 <script type="text/javascript">
94 94 $(function(){
95 95 $(".code").mark(
96 96 '${' '.join(h.normalize_text_for_matching(c.cur_query).split())}',
97 97 {"className": 'match',
98 98 });
99 99 })
100 100 </script>
101 101 %endif No newline at end of file
1 NO CONTENT: file renamed from rhodecode/tests/controllers/test_search.py to rhodecode/tests/lib/test_search.py
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now