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