##// END OF EJS Templates
pyramid: changes for pyramid migration.
marcink -
r1908:81441341 default
parent child Browse files
Show More
@@ -1,363 +1,368 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-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 import time
22 22 import logging
23 23
24 24 from pyramid.httpexceptions import HTTPFound
25 25
26 26 from rhodecode.lib import helpers as h
27 27 from rhodecode.lib.utils2 import StrictAttributeDict, safe_int, datetime_to_time
28 28 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
29 29 from rhodecode.model import repo
30 30 from rhodecode.model import repo_group
31 31 from rhodecode.model.db import User
32 32 from rhodecode.model.scm import ScmModel
33 33
34 34 log = logging.getLogger(__name__)
35 35
36 36
37 37 ADMIN_PREFIX = '/_admin'
38 38 STATIC_FILE_PREFIX = '/_static'
39 39
40 40
41 41 def add_route_with_slash(config,name, pattern, **kw):
42 42 config.add_route(name, pattern, **kw)
43 43 if not pattern.endswith('/'):
44 44 config.add_route(name + '_slash', pattern + '/', **kw)
45 45
46 46
47 47 def get_format_ref_id(repo):
48 48 """Returns a `repo` specific reference formatter function"""
49 49 if h.is_svn(repo):
50 50 return _format_ref_id_svn
51 51 else:
52 52 return _format_ref_id
53 53
54 54
55 55 def _format_ref_id(name, raw_id):
56 56 """Default formatting of a given reference `name`"""
57 57 return name
58 58
59 59
60 60 def _format_ref_id_svn(name, raw_id):
61 61 """Special way of formatting a reference for Subversion including path"""
62 62 return '%s@%s' % (name, raw_id)
63 63
64 64
65 65 class TemplateArgs(StrictAttributeDict):
66 66 pass
67 67
68 68
69 69 class BaseAppView(object):
70 70
71 71 def __init__(self, context, request):
72 72 self.request = request
73 73 self.context = context
74 74 self.session = request.session
75 75 self._rhodecode_user = request.user # auth user
76 76 self._rhodecode_db_user = self._rhodecode_user.get_instance()
77 77 self._maybe_needs_password_change(
78 78 request.matched_route.name, self._rhodecode_db_user)
79 79
80 80 def _maybe_needs_password_change(self, view_name, user_obj):
81 81 log.debug('Checking if user %s needs password change on view %s',
82 82 user_obj, view_name)
83 83 skip_user_views = [
84 84 'logout', 'login',
85 85 'my_account_password', 'my_account_password_update'
86 86 ]
87 87
88 88 if not user_obj:
89 89 return
90 90
91 91 if user_obj.username == User.DEFAULT_USER:
92 92 return
93 93
94 94 now = time.time()
95 95 should_change = user_obj.user_data.get('force_password_change')
96 96 change_after = safe_int(should_change) or 0
97 97 if should_change and now > change_after:
98 98 log.debug('User %s requires password change', user_obj)
99 99 h.flash('You are required to change your password', 'warning',
100 100 ignore_duplicate=True)
101 101
102 102 if view_name not in skip_user_views:
103 103 raise HTTPFound(
104 104 self.request.route_path('my_account_password'))
105 105
106 106 def _get_local_tmpl_context(self, include_app_defaults=False):
107 107 c = TemplateArgs()
108 108 c.auth_user = self.request.user
109 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
110 c.rhodecode_user = self.request.user
111
109 112 if include_app_defaults:
110 113 # NOTE(marcink): after full pyramid migration include_app_defaults
111 114 # should be turned on by default
112 115 from rhodecode.lib.base import attach_context_attributes
113 116 attach_context_attributes(c, self.request, self.request.user.user_id)
117
114 118 return c
115 119
116 120 def _register_global_c(self, tmpl_args):
117 121 """
118 122 Registers attributes to pylons global `c`
119 123 """
124
120 125 # TODO(marcink): remove once pyramid migration is finished
121 126 from pylons import tmpl_context as c
122 127 for k, v in tmpl_args.items():
123 128 setattr(c, k, v)
124 129
125 130 def _get_template_context(self, tmpl_args):
126 131 self._register_global_c(tmpl_args)
127 132
128 133 local_tmpl_args = {
129 134 'defaults': {},
130 135 'errors': {},
131 136 }
132 137 local_tmpl_args.update(tmpl_args)
133 138 return local_tmpl_args
134 139
135 140 def load_default_context(self):
136 141 """
137 142 example:
138 143
139 144 def load_default_context(self):
140 145 c = self._get_local_tmpl_context()
141 146 c.custom_var = 'foobar'
142 147 self._register_global_c(c)
143 148 return c
144 149 """
145 150 raise NotImplementedError('Needs implementation in view class')
146 151
147 152
148 153 class RepoAppView(BaseAppView):
149 154
150 155 def __init__(self, context, request):
151 156 super(RepoAppView, self).__init__(context, request)
152 157 self.db_repo = request.db_repo
153 158 self.db_repo_name = self.db_repo.repo_name
154 159 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
155 160
156 161 def _handle_missing_requirements(self, error):
157 162 log.error(
158 163 'Requirements are missing for repository %s: %s',
159 164 self.db_repo_name, error.message)
160 165
161 166 def _get_local_tmpl_context(self, include_app_defaults=False):
162 167 c = super(RepoAppView, self)._get_local_tmpl_context(
163 168 include_app_defaults=include_app_defaults)
164 169
165 170 # register common vars for this type of view
166 171 c.rhodecode_db_repo = self.db_repo
167 172 c.repo_name = self.db_repo_name
168 173 c.repository_pull_requests = self.db_repo_pull_requests
169 174
170 175 c.repository_requirements_missing = False
171 176 try:
172 177 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
173 178 except RepositoryRequirementError as e:
174 179 c.repository_requirements_missing = True
175 180 self._handle_missing_requirements(e)
176 181
177 182 return c
178 183
179 184
180 185 class DataGridAppView(object):
181 186 """
182 187 Common class to have re-usable grid rendering components
183 188 """
184 189
185 190 def _extract_ordering(self, request, column_map=None):
186 191 column_map = column_map or {}
187 192 column_index = safe_int(request.GET.get('order[0][column]'))
188 193 order_dir = request.GET.get(
189 194 'order[0][dir]', 'desc')
190 195 order_by = request.GET.get(
191 196 'columns[%s][data][sort]' % column_index, 'name_raw')
192 197
193 198 # translate datatable to DB columns
194 199 order_by = column_map.get(order_by) or order_by
195 200
196 201 search_q = request.GET.get('search[value]')
197 202 return search_q, order_by, order_dir
198 203
199 204 def _extract_chunk(self, request):
200 205 start = safe_int(request.GET.get('start'), 0)
201 206 length = safe_int(request.GET.get('length'), 25)
202 207 draw = safe_int(request.GET.get('draw'))
203 208 return draw, start, length
204 209
205 210
206 211 class BaseReferencesView(RepoAppView):
207 212 """
208 213 Base for reference view for branches, tags and bookmarks.
209 214 """
210 215 def load_default_context(self):
211 216 c = self._get_local_tmpl_context()
212 217
213 218 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
214 219 c.repo_info = self.db_repo
215 220
216 221 self._register_global_c(c)
217 222 return c
218 223
219 224 def load_refs_context(self, ref_items, partials_template):
220 225 _render = self.request.get_partial_renderer(partials_template)
221 226 pre_load = ["author", "date", "message"]
222 227
223 228 is_svn = h.is_svn(self.rhodecode_vcs_repo)
224 229 is_hg = h.is_hg(self.rhodecode_vcs_repo)
225 230
226 231 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
227 232
228 233 closed_refs = {}
229 234 if is_hg:
230 235 closed_refs = self.rhodecode_vcs_repo.branches_closed
231 236
232 237 data = []
233 238 for ref_name, commit_id in ref_items:
234 239 commit = self.rhodecode_vcs_repo.get_commit(
235 240 commit_id=commit_id, pre_load=pre_load)
236 241 closed = ref_name in closed_refs
237 242
238 243 # TODO: johbo: Unify generation of reference links
239 244 use_commit_id = '/' in ref_name or is_svn
240 245 files_url = h.url(
241 246 'files_home',
242 247 repo_name=self.db_repo_name,
243 248 f_path=ref_name if is_svn else '',
244 249 revision=commit_id if use_commit_id else ref_name,
245 250 at=ref_name)
246 251
247 252 data.append({
248 253 "name": _render('name', ref_name, files_url, closed),
249 254 "name_raw": ref_name,
250 255 "date": _render('date', commit.date),
251 256 "date_raw": datetime_to_time(commit.date),
252 257 "author": _render('author', commit.author),
253 258 "commit": _render(
254 259 'commit', commit.message, commit.raw_id, commit.idx),
255 260 "commit_raw": commit.idx,
256 261 "compare": _render(
257 262 'compare', format_ref_id(ref_name, commit.raw_id)),
258 263 })
259 264
260 265 return data
261 266
262 267
263 268 class RepoRoutePredicate(object):
264 269 def __init__(self, val, config):
265 270 self.val = val
266 271
267 272 def text(self):
268 273 return 'repo_route = %s' % self.val
269 274
270 275 phash = text
271 276
272 277 def __call__(self, info, request):
273 278
274 279 if hasattr(request, 'vcs_call'):
275 280 # skip vcs calls
276 281 return
277 282
278 283 repo_name = info['match']['repo_name']
279 284 repo_model = repo.RepoModel()
280 285 by_name_match = repo_model.get_by_repo_name(repo_name, cache=True)
281 286
282 287 if by_name_match:
283 288 # register this as request object we can re-use later
284 289 request.db_repo = by_name_match
285 290 return True
286 291
287 292 by_id_match = repo_model.get_repo_by_id(repo_name)
288 293 if by_id_match:
289 294 request.db_repo = by_id_match
290 295 return True
291 296
292 297 return False
293 298
294 299
295 300 class RepoTypeRoutePredicate(object):
296 301 def __init__(self, val, config):
297 302 self.val = val or ['hg', 'git', 'svn']
298 303
299 304 def text(self):
300 305 return 'repo_accepted_type = %s' % self.val
301 306
302 307 phash = text
303 308
304 309 def __call__(self, info, request):
305 310 if hasattr(request, 'vcs_call'):
306 311 # skip vcs calls
307 312 return
308 313
309 314 rhodecode_db_repo = request.db_repo
310 315
311 316 log.debug(
312 317 '%s checking repo type for %s in %s',
313 318 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
314 319
315 320 if rhodecode_db_repo.repo_type in self.val:
316 321 return True
317 322 else:
318 323 log.warning('Current view is not supported for repo type:%s',
319 324 rhodecode_db_repo.repo_type)
320 325 #
321 326 # h.flash(h.literal(
322 327 # _('Action not supported for %s.' % rhodecode_repo.alias)),
323 328 # category='warning')
324 329 # return redirect(
325 330 # route_path('repo_summary', repo_name=cls.rhodecode_db_repo.repo_name))
326 331
327 332 return False
328 333
329 334
330 335 class RepoGroupRoutePredicate(object):
331 336 def __init__(self, val, config):
332 337 self.val = val
333 338
334 339 def text(self):
335 340 return 'repo_group_route = %s' % self.val
336 341
337 342 phash = text
338 343
339 344 def __call__(self, info, request):
340 345 if hasattr(request, 'vcs_call'):
341 346 # skip vcs calls
342 347 return
343 348
344 349 repo_group_name = info['match']['repo_group_name']
345 350 repo_group_model = repo_group.RepoGroupModel()
346 351 by_name_match = repo_group_model.get_by_group_name(
347 352 repo_group_name, cache=True)
348 353
349 354 if by_name_match:
350 355 # register this as request object we can re-use later
351 356 request.db_repo_group = by_name_match
352 357 return True
353 358
354 359 return False
355 360
356 361
357 362 def includeme(config):
358 363 config.add_route_predicate(
359 364 'repo_route', RepoRoutePredicate)
360 365 config.add_route_predicate(
361 366 'repo_accepted_types', RepoTypeRoutePredicate)
362 367 config.add_route_predicate(
363 368 'repo_group_route', RepoGroupRoutePredicate)
@@ -1,525 +1,525 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
43 43 from rhodecode.model import meta
44 44 from rhodecode.config import patches
45 45 from rhodecode.config.routing import STATIC_FILE_PREFIX
46 46 from rhodecode.config.environment import (
47 47 load_environment, load_pyramid_environment)
48 48
49 49 from rhodecode.lib.vcs import VCSCommunicationError
50 50 from rhodecode.lib.exceptions import VCSServerUnavailable
51 51 from rhodecode.lib.middleware import csrf
52 52 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
53 53 from rhodecode.lib.middleware.error_handling import (
54 54 PylonsErrorHandlingMiddleware)
55 55 from rhodecode.lib.middleware.https_fixup import HttpsFixup
56 56 from rhodecode.lib.middleware.vcs import VCSMiddleware
57 57 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
58 58 from rhodecode.lib.utils2 import aslist as rhodecode_aslist, AttributeDict
59 59 from rhodecode.subscribers import (
60 60 scan_repositories_if_enabled, write_js_routes_if_enabled,
61 61 write_metadata_if_needed)
62 62
63 63
64 64 log = logging.getLogger(__name__)
65 65
66 66
67 67 # this is used to avoid avoid the route lookup overhead in routesmiddleware
68 68 # for certain routes which won't go to pylons to - eg. static files, debugger
69 69 # it is only needed for the pylons migration and can be removed once complete
70 70 class SkippableRoutesMiddleware(RoutesMiddleware):
71 71 """ Routes middleware that allows you to skip prefixes """
72 72
73 73 def __init__(self, *args, **kw):
74 74 self.skip_prefixes = kw.pop('skip_prefixes', [])
75 75 super(SkippableRoutesMiddleware, self).__init__(*args, **kw)
76 76
77 77 def __call__(self, environ, start_response):
78 78 for prefix in self.skip_prefixes:
79 79 if environ['PATH_INFO'].startswith(prefix):
80 80 # added to avoid the case when a missing /_static route falls
81 81 # through to pylons and causes an exception as pylons is
82 82 # expecting wsgiorg.routingargs to be set in the environ
83 83 # by RoutesMiddleware.
84 84 if 'wsgiorg.routing_args' not in environ:
85 85 environ['wsgiorg.routing_args'] = (None, {})
86 86 return self.app(environ, start_response)
87 87
88 88 return super(SkippableRoutesMiddleware, self).__call__(
89 89 environ, start_response)
90 90
91 91
92 92 def make_app(global_conf, static_files=True, **app_conf):
93 93 """Create a Pylons WSGI application and return it
94 94
95 95 ``global_conf``
96 96 The inherited configuration for this application. Normally from
97 97 the [DEFAULT] section of the Paste ini file.
98 98
99 99 ``app_conf``
100 100 The application's local configuration. Normally specified in
101 101 the [app:<name>] section of the Paste ini file (where <name>
102 102 defaults to main).
103 103
104 104 """
105 105 # Apply compatibility patches
106 106 patches.kombu_1_5_1_python_2_7_11()
107 107 patches.inspect_getargspec()
108 108
109 109 # Configure the Pylons environment
110 110 config = load_environment(global_conf, app_conf)
111 111
112 112 # The Pylons WSGI app
113 113 app = PylonsApp(config=config)
114 114
115 115 # Establish the Registry for this application
116 116 app = RegistryManager(app)
117 117
118 118 app.config = config
119 119
120 120 return app
121 121
122 122
123 123 def make_pyramid_app(global_config, **settings):
124 124 """
125 125 Constructs the WSGI application based on Pyramid and wraps the Pylons based
126 126 application.
127 127
128 128 Specials:
129 129
130 130 * We migrate from Pylons to Pyramid. While doing this, we keep both
131 131 frameworks functional. This involves moving some WSGI middlewares around
132 132 and providing access to some data internals, so that the old code is
133 133 still functional.
134 134
135 135 * The application can also be integrated like a plugin via the call to
136 136 `includeme`. This is accompanied with the other utility functions which
137 137 are called. Changing this should be done with great care to not break
138 138 cases when these fragments are assembled from another place.
139 139
140 140 """
141 141 # The edition string should be available in pylons too, so we add it here
142 142 # before copying the settings.
143 143 settings.setdefault('rhodecode.edition', 'Community Edition')
144 144
145 145 # As long as our Pylons application does expect "unprepared" settings, make
146 146 # sure that we keep an unmodified copy. This avoids unintentional change of
147 147 # behavior in the old application.
148 148 settings_pylons = settings.copy()
149 149
150 150 sanitize_settings_and_apply_defaults(settings)
151 151 config = Configurator(settings=settings)
152 152 add_pylons_compat_data(config.registry, global_config, settings_pylons)
153 153
154 154 load_pyramid_environment(global_config, settings)
155 155
156 156 includeme_first(config)
157 157 includeme(config)
158 158 pyramid_app = config.make_wsgi_app()
159 159 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
160 160 pyramid_app.config = config
161 161
162 162 # creating the app uses a connection - return it after we are done
163 163 meta.Session.remove()
164 164
165 165 return pyramid_app
166 166
167 167
168 168 def make_not_found_view(config):
169 169 """
170 170 This creates the view which should be registered as not-found-view to
171 171 pyramid. Basically it contains of the old pylons app, converted to a view.
172 172 Additionally it is wrapped by some other middlewares.
173 173 """
174 174 settings = config.registry.settings
175 175 vcs_server_enabled = settings['vcs.server.enable']
176 176
177 177 # Make pylons app from unprepared settings.
178 178 pylons_app = make_app(
179 179 config.registry._pylons_compat_global_config,
180 180 **config.registry._pylons_compat_settings)
181 181 config.registry._pylons_compat_config = pylons_app.config
182 182
183 183 # Appenlight monitoring.
184 184 pylons_app, appenlight_client = wrap_in_appenlight_if_enabled(
185 185 pylons_app, settings)
186 186
187 187 # The pylons app is executed inside of the pyramid 404 exception handler.
188 188 # Exceptions which are raised inside of it are not handled by pyramid
189 189 # again. Therefore we add a middleware that invokes the error handler in
190 190 # case of an exception or error response. This way we return proper error
191 191 # HTML pages in case of an error.
192 192 reraise = (settings.get('debugtoolbar.enabled', False) or
193 193 rhodecode.disable_error_handler)
194 194 pylons_app = PylonsErrorHandlingMiddleware(
195 195 pylons_app, error_handler, reraise)
196 196
197 197 # The VCSMiddleware shall operate like a fallback if pyramid doesn't find a
198 198 # view to handle the request. Therefore it is wrapped around the pylons
199 199 # app. It has to be outside of the error handling otherwise error responses
200 200 # from the vcsserver are converted to HTML error pages. This confuses the
201 201 # command line tools and the user won't get a meaningful error message.
202 202 if vcs_server_enabled:
203 203 pylons_app = VCSMiddleware(
204 204 pylons_app, settings, appenlight_client, registry=config.registry)
205 205
206 206 # Convert WSGI app to pyramid view and return it.
207 207 return wsgiapp(pylons_app)
208 208
209 209
210 210 def add_pylons_compat_data(registry, global_config, settings):
211 211 """
212 212 Attach data to the registry to support the Pylons integration.
213 213 """
214 214 registry._pylons_compat_global_config = global_config
215 215 registry._pylons_compat_settings = settings
216 216
217 217
218 218 def error_handler(exception, request):
219 219 import rhodecode
220 220 from rhodecode.lib import helpers
221 221
222 222 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
223 223
224 224 base_response = HTTPInternalServerError()
225 225 # prefer original exception for the response since it may have headers set
226 226 if isinstance(exception, HTTPException):
227 227 base_response = exception
228 228 elif isinstance(exception, VCSCommunicationError):
229 229 base_response = VCSServerUnavailable()
230 230
231 231 def is_http_error(response):
232 232 # error which should have traceback
233 233 return response.status_code > 499
234 234
235 235 if is_http_error(base_response):
236 236 log.exception(
237 237 'error occurred handling this request for path: %s', request.path)
238 238
239 239 c = AttributeDict()
240 240 c.error_message = base_response.status
241 241 c.error_explanation = base_response.explanation or str(base_response)
242 242 c.visual = AttributeDict()
243 243
244 244 c.visual.rhodecode_support_url = (
245 245 request.registry.settings.get('rhodecode_support_url') or
246 246 request.route_url('rhodecode_support')
247 247 )
248 248 c.redirect_time = 0
249 249 c.rhodecode_name = rhodecode_title
250 250 if not c.rhodecode_name:
251 251 c.rhodecode_name = 'Rhodecode'
252 252
253 253 c.causes = []
254 254 if hasattr(base_response, 'causes'):
255 255 c.causes = base_response.causes
256 c.messages = helpers.flash.pop_messages()
256 c.messages = helpers.flash.pop_messages(request=request)
257 257
258 258 response = render_to_response(
259 259 '/errors/error_document.mako', {'c': c, 'h': helpers}, request=request,
260 260 response=base_response)
261 261
262 262 return response
263 263
264 264
265 265 def includeme(config):
266 266 settings = config.registry.settings
267 267
268 268 # plugin information
269 269 config.registry.rhodecode_plugins = OrderedDict()
270 270
271 271 config.add_directive(
272 272 'register_rhodecode_plugin', register_rhodecode_plugin)
273 273
274 274 if asbool(settings.get('appenlight', 'false')):
275 275 config.include('appenlight_client.ext.pyramid_tween')
276 276
277 277 # Includes which are required. The application would fail without them.
278 278 config.include('pyramid_mako')
279 279 config.include('pyramid_beaker')
280 280
281 281 config.include('rhodecode.authentication')
282 282 config.include('rhodecode.integrations')
283 283
284 284 # apps
285 285 config.include('rhodecode.apps._base')
286 286 config.include('rhodecode.apps.ops')
287 287
288 288 config.include('rhodecode.apps.admin')
289 289 config.include('rhodecode.apps.channelstream')
290 290 config.include('rhodecode.apps.login')
291 291 config.include('rhodecode.apps.home')
292 292 config.include('rhodecode.apps.repository')
293 293 config.include('rhodecode.apps.repo_group')
294 294 config.include('rhodecode.apps.search')
295 295 config.include('rhodecode.apps.user_profile')
296 296 config.include('rhodecode.apps.my_account')
297 297 config.include('rhodecode.apps.svn_support')
298 298 config.include('rhodecode.apps.gist')
299 299
300 300 config.include('rhodecode.apps.debug_style')
301 301 config.include('rhodecode.tweens')
302 302 config.include('rhodecode.api')
303 303
304 304 config.add_route(
305 305 'rhodecode_support', 'https://rhodecode.com/help/', static=True)
306 306
307 307 config.add_translation_dirs('rhodecode:i18n/')
308 308 settings['default_locale_name'] = settings.get('lang', 'en')
309 309
310 310 # Add subscribers.
311 311 config.add_subscriber(scan_repositories_if_enabled, ApplicationCreated)
312 312 config.add_subscriber(write_metadata_if_needed, ApplicationCreated)
313 313 config.add_subscriber(write_js_routes_if_enabled, ApplicationCreated)
314 314
315 315 config.add_request_method(
316 316 'rhodecode.lib.partial_renderer.get_partial_renderer',
317 317 'get_partial_renderer')
318 318
319 319 # events
320 320 # TODO(marcink): this should be done when pyramid migration is finished
321 321 # config.add_subscriber(
322 322 # 'rhodecode.integrations.integrations_event_handler',
323 323 # 'rhodecode.events.RhodecodeEvent')
324 324
325 325 # Set the authorization policy.
326 326 authz_policy = ACLAuthorizationPolicy()
327 327 config.set_authorization_policy(authz_policy)
328 328
329 329 # Set the default renderer for HTML templates to mako.
330 330 config.add_mako_renderer('.html')
331 331
332 332 config.add_renderer(
333 333 name='json_ext',
334 334 factory='rhodecode.lib.ext_json_renderer.pyramid_ext_json')
335 335
336 336 # include RhodeCode plugins
337 337 includes = aslist(settings.get('rhodecode.includes', []))
338 338 for inc in includes:
339 339 config.include(inc)
340 340
341 341 # This is the glue which allows us to migrate in chunks. By registering the
342 342 # pylons based application as the "Not Found" view in Pyramid, we will
343 343 # fallback to the old application each time the new one does not yet know
344 344 # how to handle a request.
345 345 config.add_notfound_view(make_not_found_view(config))
346 346
347 347 if not settings.get('debugtoolbar.enabled', False):
348 348 # if no toolbar, then any exception gets caught and rendered
349 349 config.add_view(error_handler, context=Exception)
350 350
351 351 config.add_view(error_handler, context=HTTPError)
352 352
353 353
354 354 def includeme_first(config):
355 355 # redirect automatic browser favicon.ico requests to correct place
356 356 def favicon_redirect(context, request):
357 357 return HTTPFound(
358 358 request.static_path('rhodecode:public/images/favicon.ico'))
359 359
360 360 config.add_view(favicon_redirect, route_name='favicon')
361 361 config.add_route('favicon', '/favicon.ico')
362 362
363 363 def robots_redirect(context, request):
364 364 return HTTPFound(
365 365 request.static_path('rhodecode:public/robots.txt'))
366 366
367 367 config.add_view(robots_redirect, route_name='robots')
368 368 config.add_route('robots', '/robots.txt')
369 369
370 370 config.add_static_view(
371 371 '_static/deform', 'deform:static')
372 372 config.add_static_view(
373 373 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
374 374
375 375
376 376 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
377 377 """
378 378 Apply outer WSGI middlewares around the application.
379 379
380 380 Part of this has been moved up from the Pylons layer, so that the
381 381 data is also available if old Pylons code is hit through an already ported
382 382 view.
383 383 """
384 384 settings = config.registry.settings
385 385
386 386 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
387 387 pyramid_app = HttpsFixup(pyramid_app, settings)
388 388
389 389 # Add RoutesMiddleware to support the pylons compatibility tween during
390 390 # migration to pyramid.
391 391 pyramid_app = SkippableRoutesMiddleware(
392 392 pyramid_app, config.registry._pylons_compat_config['routes.map'],
393 393 skip_prefixes=(STATIC_FILE_PREFIX, '/_debug_toolbar'))
394 394
395 395 pyramid_app, _ = wrap_in_appenlight_if_enabled(pyramid_app, settings)
396 396
397 397 if settings['gzip_responses']:
398 398 pyramid_app = make_gzip_middleware(
399 399 pyramid_app, settings, compress_level=1)
400 400
401 401 # this should be the outer most middleware in the wsgi stack since
402 402 # middleware like Routes make database calls
403 403 def pyramid_app_with_cleanup(environ, start_response):
404 404 try:
405 405 return pyramid_app(environ, start_response)
406 406 finally:
407 407 # Dispose current database session and rollback uncommitted
408 408 # transactions.
409 409 meta.Session.remove()
410 410
411 411 # In a single threaded mode server, on non sqlite db we should have
412 412 # '0 Current Checked out connections' at the end of a request,
413 413 # if not, then something, somewhere is leaving a connection open
414 414 pool = meta.Base.metadata.bind.engine.pool
415 415 log.debug('sa pool status: %s', pool.status())
416 416
417 417 return pyramid_app_with_cleanup
418 418
419 419
420 420 def sanitize_settings_and_apply_defaults(settings):
421 421 """
422 422 Applies settings defaults and does all type conversion.
423 423
424 424 We would move all settings parsing and preparation into this place, so that
425 425 we have only one place left which deals with this part. The remaining parts
426 426 of the application would start to rely fully on well prepared settings.
427 427
428 428 This piece would later be split up per topic to avoid a big fat monster
429 429 function.
430 430 """
431 431
432 432 # Pyramid's mako renderer has to search in the templates folder so that the
433 433 # old templates still work. Ported and new templates are expected to use
434 434 # real asset specifications for the includes.
435 435 mako_directories = settings.setdefault('mako.directories', [
436 436 # Base templates of the original Pylons application
437 437 'rhodecode:templates',
438 438 ])
439 439 log.debug(
440 440 "Using the following Mako template directories: %s",
441 441 mako_directories)
442 442
443 443 # Default includes, possible to change as a user
444 444 pyramid_includes = settings.setdefault('pyramid.includes', [
445 445 'rhodecode.lib.middleware.request_wrapper',
446 446 ])
447 447 log.debug(
448 448 "Using the following pyramid.includes: %s",
449 449 pyramid_includes)
450 450
451 451 # TODO: johbo: Re-think this, usually the call to config.include
452 452 # should allow to pass in a prefix.
453 453 settings.setdefault('rhodecode.api.url', '/_admin/api')
454 454
455 455 # Sanitize generic settings.
456 456 _list_setting(settings, 'default_encoding', 'UTF-8')
457 457 _bool_setting(settings, 'is_test', 'false')
458 458 _bool_setting(settings, 'gzip_responses', 'false')
459 459
460 460 # Call split out functions that sanitize settings for each topic.
461 461 _sanitize_appenlight_settings(settings)
462 462 _sanitize_vcs_settings(settings)
463 463
464 464 return settings
465 465
466 466
467 467 def _sanitize_appenlight_settings(settings):
468 468 _bool_setting(settings, 'appenlight', 'false')
469 469
470 470
471 471 def _sanitize_vcs_settings(settings):
472 472 """
473 473 Applies settings defaults and does type conversion for all VCS related
474 474 settings.
475 475 """
476 476 _string_setting(settings, 'vcs.svn.compatible_version', '')
477 477 _string_setting(settings, 'git_rev_filter', '--all')
478 478 _string_setting(settings, 'vcs.hooks.protocol', 'http')
479 479 _string_setting(settings, 'vcs.scm_app_implementation', 'http')
480 480 _string_setting(settings, 'vcs.server', '')
481 481 _string_setting(settings, 'vcs.server.log_level', 'debug')
482 482 _string_setting(settings, 'vcs.server.protocol', 'http')
483 483 _bool_setting(settings, 'startup.import_repos', 'false')
484 484 _bool_setting(settings, 'vcs.hooks.direct_calls', 'false')
485 485 _bool_setting(settings, 'vcs.server.enable', 'true')
486 486 _bool_setting(settings, 'vcs.start_server', 'false')
487 487 _list_setting(settings, 'vcs.backends', 'hg, git, svn')
488 488 _int_setting(settings, 'vcs.connection_timeout', 3600)
489 489
490 490 # Support legacy values of vcs.scm_app_implementation. Legacy
491 491 # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http'
492 492 # which is now mapped to 'http'.
493 493 scm_app_impl = settings['vcs.scm_app_implementation']
494 494 if scm_app_impl == 'rhodecode.lib.middleware.utils.scm_app_http':
495 495 settings['vcs.scm_app_implementation'] = 'http'
496 496
497 497
498 498 def _int_setting(settings, name, default):
499 499 settings[name] = int(settings.get(name, default))
500 500
501 501
502 502 def _bool_setting(settings, name, default):
503 503 input = settings.get(name, default)
504 504 if isinstance(input, unicode):
505 505 input = input.encode('utf8')
506 506 settings[name] = asbool(input)
507 507
508 508
509 509 def _list_setting(settings, name, default):
510 510 raw_value = settings.get(name, default)
511 511
512 512 old_separator = ','
513 513 if old_separator in raw_value:
514 514 # If we get a comma separated list, pass it to our own function.
515 515 settings[name] = rhodecode_aslist(raw_value, sep=old_separator)
516 516 else:
517 517 # Otherwise we assume it uses pyramids space/newline separation.
518 518 settings[name] = aslist(raw_value)
519 519
520 520
521 521 def _string_setting(settings, name, default, lower=True):
522 522 value = settings.get(name, default)
523 523 if lower:
524 524 value = value.lower()
525 525 settings[name] = value
General Comments 0
You need to be logged in to leave comments. Login now