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