##// END OF EJS Templates
admin: moved admin pyramid into apps.
marcink -
r1503:91b1464c default
parent child Browse files
Show More
@@ -1,57 +1,57 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
21
22 from rhodecode.admin.navigation import NavigationRegistry
22 from rhodecode.apps.admin.navigation import NavigationRegistry
23 from rhodecode.config.routing import ADMIN_PREFIX
23 from rhodecode.config.routing import ADMIN_PREFIX
24 from rhodecode.lib.utils2 import str2bool
24 from rhodecode.lib.utils2 import str2bool
25
25
26
26
27 def includeme(config):
27 def includeme(config):
28 settings = config.get_settings()
28 settings = config.get_settings()
29
29
30 # Create admin navigation registry and add it to the pyramid registry.
30 # Create admin navigation registry and add it to the pyramid registry.
31 labs_active = str2bool(settings.get('labs_settings_active', False))
31 labs_active = str2bool(settings.get('labs_settings_active', False))
32 navigation_registry = NavigationRegistry(labs_active=labs_active)
32 navigation_registry = NavigationRegistry(labs_active=labs_active)
33 config.registry.registerUtility(navigation_registry)
33 config.registry.registerUtility(navigation_registry)
34
34
35 config.add_route(
35 config.add_route(
36 name='admin_settings_open_source',
36 name='admin_settings_open_source',
37 pattern=ADMIN_PREFIX + '/settings/open_source')
37 pattern=ADMIN_PREFIX + '/settings/open_source')
38 config.add_route(
38 config.add_route(
39 name='admin_settings_vcs_svn_generate_cfg',
39 name='admin_settings_vcs_svn_generate_cfg',
40 pattern=ADMIN_PREFIX + '/settings/vcs/svn_generate_cfg')
40 pattern=ADMIN_PREFIX + '/settings/vcs/svn_generate_cfg')
41
41
42 config.add_route(
42 config.add_route(
43 name='admin_settings_system',
43 name='admin_settings_system',
44 pattern=ADMIN_PREFIX + '/settings/system')
44 pattern=ADMIN_PREFIX + '/settings/system')
45 config.add_route(
45 config.add_route(
46 name='admin_settings_system_update',
46 name='admin_settings_system_update',
47 pattern=ADMIN_PREFIX + '/settings/system/updates')
47 pattern=ADMIN_PREFIX + '/settings/system/updates')
48
48
49 config.add_route(
49 config.add_route(
50 name='admin_settings_sessions',
50 name='admin_settings_sessions',
51 pattern=ADMIN_PREFIX + '/settings/sessions')
51 pattern=ADMIN_PREFIX + '/settings/sessions')
52 config.add_route(
52 config.add_route(
53 name='admin_settings_sessions_cleanup',
53 name='admin_settings_sessions_cleanup',
54 pattern=ADMIN_PREFIX + '/settings/sessions/cleanup')
54 pattern=ADMIN_PREFIX + '/settings/sessions/cleanup')
55
55
56 # Scan module for configuration decorators.
56 # Scan module for configuration decorators.
57 config.scan()
57 config.scan()
1 NO CONTENT: file renamed from rhodecode/admin/interfaces.py to rhodecode/apps/admin/interfaces.py
NO CONTENT: file renamed from rhodecode/admin/interfaces.py to rhodecode/apps/admin/interfaces.py
@@ -1,140 +1,140 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
21
22 import logging
22 import logging
23 import collections
23 import collections
24
24
25 from pylons import url
25 from pylons import url
26 from zope.interface import implementer
26 from zope.interface import implementer
27
27
28 from rhodecode.admin.interfaces import IAdminNavigationRegistry
28 from rhodecode.apps.admin.interfaces import IAdminNavigationRegistry
29 from rhodecode.lib.utils import get_registry
29 from rhodecode.lib.utils import get_registry
30 from rhodecode.translation import _
30 from rhodecode.translation import _
31
31
32
32
33 log = logging.getLogger(__name__)
33 log = logging.getLogger(__name__)
34
34
35 NavListEntry = collections.namedtuple('NavListEntry', ['key', 'name', 'url'])
35 NavListEntry = collections.namedtuple('NavListEntry', ['key', 'name', 'url'])
36
36
37
37
38 class NavEntry(object):
38 class NavEntry(object):
39 """
39 """
40 Represents an entry in the admin navigation.
40 Represents an entry in the admin navigation.
41
41
42 :param key: Unique identifier used to store reference in an OrderedDict.
42 :param key: Unique identifier used to store reference in an OrderedDict.
43 :param name: Display name, usually a translation string.
43 :param name: Display name, usually a translation string.
44 :param view_name: Name of the view, used generate the URL.
44 :param view_name: Name of the view, used generate the URL.
45 :param pyramid: Indicator to use pyramid for URL generation. This should
45 :param pyramid: Indicator to use pyramid for URL generation. This should
46 be removed as soon as we are fully migrated to pyramid.
46 be removed as soon as we are fully migrated to pyramid.
47 """
47 """
48
48
49 def __init__(self, key, name, view_name, pyramid=False):
49 def __init__(self, key, name, view_name, pyramid=False):
50 self.key = key
50 self.key = key
51 self.name = name
51 self.name = name
52 self.view_name = view_name
52 self.view_name = view_name
53 self.pyramid = pyramid
53 self.pyramid = pyramid
54
54
55 def generate_url(self, request):
55 def generate_url(self, request):
56 if self.pyramid:
56 if self.pyramid:
57 if hasattr(request, 'route_path'):
57 if hasattr(request, 'route_path'):
58 return request.route_path(self.view_name)
58 return request.route_path(self.view_name)
59 else:
59 else:
60 # TODO: johbo: Remove this after migrating to pyramid.
60 # TODO: johbo: Remove this after migrating to pyramid.
61 # We need the pyramid request here to generate URLs to pyramid
61 # We need the pyramid request here to generate URLs to pyramid
62 # views from within pylons views.
62 # views from within pylons views.
63 from pyramid.threadlocal import get_current_request
63 from pyramid.threadlocal import get_current_request
64 pyramid_request = get_current_request()
64 pyramid_request = get_current_request()
65 return pyramid_request.route_path(self.view_name)
65 return pyramid_request.route_path(self.view_name)
66 else:
66 else:
67 return url(self.view_name)
67 return url(self.view_name)
68
68
69 def get_localized_name(self, request):
69 def get_localized_name(self, request):
70 if hasattr(request, 'translate'):
70 if hasattr(request, 'translate'):
71 return request.translate(self.name)
71 return request.translate(self.name)
72 else:
72 else:
73 # TODO(marcink): Remove this after migrating to pyramid
73 # TODO(marcink): Remove this after migrating to pyramid
74 from pyramid.threadlocal import get_current_request
74 from pyramid.threadlocal import get_current_request
75 pyramid_request = get_current_request()
75 pyramid_request = get_current_request()
76 return pyramid_request.translate(self.name)
76 return pyramid_request.translate(self.name)
77
77
78
78
79 @implementer(IAdminNavigationRegistry)
79 @implementer(IAdminNavigationRegistry)
80 class NavigationRegistry(object):
80 class NavigationRegistry(object):
81
81
82 _base_entries = [
82 _base_entries = [
83 NavEntry('global', _('Global'), 'admin_settings_global'),
83 NavEntry('global', _('Global'), 'admin_settings_global'),
84 NavEntry('vcs', _('VCS'), 'admin_settings_vcs'),
84 NavEntry('vcs', _('VCS'), 'admin_settings_vcs'),
85 NavEntry('visual', _('Visual'), 'admin_settings_visual'),
85 NavEntry('visual', _('Visual'), 'admin_settings_visual'),
86 NavEntry('mapping', _('Remap and Rescan'), 'admin_settings_mapping'),
86 NavEntry('mapping', _('Remap and Rescan'), 'admin_settings_mapping'),
87 NavEntry('issuetracker', _('Issue Tracker'),
87 NavEntry('issuetracker', _('Issue Tracker'),
88 'admin_settings_issuetracker'),
88 'admin_settings_issuetracker'),
89 NavEntry('email', _('Email'), 'admin_settings_email'),
89 NavEntry('email', _('Email'), 'admin_settings_email'),
90 NavEntry('hooks', _('Hooks'), 'admin_settings_hooks'),
90 NavEntry('hooks', _('Hooks'), 'admin_settings_hooks'),
91 NavEntry('search', _('Full Text Search'), 'admin_settings_search'),
91 NavEntry('search', _('Full Text Search'), 'admin_settings_search'),
92
92
93 NavEntry('integrations', _('Integrations'),
93 NavEntry('integrations', _('Integrations'),
94 'global_integrations_home', pyramid=True),
94 'global_integrations_home', pyramid=True),
95 NavEntry('system', _('System Info'),
95 NavEntry('system', _('System Info'),
96 'admin_settings_system', pyramid=True),
96 'admin_settings_system', pyramid=True),
97 NavEntry('sessions', _('User Sessions'),
97 NavEntry('sessions', _('User Sessions'),
98 'admin_settings_sessions', pyramid=True),
98 'admin_settings_sessions', pyramid=True),
99 NavEntry('open_source', _('Open Source Licenses'),
99 NavEntry('open_source', _('Open Source Licenses'),
100 'admin_settings_open_source', pyramid=True),
100 'admin_settings_open_source', pyramid=True),
101
101
102 # TODO: marcink: we disable supervisor now until the supervisor stats
102 # TODO: marcink: we disable supervisor now until the supervisor stats
103 # page is fixed in the nix configuration
103 # page is fixed in the nix configuration
104 # NavEntry('supervisor', _('Supervisor'), 'admin_settings_supervisor'),
104 # NavEntry('supervisor', _('Supervisor'), 'admin_settings_supervisor'),
105 ]
105 ]
106
106
107 _labs_entry = NavEntry('labs', _('Labs'), 'admin_settings_labs')
107 _labs_entry = NavEntry('labs', _('Labs'), 'admin_settings_labs')
108
108
109 def __init__(self, labs_active=False):
109 def __init__(self, labs_active=False):
110 self._registered_entries = collections.OrderedDict([
110 self._registered_entries = collections.OrderedDict([
111 (item.key, item) for item in self.__class__._base_entries
111 (item.key, item) for item in self.__class__._base_entries
112 ])
112 ])
113
113
114 if labs_active:
114 if labs_active:
115 self.add_entry(self._labs_entry)
115 self.add_entry(self._labs_entry)
116
116
117 def add_entry(self, entry):
117 def add_entry(self, entry):
118 self._registered_entries[entry.key] = entry
118 self._registered_entries[entry.key] = entry
119
119
120 def get_navlist(self, request):
120 def get_navlist(self, request):
121 navlist = [NavListEntry(i.key, i.get_localized_name(request),
121 navlist = [NavListEntry(i.key, i.get_localized_name(request),
122 i.generate_url(request))
122 i.generate_url(request))
123 for i in self._registered_entries.values()]
123 for i in self._registered_entries.values()]
124 return navlist
124 return navlist
125
125
126
126
127 def navigation_registry(request):
127 def navigation_registry(request):
128 """
128 """
129 Helper that returns the admin navigation registry.
129 Helper that returns the admin navigation registry.
130 """
130 """
131 pyramid_registry = get_registry(request)
131 pyramid_registry = get_registry(request)
132 nav_registry = pyramid_registry.queryUtility(IAdminNavigationRegistry)
132 nav_registry = pyramid_registry.queryUtility(IAdminNavigationRegistry)
133 return nav_registry
133 return nav_registry
134
134
135
135
136 def navigation_list(request):
136 def navigation_list(request):
137 """
137 """
138 Helper that returns the admin navigation as list of NavListEntry objects.
138 Helper that returns the admin navigation as list of NavListEntry objects.
139 """
139 """
140 return navigation_registry(request).get_navlist(request)
140 return navigation_registry(request).get_navlist(request)
1 NO CONTENT: file renamed from rhodecode/admin/views/__init__.py to rhodecode/apps/admin/views/__init__.py
NO CONTENT: file renamed from rhodecode/admin/views/__init__.py to rhodecode/apps/admin/views/__init__.py
1 NO CONTENT: file renamed from rhodecode/admin/views/base.py to rhodecode/apps/admin/views/base.py
NO CONTENT: file renamed from rhodecode/admin/views/base.py to rhodecode/apps/admin/views/base.py
@@ -1,48 +1,48 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 collections
21 import collections
22 import logging
22 import logging
23
23
24 from pylons import tmpl_context as c
24 from pylons import tmpl_context as c
25 from pyramid.view import view_config
25 from pyramid.view import view_config
26
26
27 from rhodecode.admin.views.base import AdminSettingsView
27 from rhodecode.apps.admin.views.base import AdminSettingsView
28 from rhodecode.admin.navigation import navigation_list
28 from rhodecode.apps.admin.navigation import navigation_list
29 from rhodecode.lib.auth import (LoginRequired, HasPermissionAllDecorator)
29 from rhodecode.lib.auth import (LoginRequired, HasPermissionAllDecorator)
30 from rhodecode.lib.utils import read_opensource_licenses
30 from rhodecode.lib.utils import read_opensource_licenses
31
31
32 log = logging.getLogger(__name__)
32 log = logging.getLogger(__name__)
33
33
34
34
35 class OpenSourceLicensesAdminSettingsView(AdminSettingsView):
35 class OpenSourceLicensesAdminSettingsView(AdminSettingsView):
36
36
37 @LoginRequired()
37 @LoginRequired()
38 @HasPermissionAllDecorator('hg.admin')
38 @HasPermissionAllDecorator('hg.admin')
39 @view_config(
39 @view_config(
40 route_name='admin_settings_open_source', request_method='GET',
40 route_name='admin_settings_open_source', request_method='GET',
41 renderer='rhodecode:templates/admin/settings/settings.mako')
41 renderer='rhodecode:templates/admin/settings/settings.mako')
42 def open_source_licenses(self):
42 def open_source_licenses(self):
43 c.active = 'open_source'
43 c.active = 'open_source'
44 c.navlist = navigation_list(self.request)
44 c.navlist = navigation_list(self.request)
45 c.opensource_licenses = collections.OrderedDict(
45 c.opensource_licenses = collections.OrderedDict(
46 sorted(read_opensource_licenses().items(), key=lambda t: t[0]))
46 sorted(read_opensource_licenses().items(), key=lambda t: t[0]))
47
47
48 return {}
48 return {}
@@ -1,100 +1,98 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 logging
21 import logging
22
22
23 from pylons import tmpl_context as c
23 from pylons import tmpl_context as c
24 from pyramid.view import view_config
24 from pyramid.view import view_config
25 from pyramid.httpexceptions import HTTPFound
25 from pyramid.httpexceptions import HTTPFound
26
26
27 from rhodecode.translation import _
27 from rhodecode.translation import _
28
28
29 from rhodecode.admin.views.base import AdminSettingsView
29 from rhodecode.apps.admin.views.base import AdminSettingsView
30 from rhodecode.apps.admin.navigation import navigation_list
30 from rhodecode.lib.auth import (
31 from rhodecode.lib.auth import (
31 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
32 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
32 from rhodecode.lib.utils2 import safe_int
33 from rhodecode.lib.utils2 import safe_int
33 from rhodecode.lib import system_info
34 from rhodecode.lib import system_info
34 from rhodecode.lib import user_sessions
35 from rhodecode.lib import user_sessions
35
36
36
37
37 from rhodecode.admin.navigation import navigation_list
38
39
40 log = logging.getLogger(__name__)
38 log = logging.getLogger(__name__)
41
39
42
40
43 class AdminSessionSettingsView(AdminSettingsView):
41 class AdminSessionSettingsView(AdminSettingsView):
44
42
45 @LoginRequired()
43 @LoginRequired()
46 @HasPermissionAllDecorator('hg.admin')
44 @HasPermissionAllDecorator('hg.admin')
47 @view_config(
45 @view_config(
48 route_name='admin_settings_sessions', request_method='GET',
46 route_name='admin_settings_sessions', request_method='GET',
49 renderer='rhodecode:templates/admin/settings/settings.mako')
47 renderer='rhodecode:templates/admin/settings/settings.mako')
50 def settings_sessions(self):
48 def settings_sessions(self):
51 c.active = 'sessions'
49 c.active = 'sessions'
52 c.navlist = navigation_list(self.request)
50 c.navlist = navigation_list(self.request)
53
51
54 c.cleanup_older_days = 60
52 c.cleanup_older_days = 60
55 older_than_seconds = 60 * 60 * 24 * c.cleanup_older_days
53 older_than_seconds = 60 * 60 * 24 * c.cleanup_older_days
56
54
57 config = system_info.rhodecode_config().get_value()['value']['config']
55 config = system_info.rhodecode_config().get_value()['value']['config']
58 c.session_model = user_sessions.get_session_handler(
56 c.session_model = user_sessions.get_session_handler(
59 config.get('beaker.session.type', 'memory'))(config)
57 config.get('beaker.session.type', 'memory'))(config)
60
58
61 c.session_conf = c.session_model.config
59 c.session_conf = c.session_model.config
62 c.session_count = c.session_model.get_count()
60 c.session_count = c.session_model.get_count()
63 c.session_expired_count = c.session_model.get_expired_count(
61 c.session_expired_count = c.session_model.get_expired_count(
64 older_than_seconds)
62 older_than_seconds)
65
63
66 return {}
64 return {}
67
65
68 @LoginRequired()
66 @LoginRequired()
69 @CSRFRequired()
67 @CSRFRequired()
70 @HasPermissionAllDecorator('hg.admin')
68 @HasPermissionAllDecorator('hg.admin')
71 @view_config(
69 @view_config(
72 route_name='admin_settings_sessions_cleanup', request_method='POST')
70 route_name='admin_settings_sessions_cleanup', request_method='POST')
73 def settings_sessions_cleanup(self):
71 def settings_sessions_cleanup(self):
74 _ = self.request.translate
72 _ = self.request.translate
75 expire_days = safe_int(self.request.params.get('expire_days'))
73 expire_days = safe_int(self.request.params.get('expire_days'))
76
74
77 if expire_days is None:
75 if expire_days is None:
78 expire_days = 60
76 expire_days = 60
79
77
80 older_than_seconds = 60 * 60 * 24 * expire_days
78 older_than_seconds = 60 * 60 * 24 * expire_days
81
79
82 config = system_info.rhodecode_config().get_value()['value']['config']
80 config = system_info.rhodecode_config().get_value()['value']['config']
83 session_model = user_sessions.get_session_handler(
81 session_model = user_sessions.get_session_handler(
84 config.get('beaker.session.type', 'memory'))(config)
82 config.get('beaker.session.type', 'memory'))(config)
85
83
86 try:
84 try:
87 session_model.clean_sessions(
85 session_model.clean_sessions(
88 older_than_seconds=older_than_seconds)
86 older_than_seconds=older_than_seconds)
89 self.request.session.flash(
87 self.request.session.flash(
90 _('Cleaned up old sessions'), queue='success')
88 _('Cleaned up old sessions'), queue='success')
91 except user_sessions.CleanupCommand as msg:
89 except user_sessions.CleanupCommand as msg:
92 self.request.session.flash(msg.message, queue='warning')
90 self.request.session.flash(msg.message, queue='warning')
93 except Exception as e:
91 except Exception as e:
94 log.exception('Failed session cleanup')
92 log.exception('Failed session cleanup')
95 self.request.session.flash(
93 self.request.session.flash(
96 _('Failed to cleanup up old sessions'), queue='error')
94 _('Failed to cleanup up old sessions'), queue='error')
97
95
98 redirect_to = self.request.resource_path(
96 redirect_to = self.request.resource_path(
99 self.context, route_name='admin_settings_sessions')
97 self.context, route_name='admin_settings_sessions')
100 return HTTPFound(redirect_to)
98 return HTTPFound(redirect_to)
@@ -1,60 +1,60 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 logging
21 import logging
22
22
23 from pyramid.view import view_config
23 from pyramid.view import view_config
24
24
25 from rhodecode.svn_support.utils import generate_mod_dav_svn_config
25 from rhodecode.svn_support.utils import generate_mod_dav_svn_config
26
26
27 from rhodecode.admin.views.base import AdminSettingsView
27 from rhodecode.apps.admin.views.base import AdminSettingsView
28 from rhodecode.lib.auth import (
28 from rhodecode.lib.auth import (
29 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
29 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
30
30
31 log = logging.getLogger(__name__)
31 log = logging.getLogger(__name__)
32
32
33
33
34 class SvnConfigAdminSettingsView(AdminSettingsView):
34 class SvnConfigAdminSettingsView(AdminSettingsView):
35
35
36 @LoginRequired()
36 @LoginRequired()
37 @CSRFRequired()
37 @CSRFRequired()
38 @HasPermissionAllDecorator('hg.admin')
38 @HasPermissionAllDecorator('hg.admin')
39 @view_config(
39 @view_config(
40 route_name='admin_settings_vcs_svn_generate_cfg',
40 route_name='admin_settings_vcs_svn_generate_cfg',
41 request_method='POST', renderer='json')
41 request_method='POST', renderer='json')
42 def vcs_svn_generate_config(self):
42 def vcs_svn_generate_config(self):
43 _ = self.request.translate
43 _ = self.request.translate
44 try:
44 try:
45 generate_mod_dav_svn_config(self.request.registry)
45 generate_mod_dav_svn_config(self.request.registry)
46 msg = {
46 msg = {
47 'message': _('Apache configuration for Subversion generated.'),
47 'message': _('Apache configuration for Subversion generated.'),
48 'level': 'success',
48 'level': 'success',
49 }
49 }
50 except Exception:
50 except Exception:
51 log.exception(
51 log.exception(
52 'Exception while generating the Apache '
52 'Exception while generating the Apache '
53 'configuration for Subversion.')
53 'configuration for Subversion.')
54 msg = {
54 msg = {
55 'message': _('Failed to generate the Apache configuration for Subversion.'),
55 'message': _('Failed to generate the Apache configuration for Subversion.'),
56 'level': 'error',
56 'level': 'error',
57 }
57 }
58
58
59 data = {'message': msg}
59 data = {'message': msg}
60 return data
60 return data
@@ -1,204 +1,203 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 logging
21 import logging
22 import urllib2
22 import urllib2
23 import packaging.version
23 import packaging.version
24
24
25 from pylons import tmpl_context as c
25 from pylons import tmpl_context as c
26 from pyramid.view import view_config
26 from pyramid.view import view_config
27
27
28 import rhodecode
28 import rhodecode
29 from rhodecode.apps.admin.views.base import AdminSettingsView
30 from rhodecode.apps.admin.navigation import navigation_list
29 from rhodecode.lib import helpers as h
31 from rhodecode.lib import helpers as h
30 from rhodecode.lib.auth import (LoginRequired, HasPermissionAllDecorator)
32 from rhodecode.lib.auth import (LoginRequired, HasPermissionAllDecorator)
31 from rhodecode.lib.utils2 import str2bool
33 from rhodecode.lib.utils2 import str2bool
32 from rhodecode.lib import system_info
34 from rhodecode.lib import system_info
33 from rhodecode.lib.ext_json import json
35 from rhodecode.lib.ext_json import json
34
35 from rhodecode.admin.views.base import AdminSettingsView
36 from rhodecode.admin.navigation import navigation_list
37 from rhodecode.model.settings import SettingsModel
36 from rhodecode.model.settings import SettingsModel
38
37
39 log = logging.getLogger(__name__)
38 log = logging.getLogger(__name__)
40
39
41
40
42 class AdminSystemInfoSettingsView(AdminSettingsView):
41 class AdminSystemInfoSettingsView(AdminSettingsView):
43
42
44 @staticmethod
43 @staticmethod
45 def get_update_data(update_url):
44 def get_update_data(update_url):
46 """Return the JSON update data."""
45 """Return the JSON update data."""
47 ver = rhodecode.__version__
46 ver = rhodecode.__version__
48 log.debug('Checking for upgrade on `%s` server', update_url)
47 log.debug('Checking for upgrade on `%s` server', update_url)
49 opener = urllib2.build_opener()
48 opener = urllib2.build_opener()
50 opener.addheaders = [('User-agent', 'RhodeCode-SCM/%s' % ver)]
49 opener.addheaders = [('User-agent', 'RhodeCode-SCM/%s' % ver)]
51 response = opener.open(update_url)
50 response = opener.open(update_url)
52 response_data = response.read()
51 response_data = response.read()
53 data = json.loads(response_data)
52 data = json.loads(response_data)
54
53
55 return data
54 return data
56
55
57 def get_update_url(self):
56 def get_update_url(self):
58 settings = SettingsModel().get_all_settings()
57 settings = SettingsModel().get_all_settings()
59 return settings.get('rhodecode_update_url')
58 return settings.get('rhodecode_update_url')
60
59
61 @LoginRequired()
60 @LoginRequired()
62 @HasPermissionAllDecorator('hg.admin')
61 @HasPermissionAllDecorator('hg.admin')
63 @view_config(
62 @view_config(
64 route_name='admin_settings_system', request_method='GET',
63 route_name='admin_settings_system', request_method='GET',
65 renderer='rhodecode:templates/admin/settings/settings.mako')
64 renderer='rhodecode:templates/admin/settings/settings.mako')
66 def settings_system_info(self):
65 def settings_system_info(self):
67 _ = self.request.translate
66 _ = self.request.translate
68
67
69 c.active = 'system'
68 c.active = 'system'
70 c.navlist = navigation_list(self.request)
69 c.navlist = navigation_list(self.request)
71
70
72 # TODO(marcink), figure out how to allow only selected users to do this
71 # TODO(marcink), figure out how to allow only selected users to do this
73 c.allowed_to_snapshot = self._rhodecode_user.admin
72 c.allowed_to_snapshot = self._rhodecode_user.admin
74
73
75 snapshot = str2bool(self.request.params.get('snapshot'))
74 snapshot = str2bool(self.request.params.get('snapshot'))
76
75
77 c.rhodecode_update_url = self.get_update_url()
76 c.rhodecode_update_url = self.get_update_url()
78 server_info = system_info.get_system_info(self.request.environ)
77 server_info = system_info.get_system_info(self.request.environ)
79
78
80 for key, val in server_info.items():
79 for key, val in server_info.items():
81 setattr(c, key, val)
80 setattr(c, key, val)
82
81
83 def val(name, subkey='human_value'):
82 def val(name, subkey='human_value'):
84 return server_info[name][subkey]
83 return server_info[name][subkey]
85
84
86 def state(name):
85 def state(name):
87 return server_info[name]['state']
86 return server_info[name]['state']
88
87
89 def val2(name):
88 def val2(name):
90 val = server_info[name]['human_value']
89 val = server_info[name]['human_value']
91 state = server_info[name]['state']
90 state = server_info[name]['state']
92 return val, state
91 return val, state
93
92
94 update_info_msg = _('Note: please make sure this server can '
93 update_info_msg = _('Note: please make sure this server can '
95 'access `${url}` for the update link to work',
94 'access `${url}` for the update link to work',
96 mapping=dict(url=c.rhodecode_update_url))
95 mapping=dict(url=c.rhodecode_update_url))
97 c.data_items = [
96 c.data_items = [
98 # update info
97 # update info
99 (_('Update info'), h.literal(
98 (_('Update info'), h.literal(
100 '<span class="link" id="check_for_update" >%s.</span>' % (
99 '<span class="link" id="check_for_update" >%s.</span>' % (
101 _('Check for updates')) +
100 _('Check for updates')) +
102 '<br/> <span >%s.</span>' % (update_info_msg)
101 '<br/> <span >%s.</span>' % (update_info_msg)
103 ), ''),
102 ), ''),
104
103
105 # RhodeCode specific
104 # RhodeCode specific
106 (_('RhodeCode Version'), val('rhodecode_app')['text'], state('rhodecode_app')),
105 (_('RhodeCode Version'), val('rhodecode_app')['text'], state('rhodecode_app')),
107 (_('RhodeCode Server IP'), val('server')['server_ip'], state('server')),
106 (_('RhodeCode Server IP'), val('server')['server_ip'], state('server')),
108 (_('RhodeCode Server ID'), val('server')['server_id'], state('server')),
107 (_('RhodeCode Server ID'), val('server')['server_id'], state('server')),
109 (_('RhodeCode Configuration'), val('rhodecode_config')['path'], state('rhodecode_config')),
108 (_('RhodeCode Configuration'), val('rhodecode_config')['path'], state('rhodecode_config')),
110 (_('Workers'), val('rhodecode_config')['config']['server:main'].get('workers', '?'), state('rhodecode_config')),
109 (_('Workers'), val('rhodecode_config')['config']['server:main'].get('workers', '?'), state('rhodecode_config')),
111 (_('Worker Type'), val('rhodecode_config')['config']['server:main'].get('worker_class', 'sync'), state('rhodecode_config')),
110 (_('Worker Type'), val('rhodecode_config')['config']['server:main'].get('worker_class', 'sync'), state('rhodecode_config')),
112 ('', '', ''), # spacer
111 ('', '', ''), # spacer
113
112
114 # Database
113 # Database
115 (_('Database'), val('database')['url'], state('database')),
114 (_('Database'), val('database')['url'], state('database')),
116 (_('Database version'), val('database')['version'], state('database')),
115 (_('Database version'), val('database')['version'], state('database')),
117 ('', '', ''), # spacer
116 ('', '', ''), # spacer
118
117
119 # Platform/Python
118 # Platform/Python
120 (_('Platform'), val('platform')['name'], state('platform')),
119 (_('Platform'), val('platform')['name'], state('platform')),
121 (_('Platform UUID'), val('platform')['uuid'], state('platform')),
120 (_('Platform UUID'), val('platform')['uuid'], state('platform')),
122 (_('Python version'), val('python')['version'], state('python')),
121 (_('Python version'), val('python')['version'], state('python')),
123 (_('Python path'), val('python')['executable'], state('python')),
122 (_('Python path'), val('python')['executable'], state('python')),
124 ('', '', ''), # spacer
123 ('', '', ''), # spacer
125
124
126 # Systems stats
125 # Systems stats
127 (_('CPU'), val('cpu')['text'], state('cpu')),
126 (_('CPU'), val('cpu')['text'], state('cpu')),
128 (_('Load'), val('load')['text'], state('load')),
127 (_('Load'), val('load')['text'], state('load')),
129 (_('Memory'), val('memory')['text'], state('memory')),
128 (_('Memory'), val('memory')['text'], state('memory')),
130 (_('Uptime'), val('uptime')['text'], state('uptime')),
129 (_('Uptime'), val('uptime')['text'], state('uptime')),
131 ('', '', ''), # spacer
130 ('', '', ''), # spacer
132
131
133 # Repo storage
132 # Repo storage
134 (_('Storage location'), val('storage')['path'], state('storage')),
133 (_('Storage location'), val('storage')['path'], state('storage')),
135 (_('Storage info'), val('storage')['text'], state('storage')),
134 (_('Storage info'), val('storage')['text'], state('storage')),
136 (_('Storage inodes'), val('storage_inodes')['text'], state('storage_inodes')),
135 (_('Storage inodes'), val('storage_inodes')['text'], state('storage_inodes')),
137
136
138 (_('Gist storage location'), val('storage_gist')['path'], state('storage_gist')),
137 (_('Gist storage location'), val('storage_gist')['path'], state('storage_gist')),
139 (_('Gist storage info'), val('storage_gist')['text'], state('storage_gist')),
138 (_('Gist storage info'), val('storage_gist')['text'], state('storage_gist')),
140
139
141 (_('Archive cache storage location'), val('storage_archive')['path'], state('storage_archive')),
140 (_('Archive cache storage location'), val('storage_archive')['path'], state('storage_archive')),
142 (_('Archive cache info'), val('storage_archive')['text'], state('storage_archive')),
141 (_('Archive cache info'), val('storage_archive')['text'], state('storage_archive')),
143
142
144 (_('Temp storage location'), val('storage_temp')['path'], state('storage_temp')),
143 (_('Temp storage location'), val('storage_temp')['path'], state('storage_temp')),
145 (_('Temp storage info'), val('storage_temp')['text'], state('storage_temp')),
144 (_('Temp storage info'), val('storage_temp')['text'], state('storage_temp')),
146
145
147 (_('Search info'), val('search')['text'], state('search')),
146 (_('Search info'), val('search')['text'], state('search')),
148 (_('Search location'), val('search')['location'], state('search')),
147 (_('Search location'), val('search')['location'], state('search')),
149 ('', '', ''), # spacer
148 ('', '', ''), # spacer
150
149
151 # VCS specific
150 # VCS specific
152 (_('VCS Backends'), val('vcs_backends'), state('vcs_backends')),
151 (_('VCS Backends'), val('vcs_backends'), state('vcs_backends')),
153 (_('VCS Server'), val('vcs_server')['text'], state('vcs_server')),
152 (_('VCS Server'), val('vcs_server')['text'], state('vcs_server')),
154 (_('GIT'), val('git'), state('git')),
153 (_('GIT'), val('git'), state('git')),
155 (_('HG'), val('hg'), state('hg')),
154 (_('HG'), val('hg'), state('hg')),
156 (_('SVN'), val('svn'), state('svn')),
155 (_('SVN'), val('svn'), state('svn')),
157
156
158 ]
157 ]
159
158
160 if snapshot:
159 if snapshot:
161 if c.allowed_to_snapshot:
160 if c.allowed_to_snapshot:
162 c.data_items.pop(0) # remove server info
161 c.data_items.pop(0) # remove server info
163 self.request.override_renderer = 'admin/settings/settings_system_snapshot.mako'
162 self.request.override_renderer = 'admin/settings/settings_system_snapshot.mako'
164 else:
163 else:
165 self.request.session.flash(
164 self.request.session.flash(
166 'You are not allowed to do this', queue='warning')
165 'You are not allowed to do this', queue='warning')
167 return {}
166 return {}
168
167
169 @LoginRequired()
168 @LoginRequired()
170 @HasPermissionAllDecorator('hg.admin')
169 @HasPermissionAllDecorator('hg.admin')
171 @view_config(
170 @view_config(
172 route_name='admin_settings_system_update', request_method='GET',
171 route_name='admin_settings_system_update', request_method='GET',
173 renderer='rhodecode:templates/admin/settings/settings_system_update.mako')
172 renderer='rhodecode:templates/admin/settings/settings_system_update.mako')
174 def settings_system_info_check_update(self):
173 def settings_system_info_check_update(self):
175 _ = self.request.translate
174 _ = self.request.translate
176
175
177 update_url = self.get_update_url()
176 update_url = self.get_update_url()
178
177
179 _err = lambda s: '<div style="color:#ff8888; padding:4px 0px">{}</div>'.format(s)
178 _err = lambda s: '<div style="color:#ff8888; padding:4px 0px">{}</div>'.format(s)
180 try:
179 try:
181 data = self.get_update_data(update_url)
180 data = self.get_update_data(update_url)
182 except urllib2.URLError as e:
181 except urllib2.URLError as e:
183 log.exception("Exception contacting upgrade server")
182 log.exception("Exception contacting upgrade server")
184 self.request.override_renderer = 'string'
183 self.request.override_renderer = 'string'
185 return _err('Failed to contact upgrade server: %r' % e)
184 return _err('Failed to contact upgrade server: %r' % e)
186 except ValueError as e:
185 except ValueError as e:
187 log.exception("Bad data sent from update server")
186 log.exception("Bad data sent from update server")
188 self.request.override_renderer = 'string'
187 self.request.override_renderer = 'string'
189 return _err('Bad data sent from update server')
188 return _err('Bad data sent from update server')
190
189
191 latest = data['versions'][0]
190 latest = data['versions'][0]
192
191
193 c.update_url = update_url
192 c.update_url = update_url
194 c.latest_data = latest
193 c.latest_data = latest
195 c.latest_ver = latest['version']
194 c.latest_ver = latest['version']
196 c.cur_ver = rhodecode.__version__
195 c.cur_ver = rhodecode.__version__
197 c.should_upgrade = False
196 c.should_upgrade = False
198
197
199 if (packaging.version.Version(c.latest_ver) >
198 if (packaging.version.Version(c.latest_ver) >
200 packaging.version.Version(c.cur_ver)):
199 packaging.version.Version(c.cur_ver)):
201 c.should_upgrade = True
200 c.should_upgrade = True
202 c.important_notices = latest['general']
201 c.important_notices = latest['general']
203
202
204 return {}
203 return {}
@@ -1,499 +1,501 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
57
58
58
59 log = logging.getLogger(__name__)
59 log = logging.getLogger(__name__)
60
60
61
61
62 # this is used to avoid avoid the route lookup overhead in routesmiddleware
62 # this is used to avoid avoid the route lookup overhead in routesmiddleware
63 # for certain routes which won't go to pylons to - eg. static files, debugger
63 # for certain routes which won't go to pylons to - eg. static files, debugger
64 # it is only needed for the pylons migration and can be removed once complete
64 # it is only needed for the pylons migration and can be removed once complete
65 class SkippableRoutesMiddleware(RoutesMiddleware):
65 class SkippableRoutesMiddleware(RoutesMiddleware):
66 """ Routes middleware that allows you to skip prefixes """
66 """ Routes middleware that allows you to skip prefixes """
67
67
68 def __init__(self, *args, **kw):
68 def __init__(self, *args, **kw):
69 self.skip_prefixes = kw.pop('skip_prefixes', [])
69 self.skip_prefixes = kw.pop('skip_prefixes', [])
70 super(SkippableRoutesMiddleware, self).__init__(*args, **kw)
70 super(SkippableRoutesMiddleware, self).__init__(*args, **kw)
71
71
72 def __call__(self, environ, start_response):
72 def __call__(self, environ, start_response):
73 for prefix in self.skip_prefixes:
73 for prefix in self.skip_prefixes:
74 if environ['PATH_INFO'].startswith(prefix):
74 if environ['PATH_INFO'].startswith(prefix):
75 # added to avoid the case when a missing /_static route falls
75 # added to avoid the case when a missing /_static route falls
76 # through to pylons and causes an exception as pylons is
76 # through to pylons and causes an exception as pylons is
77 # expecting wsgiorg.routingargs to be set in the environ
77 # expecting wsgiorg.routingargs to be set in the environ
78 # by RoutesMiddleware.
78 # by RoutesMiddleware.
79 if 'wsgiorg.routing_args' not in environ:
79 if 'wsgiorg.routing_args' not in environ:
80 environ['wsgiorg.routing_args'] = (None, {})
80 environ['wsgiorg.routing_args'] = (None, {})
81 return self.app(environ, start_response)
81 return self.app(environ, start_response)
82
82
83 return super(SkippableRoutesMiddleware, self).__call__(
83 return super(SkippableRoutesMiddleware, self).__call__(
84 environ, start_response)
84 environ, start_response)
85
85
86
86
87 def make_app(global_conf, static_files=True, **app_conf):
87 def make_app(global_conf, static_files=True, **app_conf):
88 """Create a Pylons WSGI application and return it
88 """Create a Pylons WSGI application and return it
89
89
90 ``global_conf``
90 ``global_conf``
91 The inherited configuration for this application. Normally from
91 The inherited configuration for this application. Normally from
92 the [DEFAULT] section of the Paste ini file.
92 the [DEFAULT] section of the Paste ini file.
93
93
94 ``app_conf``
94 ``app_conf``
95 The application's local configuration. Normally specified in
95 The application's local configuration. Normally specified in
96 the [app:<name>] section of the Paste ini file (where <name>
96 the [app:<name>] section of the Paste ini file (where <name>
97 defaults to main).
97 defaults to main).
98
98
99 """
99 """
100 # Apply compatibility patches
100 # Apply compatibility patches
101 patches.kombu_1_5_1_python_2_7_11()
101 patches.kombu_1_5_1_python_2_7_11()
102 patches.inspect_getargspec()
102 patches.inspect_getargspec()
103
103
104 # Configure the Pylons environment
104 # Configure the Pylons environment
105 config = load_environment(global_conf, app_conf)
105 config = load_environment(global_conf, app_conf)
106
106
107 # The Pylons WSGI app
107 # The Pylons WSGI app
108 app = PylonsApp(config=config)
108 app = PylonsApp(config=config)
109 if rhodecode.is_test:
109 if rhodecode.is_test:
110 app = csrf.CSRFDetector(app)
110 app = csrf.CSRFDetector(app)
111
111
112 expected_origin = config.get('expected_origin')
112 expected_origin = config.get('expected_origin')
113 if expected_origin:
113 if expected_origin:
114 # The API can be accessed from other Origins.
114 # The API can be accessed from other Origins.
115 app = csrf.OriginChecker(app, expected_origin,
115 app = csrf.OriginChecker(app, expected_origin,
116 skip_urls=[routes.util.url_for('api')])
116 skip_urls=[routes.util.url_for('api')])
117
117
118 # Establish the Registry for this application
118 # Establish the Registry for this application
119 app = RegistryManager(app)
119 app = RegistryManager(app)
120
120
121 app.config = config
121 app.config = config
122
122
123 return app
123 return app
124
124
125
125
126 def make_pyramid_app(global_config, **settings):
126 def make_pyramid_app(global_config, **settings):
127 """
127 """
128 Constructs the WSGI application based on Pyramid and wraps the Pylons based
128 Constructs the WSGI application based on Pyramid and wraps the Pylons based
129 application.
129 application.
130
130
131 Specials:
131 Specials:
132
132
133 * We migrate from Pylons to Pyramid. While doing this, we keep both
133 * We migrate from Pylons to Pyramid. While doing this, we keep both
134 frameworks functional. This involves moving some WSGI middlewares around
134 frameworks functional. This involves moving some WSGI middlewares around
135 and providing access to some data internals, so that the old code is
135 and providing access to some data internals, so that the old code is
136 still functional.
136 still functional.
137
137
138 * The application can also be integrated like a plugin via the call to
138 * The application can also be integrated like a plugin via the call to
139 `includeme`. This is accompanied with the other utility functions which
139 `includeme`. This is accompanied with the other utility functions which
140 are called. Changing this should be done with great care to not break
140 are called. Changing this should be done with great care to not break
141 cases when these fragments are assembled from another place.
141 cases when these fragments are assembled from another place.
142
142
143 """
143 """
144 # The edition string should be available in pylons too, so we add it here
144 # The edition string should be available in pylons too, so we add it here
145 # before copying the settings.
145 # before copying the settings.
146 settings.setdefault('rhodecode.edition', 'Community Edition')
146 settings.setdefault('rhodecode.edition', 'Community Edition')
147
147
148 # As long as our Pylons application does expect "unprepared" settings, make
148 # As long as our Pylons application does expect "unprepared" settings, make
149 # sure that we keep an unmodified copy. This avoids unintentional change of
149 # sure that we keep an unmodified copy. This avoids unintentional change of
150 # behavior in the old application.
150 # behavior in the old application.
151 settings_pylons = settings.copy()
151 settings_pylons = settings.copy()
152
152
153 sanitize_settings_and_apply_defaults(settings)
153 sanitize_settings_and_apply_defaults(settings)
154 config = Configurator(settings=settings)
154 config = Configurator(settings=settings)
155 add_pylons_compat_data(config.registry, global_config, settings_pylons)
155 add_pylons_compat_data(config.registry, global_config, settings_pylons)
156
156
157 load_pyramid_environment(global_config, settings)
157 load_pyramid_environment(global_config, settings)
158
158
159 includeme_first(config)
159 includeme_first(config)
160 includeme(config)
160 includeme(config)
161 pyramid_app = config.make_wsgi_app()
161 pyramid_app = config.make_wsgi_app()
162 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
162 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
163 pyramid_app.config = config
163 pyramid_app.config = config
164
164
165 # creating the app uses a connection - return it after we are done
165 # creating the app uses a connection - return it after we are done
166 meta.Session.remove()
166 meta.Session.remove()
167
167
168 return pyramid_app
168 return pyramid_app
169
169
170
170
171 def make_not_found_view(config):
171 def make_not_found_view(config):
172 """
172 """
173 This creates the view which should be registered as not-found-view to
173 This creates the view which should be registered as not-found-view to
174 pyramid. Basically it contains of the old pylons app, converted to a view.
174 pyramid. Basically it contains of the old pylons app, converted to a view.
175 Additionally it is wrapped by some other middlewares.
175 Additionally it is wrapped by some other middlewares.
176 """
176 """
177 settings = config.registry.settings
177 settings = config.registry.settings
178 vcs_server_enabled = settings['vcs.server.enable']
178 vcs_server_enabled = settings['vcs.server.enable']
179
179
180 # Make pylons app from unprepared settings.
180 # Make pylons app from unprepared settings.
181 pylons_app = make_app(
181 pylons_app = make_app(
182 config.registry._pylons_compat_global_config,
182 config.registry._pylons_compat_global_config,
183 **config.registry._pylons_compat_settings)
183 **config.registry._pylons_compat_settings)
184 config.registry._pylons_compat_config = pylons_app.config
184 config.registry._pylons_compat_config = pylons_app.config
185
185
186 # Appenlight monitoring.
186 # Appenlight monitoring.
187 pylons_app, appenlight_client = wrap_in_appenlight_if_enabled(
187 pylons_app, appenlight_client = wrap_in_appenlight_if_enabled(
188 pylons_app, settings)
188 pylons_app, settings)
189
189
190 # The pylons app is executed inside of the pyramid 404 exception handler.
190 # The pylons app is executed inside of the pyramid 404 exception handler.
191 # Exceptions which are raised inside of it are not handled by pyramid
191 # Exceptions which are raised inside of it are not handled by pyramid
192 # again. Therefore we add a middleware that invokes the error handler in
192 # again. Therefore we add a middleware that invokes the error handler in
193 # case of an exception or error response. This way we return proper error
193 # case of an exception or error response. This way we return proper error
194 # HTML pages in case of an error.
194 # HTML pages in case of an error.
195 reraise = (settings.get('debugtoolbar.enabled', False) or
195 reraise = (settings.get('debugtoolbar.enabled', False) or
196 rhodecode.disable_error_handler)
196 rhodecode.disable_error_handler)
197 pylons_app = PylonsErrorHandlingMiddleware(
197 pylons_app = PylonsErrorHandlingMiddleware(
198 pylons_app, error_handler, reraise)
198 pylons_app, error_handler, reraise)
199
199
200 # The VCSMiddleware shall operate like a fallback if pyramid doesn't find a
200 # The VCSMiddleware shall operate like a fallback if pyramid doesn't find a
201 # view to handle the request. Therefore it is wrapped around the pylons
201 # view to handle the request. Therefore it is wrapped around the pylons
202 # app. It has to be outside of the error handling otherwise error responses
202 # app. It has to be outside of the error handling otherwise error responses
203 # from the vcsserver are converted to HTML error pages. This confuses the
203 # from the vcsserver are converted to HTML error pages. This confuses the
204 # command line tools and the user won't get a meaningful error message.
204 # command line tools and the user won't get a meaningful error message.
205 if vcs_server_enabled:
205 if vcs_server_enabled:
206 pylons_app = VCSMiddleware(
206 pylons_app = VCSMiddleware(
207 pylons_app, settings, appenlight_client, registry=config.registry)
207 pylons_app, settings, appenlight_client, registry=config.registry)
208
208
209 # Convert WSGI app to pyramid view and return it.
209 # Convert WSGI app to pyramid view and return it.
210 return wsgiapp(pylons_app)
210 return wsgiapp(pylons_app)
211
211
212
212
213 def add_pylons_compat_data(registry, global_config, settings):
213 def add_pylons_compat_data(registry, global_config, settings):
214 """
214 """
215 Attach data to the registry to support the Pylons integration.
215 Attach data to the registry to support the Pylons integration.
216 """
216 """
217 registry._pylons_compat_global_config = global_config
217 registry._pylons_compat_global_config = global_config
218 registry._pylons_compat_settings = settings
218 registry._pylons_compat_settings = settings
219
219
220
220
221 def error_handler(exception, request):
221 def error_handler(exception, request):
222 import rhodecode
222 import rhodecode
223 from rhodecode.lib.utils2 import AttributeDict
223 from rhodecode.lib.utils2 import AttributeDict
224
224
225 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
225 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
226
226
227 base_response = HTTPInternalServerError()
227 base_response = HTTPInternalServerError()
228 # prefer original exception for the response since it may have headers set
228 # prefer original exception for the response since it may have headers set
229 if isinstance(exception, HTTPException):
229 if isinstance(exception, HTTPException):
230 base_response = exception
230 base_response = exception
231
231
232 def is_http_error(response):
232 def is_http_error(response):
233 # error which should have traceback
233 # error which should have traceback
234 return response.status_code > 499
234 return response.status_code > 499
235
235
236 if is_http_error(base_response):
236 if is_http_error(base_response):
237 log.exception(
237 log.exception(
238 'error occurred handling this request for path: %s', request.path)
238 'error occurred handling this request for path: %s', request.path)
239
239
240 c = AttributeDict()
240 c = AttributeDict()
241 c.error_message = base_response.status
241 c.error_message = base_response.status
242 c.error_explanation = base_response.explanation or str(base_response)
242 c.error_explanation = base_response.explanation or str(base_response)
243 c.visual = AttributeDict()
243 c.visual = AttributeDict()
244
244
245 c.visual.rhodecode_support_url = (
245 c.visual.rhodecode_support_url = (
246 request.registry.settings.get('rhodecode_support_url') or
246 request.registry.settings.get('rhodecode_support_url') or
247 request.route_url('rhodecode_support')
247 request.route_url('rhodecode_support')
248 )
248 )
249 c.redirect_time = 0
249 c.redirect_time = 0
250 c.rhodecode_name = rhodecode_title
250 c.rhodecode_name = rhodecode_title
251 if not c.rhodecode_name:
251 if not c.rhodecode_name:
252 c.rhodecode_name = 'Rhodecode'
252 c.rhodecode_name = 'Rhodecode'
253
253
254 c.causes = []
254 c.causes = []
255 if hasattr(base_response, 'causes'):
255 if hasattr(base_response, 'causes'):
256 c.causes = base_response.causes
256 c.causes = base_response.causes
257
257
258 response = render_to_response(
258 response = render_to_response(
259 '/errors/error_document.mako', {'c': c}, request=request,
259 '/errors/error_document.mako', {'c': c}, 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 config.include('rhodecode.channelstream')
281 config.include('rhodecode.channelstream')
281 config.include('rhodecode.admin')
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.admin')
286 config.include('rhodecode.apps.login')
287 config.include('rhodecode.apps.login')
287 config.include('rhodecode.apps.user_profile')
288 config.include('rhodecode.apps.user_profile')
288
289
289 config.include('rhodecode.tweens')
290 config.include('rhodecode.tweens')
290 config.include('rhodecode.api')
291 config.include('rhodecode.api')
291 config.include('rhodecode.svn_support')
292 config.include('rhodecode.svn_support')
293
292 config.add_route(
294 config.add_route(
293 'rhodecode_support', 'https://rhodecode.com/help/', static=True)
295 'rhodecode_support', 'https://rhodecode.com/help/', static=True)
294
296
295 config.add_translation_dirs('rhodecode:i18n/')
297 config.add_translation_dirs('rhodecode:i18n/')
296 settings['default_locale_name'] = settings.get('lang', 'en')
298 settings['default_locale_name'] = settings.get('lang', 'en')
297
299
298 # Add subscribers.
300 # Add subscribers.
299 config.add_subscriber(scan_repositories_if_enabled, ApplicationCreated)
301 config.add_subscriber(scan_repositories_if_enabled, ApplicationCreated)
300 config.add_subscriber(write_metadata_if_needed, ApplicationCreated)
302 config.add_subscriber(write_metadata_if_needed, ApplicationCreated)
301
303
302 # Set the authorization policy.
304 # Set the authorization policy.
303 authz_policy = ACLAuthorizationPolicy()
305 authz_policy = ACLAuthorizationPolicy()
304 config.set_authorization_policy(authz_policy)
306 config.set_authorization_policy(authz_policy)
305
307
306 # Set the default renderer for HTML templates to mako.
308 # Set the default renderer for HTML templates to mako.
307 config.add_mako_renderer('.html')
309 config.add_mako_renderer('.html')
308
310
309 # include RhodeCode plugins
311 # include RhodeCode plugins
310 includes = aslist(settings.get('rhodecode.includes', []))
312 includes = aslist(settings.get('rhodecode.includes', []))
311 for inc in includes:
313 for inc in includes:
312 config.include(inc)
314 config.include(inc)
313
315
314 # This is the glue which allows us to migrate in chunks. By registering the
316 # This is the glue which allows us to migrate in chunks. By registering the
315 # pylons based application as the "Not Found" view in Pyramid, we will
317 # pylons based application as the "Not Found" view in Pyramid, we will
316 # fallback to the old application each time the new one does not yet know
318 # fallback to the old application each time the new one does not yet know
317 # how to handle a request.
319 # how to handle a request.
318 config.add_notfound_view(make_not_found_view(config))
320 config.add_notfound_view(make_not_found_view(config))
319
321
320 if not settings.get('debugtoolbar.enabled', False):
322 if not settings.get('debugtoolbar.enabled', False):
321 # if no toolbar, then any exception gets caught and rendered
323 # if no toolbar, then any exception gets caught and rendered
322 config.add_view(error_handler, context=Exception)
324 config.add_view(error_handler, context=Exception)
323
325
324 config.add_view(error_handler, context=HTTPError)
326 config.add_view(error_handler, context=HTTPError)
325
327
326
328
327 def includeme_first(config):
329 def includeme_first(config):
328 # redirect automatic browser favicon.ico requests to correct place
330 # redirect automatic browser favicon.ico requests to correct place
329 def favicon_redirect(context, request):
331 def favicon_redirect(context, request):
330 return HTTPFound(
332 return HTTPFound(
331 request.static_path('rhodecode:public/images/favicon.ico'))
333 request.static_path('rhodecode:public/images/favicon.ico'))
332
334
333 config.add_view(favicon_redirect, route_name='favicon')
335 config.add_view(favicon_redirect, route_name='favicon')
334 config.add_route('favicon', '/favicon.ico')
336 config.add_route('favicon', '/favicon.ico')
335
337
336 def robots_redirect(context, request):
338 def robots_redirect(context, request):
337 return HTTPFound(
339 return HTTPFound(
338 request.static_path('rhodecode:public/robots.txt'))
340 request.static_path('rhodecode:public/robots.txt'))
339
341
340 config.add_view(robots_redirect, route_name='robots')
342 config.add_view(robots_redirect, route_name='robots')
341 config.add_route('robots', '/robots.txt')
343 config.add_route('robots', '/robots.txt')
342
344
343 config.add_static_view(
345 config.add_static_view(
344 '_static/deform', 'deform:static')
346 '_static/deform', 'deform:static')
345 config.add_static_view(
347 config.add_static_view(
346 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
348 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
347
349
348
350
349 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
351 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
350 """
352 """
351 Apply outer WSGI middlewares around the application.
353 Apply outer WSGI middlewares around the application.
352
354
353 Part of this has been moved up from the Pylons layer, so that the
355 Part of this has been moved up from the Pylons layer, so that the
354 data is also available if old Pylons code is hit through an already ported
356 data is also available if old Pylons code is hit through an already ported
355 view.
357 view.
356 """
358 """
357 settings = config.registry.settings
359 settings = config.registry.settings
358
360
359 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
361 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
360 pyramid_app = HttpsFixup(pyramid_app, settings)
362 pyramid_app = HttpsFixup(pyramid_app, settings)
361
363
362 # Add RoutesMiddleware to support the pylons compatibility tween during
364 # Add RoutesMiddleware to support the pylons compatibility tween during
363 # migration to pyramid.
365 # migration to pyramid.
364 pyramid_app = SkippableRoutesMiddleware(
366 pyramid_app = SkippableRoutesMiddleware(
365 pyramid_app, config.registry._pylons_compat_config['routes.map'],
367 pyramid_app, config.registry._pylons_compat_config['routes.map'],
366 skip_prefixes=(STATIC_FILE_PREFIX, '/_debug_toolbar'))
368 skip_prefixes=(STATIC_FILE_PREFIX, '/_debug_toolbar'))
367
369
368 pyramid_app, _ = wrap_in_appenlight_if_enabled(pyramid_app, settings)
370 pyramid_app, _ = wrap_in_appenlight_if_enabled(pyramid_app, settings)
369
371
370 if settings['gzip_responses']:
372 if settings['gzip_responses']:
371 pyramid_app = make_gzip_middleware(
373 pyramid_app = make_gzip_middleware(
372 pyramid_app, settings, compress_level=1)
374 pyramid_app, settings, compress_level=1)
373
375
374 # this should be the outer most middleware in the wsgi stack since
376 # this should be the outer most middleware in the wsgi stack since
375 # middleware like Routes make database calls
377 # middleware like Routes make database calls
376 def pyramid_app_with_cleanup(environ, start_response):
378 def pyramid_app_with_cleanup(environ, start_response):
377 try:
379 try:
378 return pyramid_app(environ, start_response)
380 return pyramid_app(environ, start_response)
379 finally:
381 finally:
380 # Dispose current database session and rollback uncommitted
382 # Dispose current database session and rollback uncommitted
381 # transactions.
383 # transactions.
382 meta.Session.remove()
384 meta.Session.remove()
383
385
384 # In a single threaded mode server, on non sqlite db we should have
386 # In a single threaded mode server, on non sqlite db we should have
385 # '0 Current Checked out connections' at the end of a request,
387 # '0 Current Checked out connections' at the end of a request,
386 # if not, then something, somewhere is leaving a connection open
388 # if not, then something, somewhere is leaving a connection open
387 pool = meta.Base.metadata.bind.engine.pool
389 pool = meta.Base.metadata.bind.engine.pool
388 log.debug('sa pool status: %s', pool.status())
390 log.debug('sa pool status: %s', pool.status())
389
391
390
392
391 return pyramid_app_with_cleanup
393 return pyramid_app_with_cleanup
392
394
393
395
394 def sanitize_settings_and_apply_defaults(settings):
396 def sanitize_settings_and_apply_defaults(settings):
395 """
397 """
396 Applies settings defaults and does all type conversion.
398 Applies settings defaults and does all type conversion.
397
399
398 We would move all settings parsing and preparation into this place, so that
400 We would move all settings parsing and preparation into this place, so that
399 we have only one place left which deals with this part. The remaining parts
401 we have only one place left which deals with this part. The remaining parts
400 of the application would start to rely fully on well prepared settings.
402 of the application would start to rely fully on well prepared settings.
401
403
402 This piece would later be split up per topic to avoid a big fat monster
404 This piece would later be split up per topic to avoid a big fat monster
403 function.
405 function.
404 """
406 """
405
407
406 # Pyramid's mako renderer has to search in the templates folder so that the
408 # Pyramid's mako renderer has to search in the templates folder so that the
407 # old templates still work. Ported and new templates are expected to use
409 # old templates still work. Ported and new templates are expected to use
408 # real asset specifications for the includes.
410 # real asset specifications for the includes.
409 mako_directories = settings.setdefault('mako.directories', [
411 mako_directories = settings.setdefault('mako.directories', [
410 # Base templates of the original Pylons application
412 # Base templates of the original Pylons application
411 'rhodecode:templates',
413 'rhodecode:templates',
412 ])
414 ])
413 log.debug(
415 log.debug(
414 "Using the following Mako template directories: %s",
416 "Using the following Mako template directories: %s",
415 mako_directories)
417 mako_directories)
416
418
417 # Default includes, possible to change as a user
419 # Default includes, possible to change as a user
418 pyramid_includes = settings.setdefault('pyramid.includes', [
420 pyramid_includes = settings.setdefault('pyramid.includes', [
419 'rhodecode.lib.middleware.request_wrapper',
421 'rhodecode.lib.middleware.request_wrapper',
420 ])
422 ])
421 log.debug(
423 log.debug(
422 "Using the following pyramid.includes: %s",
424 "Using the following pyramid.includes: %s",
423 pyramid_includes)
425 pyramid_includes)
424
426
425 # TODO: johbo: Re-think this, usually the call to config.include
427 # TODO: johbo: Re-think this, usually the call to config.include
426 # should allow to pass in a prefix.
428 # should allow to pass in a prefix.
427 settings.setdefault('rhodecode.api.url', '/_admin/api')
429 settings.setdefault('rhodecode.api.url', '/_admin/api')
428
430
429 # Sanitize generic settings.
431 # Sanitize generic settings.
430 _list_setting(settings, 'default_encoding', 'UTF-8')
432 _list_setting(settings, 'default_encoding', 'UTF-8')
431 _bool_setting(settings, 'is_test', 'false')
433 _bool_setting(settings, 'is_test', 'false')
432 _bool_setting(settings, 'gzip_responses', 'false')
434 _bool_setting(settings, 'gzip_responses', 'false')
433
435
434 # Call split out functions that sanitize settings for each topic.
436 # Call split out functions that sanitize settings for each topic.
435 _sanitize_appenlight_settings(settings)
437 _sanitize_appenlight_settings(settings)
436 _sanitize_vcs_settings(settings)
438 _sanitize_vcs_settings(settings)
437
439
438 return settings
440 return settings
439
441
440
442
441 def _sanitize_appenlight_settings(settings):
443 def _sanitize_appenlight_settings(settings):
442 _bool_setting(settings, 'appenlight', 'false')
444 _bool_setting(settings, 'appenlight', 'false')
443
445
444
446
445 def _sanitize_vcs_settings(settings):
447 def _sanitize_vcs_settings(settings):
446 """
448 """
447 Applies settings defaults and does type conversion for all VCS related
449 Applies settings defaults and does type conversion for all VCS related
448 settings.
450 settings.
449 """
451 """
450 _string_setting(settings, 'vcs.svn.compatible_version', '')
452 _string_setting(settings, 'vcs.svn.compatible_version', '')
451 _string_setting(settings, 'git_rev_filter', '--all')
453 _string_setting(settings, 'git_rev_filter', '--all')
452 _string_setting(settings, 'vcs.hooks.protocol', 'http')
454 _string_setting(settings, 'vcs.hooks.protocol', 'http')
453 _string_setting(settings, 'vcs.scm_app_implementation', 'http')
455 _string_setting(settings, 'vcs.scm_app_implementation', 'http')
454 _string_setting(settings, 'vcs.server', '')
456 _string_setting(settings, 'vcs.server', '')
455 _string_setting(settings, 'vcs.server.log_level', 'debug')
457 _string_setting(settings, 'vcs.server.log_level', 'debug')
456 _string_setting(settings, 'vcs.server.protocol', 'http')
458 _string_setting(settings, 'vcs.server.protocol', 'http')
457 _bool_setting(settings, 'startup.import_repos', 'false')
459 _bool_setting(settings, 'startup.import_repos', 'false')
458 _bool_setting(settings, 'vcs.hooks.direct_calls', 'false')
460 _bool_setting(settings, 'vcs.hooks.direct_calls', 'false')
459 _bool_setting(settings, 'vcs.server.enable', 'true')
461 _bool_setting(settings, 'vcs.server.enable', 'true')
460 _bool_setting(settings, 'vcs.start_server', 'false')
462 _bool_setting(settings, 'vcs.start_server', 'false')
461 _list_setting(settings, 'vcs.backends', 'hg, git, svn')
463 _list_setting(settings, 'vcs.backends', 'hg, git, svn')
462 _int_setting(settings, 'vcs.connection_timeout', 3600)
464 _int_setting(settings, 'vcs.connection_timeout', 3600)
463
465
464 # Support legacy values of vcs.scm_app_implementation. Legacy
466 # Support legacy values of vcs.scm_app_implementation. Legacy
465 # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http'
467 # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http'
466 # which is now mapped to 'http'.
468 # which is now mapped to 'http'.
467 scm_app_impl = settings['vcs.scm_app_implementation']
469 scm_app_impl = settings['vcs.scm_app_implementation']
468 if scm_app_impl == 'rhodecode.lib.middleware.utils.scm_app_http':
470 if scm_app_impl == 'rhodecode.lib.middleware.utils.scm_app_http':
469 settings['vcs.scm_app_implementation'] = 'http'
471 settings['vcs.scm_app_implementation'] = 'http'
470
472
471
473
472 def _int_setting(settings, name, default):
474 def _int_setting(settings, name, default):
473 settings[name] = int(settings.get(name, default))
475 settings[name] = int(settings.get(name, default))
474
476
475
477
476 def _bool_setting(settings, name, default):
478 def _bool_setting(settings, name, default):
477 input = settings.get(name, default)
479 input = settings.get(name, default)
478 if isinstance(input, unicode):
480 if isinstance(input, unicode):
479 input = input.encode('utf8')
481 input = input.encode('utf8')
480 settings[name] = asbool(input)
482 settings[name] = asbool(input)
481
483
482
484
483 def _list_setting(settings, name, default):
485 def _list_setting(settings, name, default):
484 raw_value = settings.get(name, default)
486 raw_value = settings.get(name, default)
485
487
486 old_separator = ','
488 old_separator = ','
487 if old_separator in raw_value:
489 if old_separator in raw_value:
488 # If we get a comma separated list, pass it to our own function.
490 # If we get a comma separated list, pass it to our own function.
489 settings[name] = rhodecode_aslist(raw_value, sep=old_separator)
491 settings[name] = rhodecode_aslist(raw_value, sep=old_separator)
490 else:
492 else:
491 # Otherwise we assume it uses pyramids space/newline separation.
493 # Otherwise we assume it uses pyramids space/newline separation.
492 settings[name] = aslist(raw_value)
494 settings[name] = aslist(raw_value)
493
495
494
496
495 def _string_setting(settings, name, default, lower=True):
497 def _string_setting(settings, name, default, lower=True):
496 value = settings.get(name, default)
498 value = settings.get(name, default)
497 if lower:
499 if lower:
498 value = value.lower()
500 value = value.lower()
499 settings[name] = value
501 settings[name] = value
@@ -1,692 +1,692 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 """
22 """
23 settings controller for rhodecode admin
23 settings controller for rhodecode admin
24 """
24 """
25
25
26 import collections
26 import collections
27 import logging
27 import logging
28
28
29 import datetime
29 import datetime
30 import formencode
30 import formencode
31 from formencode import htmlfill
31 from formencode import htmlfill
32 from pylons import request, tmpl_context as c, url, config
32 from pylons import request, tmpl_context as c, url, config
33 from pylons.controllers.util import redirect
33 from pylons.controllers.util import redirect
34 from pylons.i18n.translation import _
34 from pylons.i18n.translation import _
35 from pyramid.threadlocal import get_current_registry
35 from pyramid.threadlocal import get_current_registry
36 from webob.exc import HTTPBadRequest
36 from webob.exc import HTTPBadRequest
37
37
38 import rhodecode
38 import rhodecode
39 from rhodecode.admin.navigation import navigation_list
39 from rhodecode.apps.admin.navigation import navigation_list
40 from rhodecode.lib import auth
40 from rhodecode.lib import auth
41 from rhodecode.lib import helpers as h
41 from rhodecode.lib import helpers as h
42 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
42 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
43 from rhodecode.lib.base import BaseController, render
43 from rhodecode.lib.base import BaseController, render
44 from rhodecode.lib.celerylib import tasks, run_task
44 from rhodecode.lib.celerylib import tasks, run_task
45 from rhodecode.lib.utils import repo2db_mapper
45 from rhodecode.lib.utils import repo2db_mapper
46 from rhodecode.lib.utils2 import (
46 from rhodecode.lib.utils2 import (
47 str2bool, safe_unicode, AttributeDict, safe_int)
47 str2bool, safe_unicode, AttributeDict, safe_int)
48 from rhodecode.lib.compat import OrderedDict
48 from rhodecode.lib.compat import OrderedDict
49 from rhodecode.lib.utils import jsonify
49 from rhodecode.lib.utils import jsonify
50
50
51 from rhodecode.model.db import RhodeCodeUi, Repository
51 from rhodecode.model.db import RhodeCodeUi, Repository
52 from rhodecode.model.forms import ApplicationSettingsForm, \
52 from rhodecode.model.forms import ApplicationSettingsForm, \
53 ApplicationUiSettingsForm, ApplicationVisualisationForm, \
53 ApplicationUiSettingsForm, ApplicationVisualisationForm, \
54 LabsSettingsForm, IssueTrackerPatternsForm
54 LabsSettingsForm, IssueTrackerPatternsForm
55 from rhodecode.model.repo_group import RepoGroupModel
55 from rhodecode.model.repo_group import RepoGroupModel
56
56
57 from rhodecode.model.scm import ScmModel
57 from rhodecode.model.scm import ScmModel
58 from rhodecode.model.notification import EmailNotificationModel
58 from rhodecode.model.notification import EmailNotificationModel
59 from rhodecode.model.meta import Session
59 from rhodecode.model.meta import Session
60 from rhodecode.model.settings import (
60 from rhodecode.model.settings import (
61 IssueTrackerSettingsModel, VcsSettingsModel, SettingNotFound,
61 IssueTrackerSettingsModel, VcsSettingsModel, SettingNotFound,
62 SettingsModel)
62 SettingsModel)
63
63
64 from rhodecode.model.supervisor import SupervisorModel, SUPERVISOR_MASTER
64 from rhodecode.model.supervisor import SupervisorModel, SUPERVISOR_MASTER
65 from rhodecode.svn_support.config_keys import generate_config
65 from rhodecode.svn_support.config_keys import generate_config
66
66
67
67
68 log = logging.getLogger(__name__)
68 log = logging.getLogger(__name__)
69
69
70
70
71 class SettingsController(BaseController):
71 class SettingsController(BaseController):
72 """REST Controller styled on the Atom Publishing Protocol"""
72 """REST Controller styled on the Atom Publishing Protocol"""
73 # To properly map this controller, ensure your config/routing.py
73 # To properly map this controller, ensure your config/routing.py
74 # file has a resource setup:
74 # file has a resource setup:
75 # map.resource('setting', 'settings', controller='admin/settings',
75 # map.resource('setting', 'settings', controller='admin/settings',
76 # path_prefix='/admin', name_prefix='admin_')
76 # path_prefix='/admin', name_prefix='admin_')
77
77
78 @LoginRequired()
78 @LoginRequired()
79 def __before__(self):
79 def __before__(self):
80 super(SettingsController, self).__before__()
80 super(SettingsController, self).__before__()
81 c.labs_active = str2bool(
81 c.labs_active = str2bool(
82 rhodecode.CONFIG.get('labs_settings_active', 'true'))
82 rhodecode.CONFIG.get('labs_settings_active', 'true'))
83 c.navlist = navigation_list(request)
83 c.navlist = navigation_list(request)
84
84
85 def _get_hg_ui_settings(self):
85 def _get_hg_ui_settings(self):
86 ret = RhodeCodeUi.query().all()
86 ret = RhodeCodeUi.query().all()
87
87
88 if not ret:
88 if not ret:
89 raise Exception('Could not get application ui settings !')
89 raise Exception('Could not get application ui settings !')
90 settings = {}
90 settings = {}
91 for each in ret:
91 for each in ret:
92 k = each.ui_key
92 k = each.ui_key
93 v = each.ui_value
93 v = each.ui_value
94 if k == '/':
94 if k == '/':
95 k = 'root_path'
95 k = 'root_path'
96
96
97 if k in ['push_ssl', 'publish']:
97 if k in ['push_ssl', 'publish']:
98 v = str2bool(v)
98 v = str2bool(v)
99
99
100 if k.find('.') != -1:
100 if k.find('.') != -1:
101 k = k.replace('.', '_')
101 k = k.replace('.', '_')
102
102
103 if each.ui_section in ['hooks', 'extensions']:
103 if each.ui_section in ['hooks', 'extensions']:
104 v = each.ui_active
104 v = each.ui_active
105
105
106 settings[each.ui_section + '_' + k] = v
106 settings[each.ui_section + '_' + k] = v
107 return settings
107 return settings
108
108
109 @HasPermissionAllDecorator('hg.admin')
109 @HasPermissionAllDecorator('hg.admin')
110 @auth.CSRFRequired()
110 @auth.CSRFRequired()
111 @jsonify
111 @jsonify
112 def delete_svn_pattern(self):
112 def delete_svn_pattern(self):
113 if not request.is_xhr:
113 if not request.is_xhr:
114 raise HTTPBadRequest()
114 raise HTTPBadRequest()
115
115
116 delete_pattern_id = request.POST.get('delete_svn_pattern')
116 delete_pattern_id = request.POST.get('delete_svn_pattern')
117 model = VcsSettingsModel()
117 model = VcsSettingsModel()
118 try:
118 try:
119 model.delete_global_svn_pattern(delete_pattern_id)
119 model.delete_global_svn_pattern(delete_pattern_id)
120 except SettingNotFound:
120 except SettingNotFound:
121 raise HTTPBadRequest()
121 raise HTTPBadRequest()
122
122
123 Session().commit()
123 Session().commit()
124 return True
124 return True
125
125
126 @HasPermissionAllDecorator('hg.admin')
126 @HasPermissionAllDecorator('hg.admin')
127 @auth.CSRFRequired()
127 @auth.CSRFRequired()
128 def settings_vcs_update(self):
128 def settings_vcs_update(self):
129 """POST /admin/settings: All items in the collection"""
129 """POST /admin/settings: All items in the collection"""
130 # url('admin_settings_vcs')
130 # url('admin_settings_vcs')
131 c.active = 'vcs'
131 c.active = 'vcs'
132
132
133 model = VcsSettingsModel()
133 model = VcsSettingsModel()
134 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
134 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
135 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
135 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
136
136
137 # TODO: Replace with request.registry after migrating to pyramid.
137 # TODO: Replace with request.registry after migrating to pyramid.
138 pyramid_settings = get_current_registry().settings
138 pyramid_settings = get_current_registry().settings
139 c.svn_proxy_generate_config = pyramid_settings[generate_config]
139 c.svn_proxy_generate_config = pyramid_settings[generate_config]
140
140
141 application_form = ApplicationUiSettingsForm()()
141 application_form = ApplicationUiSettingsForm()()
142
142
143 try:
143 try:
144 form_result = application_form.to_python(dict(request.POST))
144 form_result = application_form.to_python(dict(request.POST))
145 except formencode.Invalid as errors:
145 except formencode.Invalid as errors:
146 h.flash(
146 h.flash(
147 _("Some form inputs contain invalid data."),
147 _("Some form inputs contain invalid data."),
148 category='error')
148 category='error')
149 return htmlfill.render(
149 return htmlfill.render(
150 render('admin/settings/settings.mako'),
150 render('admin/settings/settings.mako'),
151 defaults=errors.value,
151 defaults=errors.value,
152 errors=errors.error_dict or {},
152 errors=errors.error_dict or {},
153 prefix_error=False,
153 prefix_error=False,
154 encoding="UTF-8",
154 encoding="UTF-8",
155 force_defaults=False
155 force_defaults=False
156 )
156 )
157
157
158 try:
158 try:
159 if c.visual.allow_repo_location_change:
159 if c.visual.allow_repo_location_change:
160 model.update_global_path_setting(
160 model.update_global_path_setting(
161 form_result['paths_root_path'])
161 form_result['paths_root_path'])
162
162
163 model.update_global_ssl_setting(form_result['web_push_ssl'])
163 model.update_global_ssl_setting(form_result['web_push_ssl'])
164 model.update_global_hook_settings(form_result)
164 model.update_global_hook_settings(form_result)
165
165
166 model.create_or_update_global_svn_settings(form_result)
166 model.create_or_update_global_svn_settings(form_result)
167 model.create_or_update_global_hg_settings(form_result)
167 model.create_or_update_global_hg_settings(form_result)
168 model.create_or_update_global_pr_settings(form_result)
168 model.create_or_update_global_pr_settings(form_result)
169 except Exception:
169 except Exception:
170 log.exception("Exception while updating settings")
170 log.exception("Exception while updating settings")
171 h.flash(_('Error occurred during updating '
171 h.flash(_('Error occurred during updating '
172 'application settings'), category='error')
172 'application settings'), category='error')
173 else:
173 else:
174 Session().commit()
174 Session().commit()
175 h.flash(_('Updated VCS settings'), category='success')
175 h.flash(_('Updated VCS settings'), category='success')
176 return redirect(url('admin_settings_vcs'))
176 return redirect(url('admin_settings_vcs'))
177
177
178 return htmlfill.render(
178 return htmlfill.render(
179 render('admin/settings/settings.mako'),
179 render('admin/settings/settings.mako'),
180 defaults=self._form_defaults(),
180 defaults=self._form_defaults(),
181 encoding="UTF-8",
181 encoding="UTF-8",
182 force_defaults=False)
182 force_defaults=False)
183
183
184 @HasPermissionAllDecorator('hg.admin')
184 @HasPermissionAllDecorator('hg.admin')
185 def settings_vcs(self):
185 def settings_vcs(self):
186 """GET /admin/settings: All items in the collection"""
186 """GET /admin/settings: All items in the collection"""
187 # url('admin_settings_vcs')
187 # url('admin_settings_vcs')
188 c.active = 'vcs'
188 c.active = 'vcs'
189 model = VcsSettingsModel()
189 model = VcsSettingsModel()
190 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
190 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
191 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
191 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
192
192
193 # TODO: Replace with request.registry after migrating to pyramid.
193 # TODO: Replace with request.registry after migrating to pyramid.
194 pyramid_settings = get_current_registry().settings
194 pyramid_settings = get_current_registry().settings
195 c.svn_proxy_generate_config = pyramid_settings[generate_config]
195 c.svn_proxy_generate_config = pyramid_settings[generate_config]
196
196
197 return htmlfill.render(
197 return htmlfill.render(
198 render('admin/settings/settings.mako'),
198 render('admin/settings/settings.mako'),
199 defaults=self._form_defaults(),
199 defaults=self._form_defaults(),
200 encoding="UTF-8",
200 encoding="UTF-8",
201 force_defaults=False)
201 force_defaults=False)
202
202
203 @HasPermissionAllDecorator('hg.admin')
203 @HasPermissionAllDecorator('hg.admin')
204 @auth.CSRFRequired()
204 @auth.CSRFRequired()
205 def settings_mapping_update(self):
205 def settings_mapping_update(self):
206 """POST /admin/settings/mapping: All items in the collection"""
206 """POST /admin/settings/mapping: All items in the collection"""
207 # url('admin_settings_mapping')
207 # url('admin_settings_mapping')
208 c.active = 'mapping'
208 c.active = 'mapping'
209 rm_obsolete = request.POST.get('destroy', False)
209 rm_obsolete = request.POST.get('destroy', False)
210 invalidate_cache = request.POST.get('invalidate', False)
210 invalidate_cache = request.POST.get('invalidate', False)
211 log.debug(
211 log.debug(
212 'rescanning repo location with destroy obsolete=%s', rm_obsolete)
212 'rescanning repo location with destroy obsolete=%s', rm_obsolete)
213
213
214 if invalidate_cache:
214 if invalidate_cache:
215 log.debug('invalidating all repositories cache')
215 log.debug('invalidating all repositories cache')
216 for repo in Repository.get_all():
216 for repo in Repository.get_all():
217 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
217 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
218
218
219 filesystem_repos = ScmModel().repo_scan()
219 filesystem_repos = ScmModel().repo_scan()
220 added, removed = repo2db_mapper(filesystem_repos, rm_obsolete)
220 added, removed = repo2db_mapper(filesystem_repos, rm_obsolete)
221 _repr = lambda l: ', '.join(map(safe_unicode, l)) or '-'
221 _repr = lambda l: ', '.join(map(safe_unicode, l)) or '-'
222 h.flash(_('Repositories successfully '
222 h.flash(_('Repositories successfully '
223 'rescanned added: %s ; removed: %s') %
223 'rescanned added: %s ; removed: %s') %
224 (_repr(added), _repr(removed)),
224 (_repr(added), _repr(removed)),
225 category='success')
225 category='success')
226 return redirect(url('admin_settings_mapping'))
226 return redirect(url('admin_settings_mapping'))
227
227
228 @HasPermissionAllDecorator('hg.admin')
228 @HasPermissionAllDecorator('hg.admin')
229 def settings_mapping(self):
229 def settings_mapping(self):
230 """GET /admin/settings/mapping: All items in the collection"""
230 """GET /admin/settings/mapping: All items in the collection"""
231 # url('admin_settings_mapping')
231 # url('admin_settings_mapping')
232 c.active = 'mapping'
232 c.active = 'mapping'
233
233
234 return htmlfill.render(
234 return htmlfill.render(
235 render('admin/settings/settings.mako'),
235 render('admin/settings/settings.mako'),
236 defaults=self._form_defaults(),
236 defaults=self._form_defaults(),
237 encoding="UTF-8",
237 encoding="UTF-8",
238 force_defaults=False)
238 force_defaults=False)
239
239
240 @HasPermissionAllDecorator('hg.admin')
240 @HasPermissionAllDecorator('hg.admin')
241 @auth.CSRFRequired()
241 @auth.CSRFRequired()
242 def settings_global_update(self):
242 def settings_global_update(self):
243 """POST /admin/settings/global: All items in the collection"""
243 """POST /admin/settings/global: All items in the collection"""
244 # url('admin_settings_global')
244 # url('admin_settings_global')
245 c.active = 'global'
245 c.active = 'global'
246 c.personal_repo_group_default_pattern = RepoGroupModel()\
246 c.personal_repo_group_default_pattern = RepoGroupModel()\
247 .get_personal_group_name_pattern()
247 .get_personal_group_name_pattern()
248 application_form = ApplicationSettingsForm()()
248 application_form = ApplicationSettingsForm()()
249 try:
249 try:
250 form_result = application_form.to_python(dict(request.POST))
250 form_result = application_form.to_python(dict(request.POST))
251 except formencode.Invalid as errors:
251 except formencode.Invalid as errors:
252 return htmlfill.render(
252 return htmlfill.render(
253 render('admin/settings/settings.mako'),
253 render('admin/settings/settings.mako'),
254 defaults=errors.value,
254 defaults=errors.value,
255 errors=errors.error_dict or {},
255 errors=errors.error_dict or {},
256 prefix_error=False,
256 prefix_error=False,
257 encoding="UTF-8",
257 encoding="UTF-8",
258 force_defaults=False)
258 force_defaults=False)
259
259
260 try:
260 try:
261 settings = [
261 settings = [
262 ('title', 'rhodecode_title', 'unicode'),
262 ('title', 'rhodecode_title', 'unicode'),
263 ('realm', 'rhodecode_realm', 'unicode'),
263 ('realm', 'rhodecode_realm', 'unicode'),
264 ('pre_code', 'rhodecode_pre_code', 'unicode'),
264 ('pre_code', 'rhodecode_pre_code', 'unicode'),
265 ('post_code', 'rhodecode_post_code', 'unicode'),
265 ('post_code', 'rhodecode_post_code', 'unicode'),
266 ('captcha_public_key', 'rhodecode_captcha_public_key', 'unicode'),
266 ('captcha_public_key', 'rhodecode_captcha_public_key', 'unicode'),
267 ('captcha_private_key', 'rhodecode_captcha_private_key', 'unicode'),
267 ('captcha_private_key', 'rhodecode_captcha_private_key', 'unicode'),
268 ('create_personal_repo_group', 'rhodecode_create_personal_repo_group', 'bool'),
268 ('create_personal_repo_group', 'rhodecode_create_personal_repo_group', 'bool'),
269 ('personal_repo_group_pattern', 'rhodecode_personal_repo_group_pattern', 'unicode'),
269 ('personal_repo_group_pattern', 'rhodecode_personal_repo_group_pattern', 'unicode'),
270 ]
270 ]
271 for setting, form_key, type_ in settings:
271 for setting, form_key, type_ in settings:
272 sett = SettingsModel().create_or_update_setting(
272 sett = SettingsModel().create_or_update_setting(
273 setting, form_result[form_key], type_)
273 setting, form_result[form_key], type_)
274 Session().add(sett)
274 Session().add(sett)
275
275
276 Session().commit()
276 Session().commit()
277 SettingsModel().invalidate_settings_cache()
277 SettingsModel().invalidate_settings_cache()
278 h.flash(_('Updated application settings'), category='success')
278 h.flash(_('Updated application settings'), category='success')
279 except Exception:
279 except Exception:
280 log.exception("Exception while updating application settings")
280 log.exception("Exception while updating application settings")
281 h.flash(
281 h.flash(
282 _('Error occurred during updating application settings'),
282 _('Error occurred during updating application settings'),
283 category='error')
283 category='error')
284
284
285 return redirect(url('admin_settings_global'))
285 return redirect(url('admin_settings_global'))
286
286
287 @HasPermissionAllDecorator('hg.admin')
287 @HasPermissionAllDecorator('hg.admin')
288 def settings_global(self):
288 def settings_global(self):
289 """GET /admin/settings/global: All items in the collection"""
289 """GET /admin/settings/global: All items in the collection"""
290 # url('admin_settings_global')
290 # url('admin_settings_global')
291 c.active = 'global'
291 c.active = 'global'
292 c.personal_repo_group_default_pattern = RepoGroupModel()\
292 c.personal_repo_group_default_pattern = RepoGroupModel()\
293 .get_personal_group_name_pattern()
293 .get_personal_group_name_pattern()
294
294
295 return htmlfill.render(
295 return htmlfill.render(
296 render('admin/settings/settings.mako'),
296 render('admin/settings/settings.mako'),
297 defaults=self._form_defaults(),
297 defaults=self._form_defaults(),
298 encoding="UTF-8",
298 encoding="UTF-8",
299 force_defaults=False)
299 force_defaults=False)
300
300
301 @HasPermissionAllDecorator('hg.admin')
301 @HasPermissionAllDecorator('hg.admin')
302 @auth.CSRFRequired()
302 @auth.CSRFRequired()
303 def settings_visual_update(self):
303 def settings_visual_update(self):
304 """POST /admin/settings/visual: All items in the collection"""
304 """POST /admin/settings/visual: All items in the collection"""
305 # url('admin_settings_visual')
305 # url('admin_settings_visual')
306 c.active = 'visual'
306 c.active = 'visual'
307 application_form = ApplicationVisualisationForm()()
307 application_form = ApplicationVisualisationForm()()
308 try:
308 try:
309 form_result = application_form.to_python(dict(request.POST))
309 form_result = application_form.to_python(dict(request.POST))
310 except formencode.Invalid as errors:
310 except formencode.Invalid as errors:
311 return htmlfill.render(
311 return htmlfill.render(
312 render('admin/settings/settings.mako'),
312 render('admin/settings/settings.mako'),
313 defaults=errors.value,
313 defaults=errors.value,
314 errors=errors.error_dict or {},
314 errors=errors.error_dict or {},
315 prefix_error=False,
315 prefix_error=False,
316 encoding="UTF-8",
316 encoding="UTF-8",
317 force_defaults=False
317 force_defaults=False
318 )
318 )
319
319
320 try:
320 try:
321 settings = [
321 settings = [
322 ('show_public_icon', 'rhodecode_show_public_icon', 'bool'),
322 ('show_public_icon', 'rhodecode_show_public_icon', 'bool'),
323 ('show_private_icon', 'rhodecode_show_private_icon', 'bool'),
323 ('show_private_icon', 'rhodecode_show_private_icon', 'bool'),
324 ('stylify_metatags', 'rhodecode_stylify_metatags', 'bool'),
324 ('stylify_metatags', 'rhodecode_stylify_metatags', 'bool'),
325 ('repository_fields', 'rhodecode_repository_fields', 'bool'),
325 ('repository_fields', 'rhodecode_repository_fields', 'bool'),
326 ('dashboard_items', 'rhodecode_dashboard_items', 'int'),
326 ('dashboard_items', 'rhodecode_dashboard_items', 'int'),
327 ('admin_grid_items', 'rhodecode_admin_grid_items', 'int'),
327 ('admin_grid_items', 'rhodecode_admin_grid_items', 'int'),
328 ('show_version', 'rhodecode_show_version', 'bool'),
328 ('show_version', 'rhodecode_show_version', 'bool'),
329 ('use_gravatar', 'rhodecode_use_gravatar', 'bool'),
329 ('use_gravatar', 'rhodecode_use_gravatar', 'bool'),
330 ('markup_renderer', 'rhodecode_markup_renderer', 'unicode'),
330 ('markup_renderer', 'rhodecode_markup_renderer', 'unicode'),
331 ('gravatar_url', 'rhodecode_gravatar_url', 'unicode'),
331 ('gravatar_url', 'rhodecode_gravatar_url', 'unicode'),
332 ('clone_uri_tmpl', 'rhodecode_clone_uri_tmpl', 'unicode'),
332 ('clone_uri_tmpl', 'rhodecode_clone_uri_tmpl', 'unicode'),
333 ('support_url', 'rhodecode_support_url', 'unicode'),
333 ('support_url', 'rhodecode_support_url', 'unicode'),
334 ('show_revision_number', 'rhodecode_show_revision_number', 'bool'),
334 ('show_revision_number', 'rhodecode_show_revision_number', 'bool'),
335 ('show_sha_length', 'rhodecode_show_sha_length', 'int'),
335 ('show_sha_length', 'rhodecode_show_sha_length', 'int'),
336 ]
336 ]
337 for setting, form_key, type_ in settings:
337 for setting, form_key, type_ in settings:
338 sett = SettingsModel().create_or_update_setting(
338 sett = SettingsModel().create_or_update_setting(
339 setting, form_result[form_key], type_)
339 setting, form_result[form_key], type_)
340 Session().add(sett)
340 Session().add(sett)
341
341
342 Session().commit()
342 Session().commit()
343 SettingsModel().invalidate_settings_cache()
343 SettingsModel().invalidate_settings_cache()
344 h.flash(_('Updated visualisation settings'), category='success')
344 h.flash(_('Updated visualisation settings'), category='success')
345 except Exception:
345 except Exception:
346 log.exception("Exception updating visualization settings")
346 log.exception("Exception updating visualization settings")
347 h.flash(_('Error occurred during updating '
347 h.flash(_('Error occurred during updating '
348 'visualisation settings'),
348 'visualisation settings'),
349 category='error')
349 category='error')
350
350
351 return redirect(url('admin_settings_visual'))
351 return redirect(url('admin_settings_visual'))
352
352
353 @HasPermissionAllDecorator('hg.admin')
353 @HasPermissionAllDecorator('hg.admin')
354 def settings_visual(self):
354 def settings_visual(self):
355 """GET /admin/settings/visual: All items in the collection"""
355 """GET /admin/settings/visual: All items in the collection"""
356 # url('admin_settings_visual')
356 # url('admin_settings_visual')
357 c.active = 'visual'
357 c.active = 'visual'
358
358
359 return htmlfill.render(
359 return htmlfill.render(
360 render('admin/settings/settings.mako'),
360 render('admin/settings/settings.mako'),
361 defaults=self._form_defaults(),
361 defaults=self._form_defaults(),
362 encoding="UTF-8",
362 encoding="UTF-8",
363 force_defaults=False)
363 force_defaults=False)
364
364
365 @HasPermissionAllDecorator('hg.admin')
365 @HasPermissionAllDecorator('hg.admin')
366 @auth.CSRFRequired()
366 @auth.CSRFRequired()
367 def settings_issuetracker_test(self):
367 def settings_issuetracker_test(self):
368 if request.is_xhr:
368 if request.is_xhr:
369 return h.urlify_commit_message(
369 return h.urlify_commit_message(
370 request.POST.get('test_text', ''),
370 request.POST.get('test_text', ''),
371 'repo_group/test_repo1')
371 'repo_group/test_repo1')
372 else:
372 else:
373 raise HTTPBadRequest()
373 raise HTTPBadRequest()
374
374
375 @HasPermissionAllDecorator('hg.admin')
375 @HasPermissionAllDecorator('hg.admin')
376 @auth.CSRFRequired()
376 @auth.CSRFRequired()
377 def settings_issuetracker_delete(self):
377 def settings_issuetracker_delete(self):
378 uid = request.POST.get('uid')
378 uid = request.POST.get('uid')
379 IssueTrackerSettingsModel().delete_entries(uid)
379 IssueTrackerSettingsModel().delete_entries(uid)
380 h.flash(_('Removed issue tracker entry'), category='success')
380 h.flash(_('Removed issue tracker entry'), category='success')
381 return redirect(url('admin_settings_issuetracker'))
381 return redirect(url('admin_settings_issuetracker'))
382
382
383 @HasPermissionAllDecorator('hg.admin')
383 @HasPermissionAllDecorator('hg.admin')
384 def settings_issuetracker(self):
384 def settings_issuetracker(self):
385 """GET /admin/settings/issue-tracker: All items in the collection"""
385 """GET /admin/settings/issue-tracker: All items in the collection"""
386 # url('admin_settings_issuetracker')
386 # url('admin_settings_issuetracker')
387 c.active = 'issuetracker'
387 c.active = 'issuetracker'
388 defaults = SettingsModel().get_all_settings()
388 defaults = SettingsModel().get_all_settings()
389
389
390 entry_key = 'rhodecode_issuetracker_pat_'
390 entry_key = 'rhodecode_issuetracker_pat_'
391
391
392 c.issuetracker_entries = {}
392 c.issuetracker_entries = {}
393 for k, v in defaults.items():
393 for k, v in defaults.items():
394 if k.startswith(entry_key):
394 if k.startswith(entry_key):
395 uid = k[len(entry_key):]
395 uid = k[len(entry_key):]
396 c.issuetracker_entries[uid] = None
396 c.issuetracker_entries[uid] = None
397
397
398 for uid in c.issuetracker_entries:
398 for uid in c.issuetracker_entries:
399 c.issuetracker_entries[uid] = AttributeDict({
399 c.issuetracker_entries[uid] = AttributeDict({
400 'pat': defaults.get('rhodecode_issuetracker_pat_' + uid),
400 'pat': defaults.get('rhodecode_issuetracker_pat_' + uid),
401 'url': defaults.get('rhodecode_issuetracker_url_' + uid),
401 'url': defaults.get('rhodecode_issuetracker_url_' + uid),
402 'pref': defaults.get('rhodecode_issuetracker_pref_' + uid),
402 'pref': defaults.get('rhodecode_issuetracker_pref_' + uid),
403 'desc': defaults.get('rhodecode_issuetracker_desc_' + uid),
403 'desc': defaults.get('rhodecode_issuetracker_desc_' + uid),
404 })
404 })
405
405
406 return render('admin/settings/settings.mako')
406 return render('admin/settings/settings.mako')
407
407
408 @HasPermissionAllDecorator('hg.admin')
408 @HasPermissionAllDecorator('hg.admin')
409 @auth.CSRFRequired()
409 @auth.CSRFRequired()
410 def settings_issuetracker_save(self):
410 def settings_issuetracker_save(self):
411 settings_model = IssueTrackerSettingsModel()
411 settings_model = IssueTrackerSettingsModel()
412
412
413 form = IssueTrackerPatternsForm()().to_python(request.POST)
413 form = IssueTrackerPatternsForm()().to_python(request.POST)
414 if form:
414 if form:
415 for uid in form.get('delete_patterns', []):
415 for uid in form.get('delete_patterns', []):
416 settings_model.delete_entries(uid)
416 settings_model.delete_entries(uid)
417
417
418 for pattern in form.get('patterns', []):
418 for pattern in form.get('patterns', []):
419 for setting, value, type_ in pattern:
419 for setting, value, type_ in pattern:
420 sett = settings_model.create_or_update_setting(
420 sett = settings_model.create_or_update_setting(
421 setting, value, type_)
421 setting, value, type_)
422 Session().add(sett)
422 Session().add(sett)
423
423
424 Session().commit()
424 Session().commit()
425
425
426 SettingsModel().invalidate_settings_cache()
426 SettingsModel().invalidate_settings_cache()
427 h.flash(_('Updated issue tracker entries'), category='success')
427 h.flash(_('Updated issue tracker entries'), category='success')
428 return redirect(url('admin_settings_issuetracker'))
428 return redirect(url('admin_settings_issuetracker'))
429
429
430 @HasPermissionAllDecorator('hg.admin')
430 @HasPermissionAllDecorator('hg.admin')
431 @auth.CSRFRequired()
431 @auth.CSRFRequired()
432 def settings_email_update(self):
432 def settings_email_update(self):
433 """POST /admin/settings/email: All items in the collection"""
433 """POST /admin/settings/email: All items in the collection"""
434 # url('admin_settings_email')
434 # url('admin_settings_email')
435 c.active = 'email'
435 c.active = 'email'
436
436
437 test_email = request.POST.get('test_email')
437 test_email = request.POST.get('test_email')
438
438
439 if not test_email:
439 if not test_email:
440 h.flash(_('Please enter email address'), category='error')
440 h.flash(_('Please enter email address'), category='error')
441 return redirect(url('admin_settings_email'))
441 return redirect(url('admin_settings_email'))
442
442
443 email_kwargs = {
443 email_kwargs = {
444 'date': datetime.datetime.now(),
444 'date': datetime.datetime.now(),
445 'user': c.rhodecode_user,
445 'user': c.rhodecode_user,
446 'rhodecode_version': c.rhodecode_version
446 'rhodecode_version': c.rhodecode_version
447 }
447 }
448
448
449 (subject, headers, email_body,
449 (subject, headers, email_body,
450 email_body_plaintext) = EmailNotificationModel().render_email(
450 email_body_plaintext) = EmailNotificationModel().render_email(
451 EmailNotificationModel.TYPE_EMAIL_TEST, **email_kwargs)
451 EmailNotificationModel.TYPE_EMAIL_TEST, **email_kwargs)
452
452
453 recipients = [test_email] if test_email else None
453 recipients = [test_email] if test_email else None
454
454
455 run_task(tasks.send_email, recipients, subject,
455 run_task(tasks.send_email, recipients, subject,
456 email_body_plaintext, email_body)
456 email_body_plaintext, email_body)
457
457
458 h.flash(_('Send email task created'), category='success')
458 h.flash(_('Send email task created'), category='success')
459 return redirect(url('admin_settings_email'))
459 return redirect(url('admin_settings_email'))
460
460
461 @HasPermissionAllDecorator('hg.admin')
461 @HasPermissionAllDecorator('hg.admin')
462 def settings_email(self):
462 def settings_email(self):
463 """GET /admin/settings/email: All items in the collection"""
463 """GET /admin/settings/email: All items in the collection"""
464 # url('admin_settings_email')
464 # url('admin_settings_email')
465 c.active = 'email'
465 c.active = 'email'
466 c.rhodecode_ini = rhodecode.CONFIG
466 c.rhodecode_ini = rhodecode.CONFIG
467
467
468 return htmlfill.render(
468 return htmlfill.render(
469 render('admin/settings/settings.mako'),
469 render('admin/settings/settings.mako'),
470 defaults=self._form_defaults(),
470 defaults=self._form_defaults(),
471 encoding="UTF-8",
471 encoding="UTF-8",
472 force_defaults=False)
472 force_defaults=False)
473
473
474 @HasPermissionAllDecorator('hg.admin')
474 @HasPermissionAllDecorator('hg.admin')
475 @auth.CSRFRequired()
475 @auth.CSRFRequired()
476 def settings_hooks_update(self):
476 def settings_hooks_update(self):
477 """POST or DELETE /admin/settings/hooks: All items in the collection"""
477 """POST or DELETE /admin/settings/hooks: All items in the collection"""
478 # url('admin_settings_hooks')
478 # url('admin_settings_hooks')
479 c.active = 'hooks'
479 c.active = 'hooks'
480 if c.visual.allow_custom_hooks_settings:
480 if c.visual.allow_custom_hooks_settings:
481 ui_key = request.POST.get('new_hook_ui_key')
481 ui_key = request.POST.get('new_hook_ui_key')
482 ui_value = request.POST.get('new_hook_ui_value')
482 ui_value = request.POST.get('new_hook_ui_value')
483
483
484 hook_id = request.POST.get('hook_id')
484 hook_id = request.POST.get('hook_id')
485 new_hook = False
485 new_hook = False
486
486
487 model = SettingsModel()
487 model = SettingsModel()
488 try:
488 try:
489 if ui_value and ui_key:
489 if ui_value and ui_key:
490 model.create_or_update_hook(ui_key, ui_value)
490 model.create_or_update_hook(ui_key, ui_value)
491 h.flash(_('Added new hook'), category='success')
491 h.flash(_('Added new hook'), category='success')
492 new_hook = True
492 new_hook = True
493 elif hook_id:
493 elif hook_id:
494 RhodeCodeUi.delete(hook_id)
494 RhodeCodeUi.delete(hook_id)
495 Session().commit()
495 Session().commit()
496
496
497 # check for edits
497 # check for edits
498 update = False
498 update = False
499 _d = request.POST.dict_of_lists()
499 _d = request.POST.dict_of_lists()
500 for k, v in zip(_d.get('hook_ui_key', []),
500 for k, v in zip(_d.get('hook_ui_key', []),
501 _d.get('hook_ui_value_new', [])):
501 _d.get('hook_ui_value_new', [])):
502 model.create_or_update_hook(k, v)
502 model.create_or_update_hook(k, v)
503 update = True
503 update = True
504
504
505 if update and not new_hook:
505 if update and not new_hook:
506 h.flash(_('Updated hooks'), category='success')
506 h.flash(_('Updated hooks'), category='success')
507 Session().commit()
507 Session().commit()
508 except Exception:
508 except Exception:
509 log.exception("Exception during hook creation")
509 log.exception("Exception during hook creation")
510 h.flash(_('Error occurred during hook creation'),
510 h.flash(_('Error occurred during hook creation'),
511 category='error')
511 category='error')
512
512
513 return redirect(url('admin_settings_hooks'))
513 return redirect(url('admin_settings_hooks'))
514
514
515 @HasPermissionAllDecorator('hg.admin')
515 @HasPermissionAllDecorator('hg.admin')
516 def settings_hooks(self):
516 def settings_hooks(self):
517 """GET /admin/settings/hooks: All items in the collection"""
517 """GET /admin/settings/hooks: All items in the collection"""
518 # url('admin_settings_hooks')
518 # url('admin_settings_hooks')
519 c.active = 'hooks'
519 c.active = 'hooks'
520
520
521 model = SettingsModel()
521 model = SettingsModel()
522 c.hooks = model.get_builtin_hooks()
522 c.hooks = model.get_builtin_hooks()
523 c.custom_hooks = model.get_custom_hooks()
523 c.custom_hooks = model.get_custom_hooks()
524
524
525 return htmlfill.render(
525 return htmlfill.render(
526 render('admin/settings/settings.mako'),
526 render('admin/settings/settings.mako'),
527 defaults=self._form_defaults(),
527 defaults=self._form_defaults(),
528 encoding="UTF-8",
528 encoding="UTF-8",
529 force_defaults=False)
529 force_defaults=False)
530
530
531 @HasPermissionAllDecorator('hg.admin')
531 @HasPermissionAllDecorator('hg.admin')
532 def settings_search(self):
532 def settings_search(self):
533 """GET /admin/settings/search: All items in the collection"""
533 """GET /admin/settings/search: All items in the collection"""
534 # url('admin_settings_search')
534 # url('admin_settings_search')
535 c.active = 'search'
535 c.active = 'search'
536
536
537 from rhodecode.lib.index import searcher_from_config
537 from rhodecode.lib.index import searcher_from_config
538 searcher = searcher_from_config(config)
538 searcher = searcher_from_config(config)
539 c.statistics = searcher.statistics()
539 c.statistics = searcher.statistics()
540
540
541 return render('admin/settings/settings.mako')
541 return render('admin/settings/settings.mako')
542
542
543 @HasPermissionAllDecorator('hg.admin')
543 @HasPermissionAllDecorator('hg.admin')
544 def settings_supervisor(self):
544 def settings_supervisor(self):
545 c.rhodecode_ini = rhodecode.CONFIG
545 c.rhodecode_ini = rhodecode.CONFIG
546 c.active = 'supervisor'
546 c.active = 'supervisor'
547
547
548 c.supervisor_procs = OrderedDict([
548 c.supervisor_procs = OrderedDict([
549 (SUPERVISOR_MASTER, {}),
549 (SUPERVISOR_MASTER, {}),
550 ])
550 ])
551
551
552 c.log_size = 10240
552 c.log_size = 10240
553 supervisor = SupervisorModel()
553 supervisor = SupervisorModel()
554
554
555 _connection = supervisor.get_connection(
555 _connection = supervisor.get_connection(
556 c.rhodecode_ini.get('supervisor.uri'))
556 c.rhodecode_ini.get('supervisor.uri'))
557 c.connection_error = None
557 c.connection_error = None
558 try:
558 try:
559 _connection.supervisor.getAllProcessInfo()
559 _connection.supervisor.getAllProcessInfo()
560 except Exception as e:
560 except Exception as e:
561 c.connection_error = str(e)
561 c.connection_error = str(e)
562 log.exception("Exception reading supervisor data")
562 log.exception("Exception reading supervisor data")
563 return render('admin/settings/settings.mako')
563 return render('admin/settings/settings.mako')
564
564
565 groupid = c.rhodecode_ini.get('supervisor.group_id')
565 groupid = c.rhodecode_ini.get('supervisor.group_id')
566
566
567 # feed our group processes to the main
567 # feed our group processes to the main
568 for proc in supervisor.get_group_processes(_connection, groupid):
568 for proc in supervisor.get_group_processes(_connection, groupid):
569 c.supervisor_procs[proc['name']] = {}
569 c.supervisor_procs[proc['name']] = {}
570
570
571 for k in c.supervisor_procs.keys():
571 for k in c.supervisor_procs.keys():
572 try:
572 try:
573 # master process info
573 # master process info
574 if k == SUPERVISOR_MASTER:
574 if k == SUPERVISOR_MASTER:
575 _data = supervisor.get_master_state(_connection)
575 _data = supervisor.get_master_state(_connection)
576 _data['name'] = 'supervisor master'
576 _data['name'] = 'supervisor master'
577 _data['description'] = 'pid %s, id: %s, ver: %s' % (
577 _data['description'] = 'pid %s, id: %s, ver: %s' % (
578 _data['pid'], _data['id'], _data['ver'])
578 _data['pid'], _data['id'], _data['ver'])
579 c.supervisor_procs[k] = _data
579 c.supervisor_procs[k] = _data
580 else:
580 else:
581 procid = groupid + ":" + k
581 procid = groupid + ":" + k
582 c.supervisor_procs[k] = supervisor.get_process_info(_connection, procid)
582 c.supervisor_procs[k] = supervisor.get_process_info(_connection, procid)
583 except Exception as e:
583 except Exception as e:
584 log.exception("Exception reading supervisor data")
584 log.exception("Exception reading supervisor data")
585 c.supervisor_procs[k] = {'_rhodecode_error': str(e)}
585 c.supervisor_procs[k] = {'_rhodecode_error': str(e)}
586
586
587 return render('admin/settings/settings.mako')
587 return render('admin/settings/settings.mako')
588
588
589 @HasPermissionAllDecorator('hg.admin')
589 @HasPermissionAllDecorator('hg.admin')
590 def settings_supervisor_log(self, procid):
590 def settings_supervisor_log(self, procid):
591 import rhodecode
591 import rhodecode
592 c.rhodecode_ini = rhodecode.CONFIG
592 c.rhodecode_ini = rhodecode.CONFIG
593 c.active = 'supervisor_tail'
593 c.active = 'supervisor_tail'
594
594
595 supervisor = SupervisorModel()
595 supervisor = SupervisorModel()
596 _connection = supervisor.get_connection(c.rhodecode_ini.get('supervisor.uri'))
596 _connection = supervisor.get_connection(c.rhodecode_ini.get('supervisor.uri'))
597 groupid = c.rhodecode_ini.get('supervisor.group_id')
597 groupid = c.rhodecode_ini.get('supervisor.group_id')
598 procid = groupid + ":" + procid if procid != SUPERVISOR_MASTER else procid
598 procid = groupid + ":" + procid if procid != SUPERVISOR_MASTER else procid
599
599
600 c.log_size = 10240
600 c.log_size = 10240
601 offset = abs(safe_int(request.GET.get('offset', c.log_size))) * -1
601 offset = abs(safe_int(request.GET.get('offset', c.log_size))) * -1
602 c.log = supervisor.read_process_log(_connection, procid, offset, 0)
602 c.log = supervisor.read_process_log(_connection, procid, offset, 0)
603
603
604 return render('admin/settings/settings.mako')
604 return render('admin/settings/settings.mako')
605
605
606 @HasPermissionAllDecorator('hg.admin')
606 @HasPermissionAllDecorator('hg.admin')
607 @auth.CSRFRequired()
607 @auth.CSRFRequired()
608 def settings_labs_update(self):
608 def settings_labs_update(self):
609 """POST /admin/settings/labs: All items in the collection"""
609 """POST /admin/settings/labs: All items in the collection"""
610 # url('admin_settings/labs', method={'POST'})
610 # url('admin_settings/labs', method={'POST'})
611 c.active = 'labs'
611 c.active = 'labs'
612
612
613 application_form = LabsSettingsForm()()
613 application_form = LabsSettingsForm()()
614 try:
614 try:
615 form_result = application_form.to_python(dict(request.POST))
615 form_result = application_form.to_python(dict(request.POST))
616 except formencode.Invalid as errors:
616 except formencode.Invalid as errors:
617 h.flash(
617 h.flash(
618 _('Some form inputs contain invalid data.'),
618 _('Some form inputs contain invalid data.'),
619 category='error')
619 category='error')
620 return htmlfill.render(
620 return htmlfill.render(
621 render('admin/settings/settings.mako'),
621 render('admin/settings/settings.mako'),
622 defaults=errors.value,
622 defaults=errors.value,
623 errors=errors.error_dict or {},
623 errors=errors.error_dict or {},
624 prefix_error=False,
624 prefix_error=False,
625 encoding='UTF-8',
625 encoding='UTF-8',
626 force_defaults=False
626 force_defaults=False
627 )
627 )
628
628
629 try:
629 try:
630 session = Session()
630 session = Session()
631 for setting in _LAB_SETTINGS:
631 for setting in _LAB_SETTINGS:
632 setting_name = setting.key[len('rhodecode_'):]
632 setting_name = setting.key[len('rhodecode_'):]
633 sett = SettingsModel().create_or_update_setting(
633 sett = SettingsModel().create_or_update_setting(
634 setting_name, form_result[setting.key], setting.type)
634 setting_name, form_result[setting.key], setting.type)
635 session.add(sett)
635 session.add(sett)
636
636
637 except Exception:
637 except Exception:
638 log.exception('Exception while updating lab settings')
638 log.exception('Exception while updating lab settings')
639 h.flash(_('Error occurred during updating labs settings'),
639 h.flash(_('Error occurred during updating labs settings'),
640 category='error')
640 category='error')
641 else:
641 else:
642 Session().commit()
642 Session().commit()
643 SettingsModel().invalidate_settings_cache()
643 SettingsModel().invalidate_settings_cache()
644 h.flash(_('Updated Labs settings'), category='success')
644 h.flash(_('Updated Labs settings'), category='success')
645 return redirect(url('admin_settings_labs'))
645 return redirect(url('admin_settings_labs'))
646
646
647 return htmlfill.render(
647 return htmlfill.render(
648 render('admin/settings/settings.mako'),
648 render('admin/settings/settings.mako'),
649 defaults=self._form_defaults(),
649 defaults=self._form_defaults(),
650 encoding='UTF-8',
650 encoding='UTF-8',
651 force_defaults=False)
651 force_defaults=False)
652
652
653 @HasPermissionAllDecorator('hg.admin')
653 @HasPermissionAllDecorator('hg.admin')
654 def settings_labs(self):
654 def settings_labs(self):
655 """GET /admin/settings/labs: All items in the collection"""
655 """GET /admin/settings/labs: All items in the collection"""
656 # url('admin_settings_labs')
656 # url('admin_settings_labs')
657 if not c.labs_active:
657 if not c.labs_active:
658 redirect(url('admin_settings'))
658 redirect(url('admin_settings'))
659
659
660 c.active = 'labs'
660 c.active = 'labs'
661 c.lab_settings = _LAB_SETTINGS
661 c.lab_settings = _LAB_SETTINGS
662
662
663 return htmlfill.render(
663 return htmlfill.render(
664 render('admin/settings/settings.mako'),
664 render('admin/settings/settings.mako'),
665 defaults=self._form_defaults(),
665 defaults=self._form_defaults(),
666 encoding='UTF-8',
666 encoding='UTF-8',
667 force_defaults=False)
667 force_defaults=False)
668
668
669 def _form_defaults(self):
669 def _form_defaults(self):
670 defaults = SettingsModel().get_all_settings()
670 defaults = SettingsModel().get_all_settings()
671 defaults.update(self._get_hg_ui_settings())
671 defaults.update(self._get_hg_ui_settings())
672 defaults.update({
672 defaults.update({
673 'new_svn_branch': '',
673 'new_svn_branch': '',
674 'new_svn_tag': '',
674 'new_svn_tag': '',
675 })
675 })
676 return defaults
676 return defaults
677
677
678
678
679 # :param key: name of the setting including the 'rhodecode_' prefix
679 # :param key: name of the setting including the 'rhodecode_' prefix
680 # :param type: the RhodeCodeSetting type to use.
680 # :param type: the RhodeCodeSetting type to use.
681 # :param group: the i18ned group in which we should dispaly this setting
681 # :param group: the i18ned group in which we should dispaly this setting
682 # :param label: the i18ned label we should display for this setting
682 # :param label: the i18ned label we should display for this setting
683 # :param help: the i18ned help we should dispaly for this setting
683 # :param help: the i18ned help we should dispaly for this setting
684 LabSetting = collections.namedtuple(
684 LabSetting = collections.namedtuple(
685 'LabSetting', ('key', 'type', 'group', 'label', 'help'))
685 'LabSetting', ('key', 'type', 'group', 'label', 'help'))
686
686
687
687
688 # This list has to be kept in sync with the form
688 # This list has to be kept in sync with the form
689 # rhodecode.model.forms.LabsSettingsForm.
689 # rhodecode.model.forms.LabsSettingsForm.
690 _LAB_SETTINGS = [
690 _LAB_SETTINGS = [
691
691
692 ]
692 ]
@@ -1,391 +1,390 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2017 RhodeCode GmbH
3 # Copyright (C) 2012-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 pylons
21 import pylons
22 import deform
22 import deform
23 import logging
23 import logging
24 import colander
24 import colander
25 import peppercorn
25 import peppercorn
26 import webhelpers.paginate
26 import webhelpers.paginate
27
27
28 from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPBadRequest
28 from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPBadRequest
29 from pyramid.renderers import render
29 from pyramid.renderers import render
30 from pyramid.response import Response
30 from pyramid.response import Response
31
31
32 from rhodecode.apps.admin.navigation import navigation_list
32 from rhodecode.lib import auth
33 from rhodecode.lib import auth
33 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
34 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
34 from rhodecode.lib.utils2 import safe_int
35 from rhodecode.lib.utils2 import safe_int
35 from rhodecode.lib.helpers import Page
36 from rhodecode.lib.helpers import Page
36 from rhodecode.model.db import Repository, RepoGroup, Session, Integration
37 from rhodecode.model.db import Repository, RepoGroup, Session, Integration
37 from rhodecode.model.scm import ScmModel
38 from rhodecode.model.scm import ScmModel
38 from rhodecode.model.integration import IntegrationModel
39 from rhodecode.model.integration import IntegrationModel
39 from rhodecode.admin.navigation import navigation_list
40 from rhodecode.translation import _
40 from rhodecode.translation import _
41 from rhodecode.integrations import integration_type_registry
41 from rhodecode.integrations import integration_type_registry
42 from rhodecode.model.validation_schema.schemas.integration_schema import (
42 from rhodecode.model.validation_schema.schemas.integration_schema import (
43 make_integration_schema, IntegrationScopeType)
43 make_integration_schema, IntegrationScopeType)
44
44
45 log = logging.getLogger(__name__)
45 log = logging.getLogger(__name__)
46
46
47
47
48 class IntegrationSettingsViewBase(object):
48 class IntegrationSettingsViewBase(object):
49 """ Base Integration settings view used by both repo / global settings """
49 """ Base Integration settings view used by both repo / global settings """
50
50
51 def __init__(self, context, request):
51 def __init__(self, context, request):
52 self.context = context
52 self.context = context
53 self.request = request
53 self.request = request
54 self._load_general_context()
54 self._load_general_context()
55
55
56 if not self.perm_check(request.user):
56 if not self.perm_check(request.user):
57 raise HTTPForbidden()
57 raise HTTPForbidden()
58
58
59 def _load_general_context(self):
59 def _load_general_context(self):
60 """
60 """
61 This avoids boilerplate for repo/global+list/edit+views/templates
61 This avoids boilerplate for repo/global+list/edit+views/templates
62 by doing all possible contexts at the same time however it should
62 by doing all possible contexts at the same time however it should
63 be split up into separate functions once more "contexts" exist
63 be split up into separate functions once more "contexts" exist
64 """
64 """
65
65
66 self.IntegrationType = None
66 self.IntegrationType = None
67 self.repo = None
67 self.repo = None
68 self.repo_group = None
68 self.repo_group = None
69 self.integration = None
69 self.integration = None
70 self.integrations = {}
70 self.integrations = {}
71
71
72 request = self.request
72 request = self.request
73
73
74 if 'repo_name' in request.matchdict: # in repo settings context
74 if 'repo_name' in request.matchdict: # in repo settings context
75 repo_name = request.matchdict['repo_name']
75 repo_name = request.matchdict['repo_name']
76 self.repo = Repository.get_by_repo_name(repo_name)
76 self.repo = Repository.get_by_repo_name(repo_name)
77
77
78 if 'repo_group_name' in request.matchdict: # in group settings context
78 if 'repo_group_name' in request.matchdict: # in group settings context
79 repo_group_name = request.matchdict['repo_group_name']
79 repo_group_name = request.matchdict['repo_group_name']
80 self.repo_group = RepoGroup.get_by_group_name(repo_group_name)
80 self.repo_group = RepoGroup.get_by_group_name(repo_group_name)
81
81
82
83 if 'integration' in request.matchdict: # integration type context
82 if 'integration' in request.matchdict: # integration type context
84 integration_type = request.matchdict['integration']
83 integration_type = request.matchdict['integration']
85 self.IntegrationType = integration_type_registry[integration_type]
84 self.IntegrationType = integration_type_registry[integration_type]
86
85
87 if 'integration_id' in request.matchdict: # single integration context
86 if 'integration_id' in request.matchdict: # single integration context
88 integration_id = request.matchdict['integration_id']
87 integration_id = request.matchdict['integration_id']
89 self.integration = Integration.get(integration_id)
88 self.integration = Integration.get(integration_id)
90
89
91 # extra perms check just in case
90 # extra perms check just in case
92 if not self._has_perms_for_integration(self.integration):
91 if not self._has_perms_for_integration(self.integration):
93 raise HTTPForbidden()
92 raise HTTPForbidden()
94
93
95 self.settings = self.integration and self.integration.settings or {}
94 self.settings = self.integration and self.integration.settings or {}
96 self.admin_view = not (self.repo or self.repo_group)
95 self.admin_view = not (self.repo or self.repo_group)
97
96
98 def _has_perms_for_integration(self, integration):
97 def _has_perms_for_integration(self, integration):
99 perms = self.request.user.permissions
98 perms = self.request.user.permissions
100
99
101 if 'hg.admin' in perms['global']:
100 if 'hg.admin' in perms['global']:
102 return True
101 return True
103
102
104 if integration.repo:
103 if integration.repo:
105 return perms['repositories'].get(
104 return perms['repositories'].get(
106 integration.repo.repo_name) == 'repository.admin'
105 integration.repo.repo_name) == 'repository.admin'
107
106
108 if integration.repo_group:
107 if integration.repo_group:
109 return perms['repositories_groups'].get(
108 return perms['repositories_groups'].get(
110 integration.repo_group.group_name) == 'group.admin'
109 integration.repo_group.group_name) == 'group.admin'
111
110
112 return False
111 return False
113
112
114 def _template_c_context(self):
113 def _template_c_context(self):
115 # TODO: dan: this is a stopgap in order to inherit from current pylons
114 # TODO: dan: this is a stopgap in order to inherit from current pylons
116 # based admin/repo settings templates - this should be removed entirely
115 # based admin/repo settings templates - this should be removed entirely
117 # after port to pyramid
116 # after port to pyramid
118
117
119 c = pylons.tmpl_context
118 c = pylons.tmpl_context
120 c.active = 'integrations'
119 c.active = 'integrations'
121 c.rhodecode_user = self.request.user
120 c.rhodecode_user = self.request.user
122 c.repo = self.repo
121 c.repo = self.repo
123 c.repo_group = self.repo_group
122 c.repo_group = self.repo_group
124 c.repo_name = self.repo and self.repo.repo_name or None
123 c.repo_name = self.repo and self.repo.repo_name or None
125 c.repo_group_name = self.repo_group and self.repo_group.group_name or None
124 c.repo_group_name = self.repo_group and self.repo_group.group_name or None
126
125
127 if self.repo:
126 if self.repo:
128 c.repo_info = self.repo
127 c.repo_info = self.repo
129 c.rhodecode_db_repo = self.repo
128 c.rhodecode_db_repo = self.repo
130 c.repository_pull_requests = ScmModel().get_pull_requests(self.repo)
129 c.repository_pull_requests = ScmModel().get_pull_requests(self.repo)
131 else:
130 else:
132 c.navlist = navigation_list(self.request)
131 c.navlist = navigation_list(self.request)
133
132
134 return c
133 return c
135
134
136 def _form_schema(self):
135 def _form_schema(self):
137 schema = make_integration_schema(IntegrationType=self.IntegrationType,
136 schema = make_integration_schema(IntegrationType=self.IntegrationType,
138 settings=self.settings)
137 settings=self.settings)
139
138
140 # returns a clone, important if mutating the schema later
139 # returns a clone, important if mutating the schema later
141 return schema.bind(
140 return schema.bind(
142 permissions=self.request.user.permissions,
141 permissions=self.request.user.permissions,
143 no_scope=not self.admin_view)
142 no_scope=not self.admin_view)
144
143
145 def _form_defaults(self):
144 def _form_defaults(self):
146 defaults = {}
145 defaults = {}
147
146
148 if self.integration:
147 if self.integration:
149 defaults['settings'] = self.integration.settings or {}
148 defaults['settings'] = self.integration.settings or {}
150 defaults['options'] = {
149 defaults['options'] = {
151 'name': self.integration.name,
150 'name': self.integration.name,
152 'enabled': self.integration.enabled,
151 'enabled': self.integration.enabled,
153 'scope': {
152 'scope': {
154 'repo': self.integration.repo,
153 'repo': self.integration.repo,
155 'repo_group': self.integration.repo_group,
154 'repo_group': self.integration.repo_group,
156 'child_repos_only': self.integration.child_repos_only,
155 'child_repos_only': self.integration.child_repos_only,
157 },
156 },
158 }
157 }
159 else:
158 else:
160 if self.repo:
159 if self.repo:
161 scope = _('{repo_name} repository').format(
160 scope = _('{repo_name} repository').format(
162 repo_name=self.repo.repo_name)
161 repo_name=self.repo.repo_name)
163 elif self.repo_group:
162 elif self.repo_group:
164 scope = _('{repo_group_name} repo group').format(
163 scope = _('{repo_group_name} repo group').format(
165 repo_group_name=self.repo_group.group_name)
164 repo_group_name=self.repo_group.group_name)
166 else:
165 else:
167 scope = _('Global')
166 scope = _('Global')
168
167
169 defaults['options'] = {
168 defaults['options'] = {
170 'enabled': True,
169 'enabled': True,
171 'name': _('{name} integration').format(
170 'name': _('{name} integration').format(
172 name=self.IntegrationType.display_name),
171 name=self.IntegrationType.display_name),
173 }
172 }
174 defaults['options']['scope'] = {
173 defaults['options']['scope'] = {
175 'repo': self.repo,
174 'repo': self.repo,
176 'repo_group': self.repo_group,
175 'repo_group': self.repo_group,
177 }
176 }
178
177
179 return defaults
178 return defaults
180
179
181 def _delete_integration(self, integration):
180 def _delete_integration(self, integration):
182 Session().delete(self.integration)
181 Session().delete(self.integration)
183 Session().commit()
182 Session().commit()
184 self.request.session.flash(
183 self.request.session.flash(
185 _('Integration {integration_name} deleted successfully.').format(
184 _('Integration {integration_name} deleted successfully.').format(
186 integration_name=self.integration.name),
185 integration_name=self.integration.name),
187 queue='success')
186 queue='success')
188
187
189 if self.repo:
188 if self.repo:
190 redirect_to = self.request.route_url(
189 redirect_to = self.request.route_url(
191 'repo_integrations_home', repo_name=self.repo.repo_name)
190 'repo_integrations_home', repo_name=self.repo.repo_name)
192 elif self.repo_group:
191 elif self.repo_group:
193 redirect_to = self.request.route_url(
192 redirect_to = self.request.route_url(
194 'repo_group_integrations_home',
193 'repo_group_integrations_home',
195 repo_group_name=self.repo_group.group_name)
194 repo_group_name=self.repo_group.group_name)
196 else:
195 else:
197 redirect_to = self.request.route_url('global_integrations_home')
196 redirect_to = self.request.route_url('global_integrations_home')
198 raise HTTPFound(redirect_to)
197 raise HTTPFound(redirect_to)
199
198
200 def settings_get(self, defaults=None, form=None):
199 def settings_get(self, defaults=None, form=None):
201 """
200 """
202 View that displays the integration settings as a form.
201 View that displays the integration settings as a form.
203 """
202 """
204
203
205 defaults = defaults or self._form_defaults()
204 defaults = defaults or self._form_defaults()
206 schema = self._form_schema()
205 schema = self._form_schema()
207
206
208 if self.integration:
207 if self.integration:
209 buttons = ('submit', 'delete')
208 buttons = ('submit', 'delete')
210 else:
209 else:
211 buttons = ('submit',)
210 buttons = ('submit',)
212
211
213 form = form or deform.Form(schema, appstruct=defaults, buttons=buttons)
212 form = form or deform.Form(schema, appstruct=defaults, buttons=buttons)
214
213
215 template_context = {
214 template_context = {
216 'form': form,
215 'form': form,
217 'current_IntegrationType': self.IntegrationType,
216 'current_IntegrationType': self.IntegrationType,
218 'integration': self.integration,
217 'integration': self.integration,
219 'c': self._template_c_context(),
218 'c': self._template_c_context(),
220 }
219 }
221
220
222 return template_context
221 return template_context
223
222
224 @auth.CSRFRequired()
223 @auth.CSRFRequired()
225 def settings_post(self):
224 def settings_post(self):
226 """
225 """
227 View that validates and stores the integration settings.
226 View that validates and stores the integration settings.
228 """
227 """
229 controls = self.request.POST.items()
228 controls = self.request.POST.items()
230 pstruct = peppercorn.parse(controls)
229 pstruct = peppercorn.parse(controls)
231
230
232 if self.integration and pstruct.get('delete'):
231 if self.integration and pstruct.get('delete'):
233 return self._delete_integration(self.integration)
232 return self._delete_integration(self.integration)
234
233
235 schema = self._form_schema()
234 schema = self._form_schema()
236
235
237 skip_settings_validation = False
236 skip_settings_validation = False
238 if self.integration and 'enabled' not in pstruct.get('options', {}):
237 if self.integration and 'enabled' not in pstruct.get('options', {}):
239 skip_settings_validation = True
238 skip_settings_validation = True
240 schema['settings'].validator = None
239 schema['settings'].validator = None
241 for field in schema['settings'].children:
240 for field in schema['settings'].children:
242 field.validator = None
241 field.validator = None
243 field.missing = ''
242 field.missing = ''
244
243
245 if self.integration:
244 if self.integration:
246 buttons = ('submit', 'delete')
245 buttons = ('submit', 'delete')
247 else:
246 else:
248 buttons = ('submit',)
247 buttons = ('submit',)
249
248
250 form = deform.Form(schema, buttons=buttons)
249 form = deform.Form(schema, buttons=buttons)
251
250
252 if not self.admin_view:
251 if not self.admin_view:
253 # scope is read only field in these cases, and has to be added
252 # scope is read only field in these cases, and has to be added
254 options = pstruct.setdefault('options', {})
253 options = pstruct.setdefault('options', {})
255 if 'scope' not in options:
254 if 'scope' not in options:
256 options['scope'] = IntegrationScopeType().serialize(None, {
255 options['scope'] = IntegrationScopeType().serialize(None, {
257 'repo': self.repo,
256 'repo': self.repo,
258 'repo_group': self.repo_group,
257 'repo_group': self.repo_group,
259 })
258 })
260
259
261 try:
260 try:
262 valid_data = form.validate_pstruct(pstruct)
261 valid_data = form.validate_pstruct(pstruct)
263 except deform.ValidationFailure as e:
262 except deform.ValidationFailure as e:
264 self.request.session.flash(
263 self.request.session.flash(
265 _('Errors exist when saving integration settings. '
264 _('Errors exist when saving integration settings. '
266 'Please check the form inputs.'),
265 'Please check the form inputs.'),
267 queue='error')
266 queue='error')
268 return self.settings_get(form=e)
267 return self.settings_get(form=e)
269
268
270 if not self.integration:
269 if not self.integration:
271 self.integration = Integration()
270 self.integration = Integration()
272 self.integration.integration_type = self.IntegrationType.key
271 self.integration.integration_type = self.IntegrationType.key
273 Session().add(self.integration)
272 Session().add(self.integration)
274
273
275 scope = valid_data['options']['scope']
274 scope = valid_data['options']['scope']
276
275
277 IntegrationModel().update_integration(self.integration,
276 IntegrationModel().update_integration(self.integration,
278 name=valid_data['options']['name'],
277 name=valid_data['options']['name'],
279 enabled=valid_data['options']['enabled'],
278 enabled=valid_data['options']['enabled'],
280 settings=valid_data['settings'],
279 settings=valid_data['settings'],
281 repo=scope['repo'],
280 repo=scope['repo'],
282 repo_group=scope['repo_group'],
281 repo_group=scope['repo_group'],
283 child_repos_only=scope['child_repos_only'],
282 child_repos_only=scope['child_repos_only'],
284 )
283 )
285
284
286 self.integration.settings = valid_data['settings']
285 self.integration.settings = valid_data['settings']
287 Session().commit()
286 Session().commit()
288 # Display success message and redirect.
287 # Display success message and redirect.
289 self.request.session.flash(
288 self.request.session.flash(
290 _('Integration {integration_name} updated successfully.').format(
289 _('Integration {integration_name} updated successfully.').format(
291 integration_name=self.IntegrationType.display_name),
290 integration_name=self.IntegrationType.display_name),
292 queue='success')
291 queue='success')
293
292
294 # if integration scope changes, we must redirect to the right place
293 # if integration scope changes, we must redirect to the right place
295 # keeping in mind if the original view was for /repo/ or /_admin/
294 # keeping in mind if the original view was for /repo/ or /_admin/
296 admin_view = not (self.repo or self.repo_group)
295 admin_view = not (self.repo or self.repo_group)
297
296
298 if self.integration.repo and not admin_view:
297 if self.integration.repo and not admin_view:
299 redirect_to = self.request.route_path(
298 redirect_to = self.request.route_path(
300 'repo_integrations_edit',
299 'repo_integrations_edit',
301 repo_name=self.integration.repo.repo_name,
300 repo_name=self.integration.repo.repo_name,
302 integration=self.integration.integration_type,
301 integration=self.integration.integration_type,
303 integration_id=self.integration.integration_id)
302 integration_id=self.integration.integration_id)
304 elif self.integration.repo_group and not admin_view:
303 elif self.integration.repo_group and not admin_view:
305 redirect_to = self.request.route_path(
304 redirect_to = self.request.route_path(
306 'repo_group_integrations_edit',
305 'repo_group_integrations_edit',
307 repo_group_name=self.integration.repo_group.group_name,
306 repo_group_name=self.integration.repo_group.group_name,
308 integration=self.integration.integration_type,
307 integration=self.integration.integration_type,
309 integration_id=self.integration.integration_id)
308 integration_id=self.integration.integration_id)
310 else:
309 else:
311 redirect_to = self.request.route_path(
310 redirect_to = self.request.route_path(
312 'global_integrations_edit',
311 'global_integrations_edit',
313 integration=self.integration.integration_type,
312 integration=self.integration.integration_type,
314 integration_id=self.integration.integration_id)
313 integration_id=self.integration.integration_id)
315
314
316 return HTTPFound(redirect_to)
315 return HTTPFound(redirect_to)
317
316
318 def index(self):
317 def index(self):
319 """ List integrations """
318 """ List integrations """
320 if self.repo:
319 if self.repo:
321 scope = self.repo
320 scope = self.repo
322 elif self.repo_group:
321 elif self.repo_group:
323 scope = self.repo_group
322 scope = self.repo_group
324 else:
323 else:
325 scope = 'all'
324 scope = 'all'
326
325
327 integrations = []
326 integrations = []
328
327
329 for IntType, integration in IntegrationModel().get_integrations(
328 for IntType, integration in IntegrationModel().get_integrations(
330 scope=scope, IntegrationType=self.IntegrationType):
329 scope=scope, IntegrationType=self.IntegrationType):
331
330
332 # extra permissions check *just in case*
331 # extra permissions check *just in case*
333 if not self._has_perms_for_integration(integration):
332 if not self._has_perms_for_integration(integration):
334 continue
333 continue
335
334
336 integrations.append((IntType, integration))
335 integrations.append((IntType, integration))
337
336
338 sort_arg = self.request.GET.get('sort', 'name:asc')
337 sort_arg = self.request.GET.get('sort', 'name:asc')
339 if ':' in sort_arg:
338 if ':' in sort_arg:
340 sort_field, sort_dir = sort_arg.split(':')
339 sort_field, sort_dir = sort_arg.split(':')
341 else:
340 else:
342 sort_field = sort_arg, 'asc'
341 sort_field = sort_arg, 'asc'
343
342
344 assert sort_field in ('name', 'integration_type', 'enabled', 'scope')
343 assert sort_field in ('name', 'integration_type', 'enabled', 'scope')
345
344
346 integrations.sort(
345 integrations.sort(
347 key=lambda x: getattr(x[1], sort_field),
346 key=lambda x: getattr(x[1], sort_field),
348 reverse=(sort_dir == 'desc'))
347 reverse=(sort_dir == 'desc'))
349
348
350 page_url = webhelpers.paginate.PageURL(
349 page_url = webhelpers.paginate.PageURL(
351 self.request.path, self.request.GET)
350 self.request.path, self.request.GET)
352 page = safe_int(self.request.GET.get('page', 1), 1)
351 page = safe_int(self.request.GET.get('page', 1), 1)
353
352
354 integrations = Page(integrations, page=page, items_per_page=10,
353 integrations = Page(integrations, page=page, items_per_page=10,
355 url=page_url)
354 url=page_url)
356
355
357 template_context = {
356 template_context = {
358 'sort_field': sort_field,
357 'sort_field': sort_field,
359 'rev_sort_dir': sort_dir != 'desc' and 'desc' or 'asc',
358 'rev_sort_dir': sort_dir != 'desc' and 'desc' or 'asc',
360 'current_IntegrationType': self.IntegrationType,
359 'current_IntegrationType': self.IntegrationType,
361 'integrations_list': integrations,
360 'integrations_list': integrations,
362 'available_integrations': integration_type_registry,
361 'available_integrations': integration_type_registry,
363 'c': self._template_c_context(),
362 'c': self._template_c_context(),
364 'request': self.request,
363 'request': self.request,
365 }
364 }
366 return template_context
365 return template_context
367
366
368 def new_integration(self):
367 def new_integration(self):
369 template_context = {
368 template_context = {
370 'available_integrations': integration_type_registry,
369 'available_integrations': integration_type_registry,
371 'c': self._template_c_context(),
370 'c': self._template_c_context(),
372 }
371 }
373 return template_context
372 return template_context
374
373
375
374
376 class GlobalIntegrationsView(IntegrationSettingsViewBase):
375 class GlobalIntegrationsView(IntegrationSettingsViewBase):
377 def perm_check(self, user):
376 def perm_check(self, user):
378 return auth.HasPermissionAll('hg.admin').check_permissions(user=user)
377 return auth.HasPermissionAll('hg.admin').check_permissions(user=user)
379
378
380
379
381 class RepoIntegrationsView(IntegrationSettingsViewBase):
380 class RepoIntegrationsView(IntegrationSettingsViewBase):
382 def perm_check(self, user):
381 def perm_check(self, user):
383 return auth.HasRepoPermissionAll('repository.admin'
382 return auth.HasRepoPermissionAll('repository.admin')(
384 )(repo_name=self.repo.repo_name, user=user)
383 repo_name=self.repo.repo_name, user=user)
385
384
386
385
387 class RepoGroupIntegrationsView(IntegrationSettingsViewBase):
386 class RepoGroupIntegrationsView(IntegrationSettingsViewBase):
388 def perm_check(self, user):
387 def perm_check(self, user):
389 return auth.HasRepoGroupPermissionAll('group.admin'
388 return auth.HasRepoGroupPermissionAll('group.admin')(
390 )(group_name=self.repo_group.group_name, user=user)
389 group_name=self.repo_group.group_name, user=user)
391
390
@@ -1,665 +1,665 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 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 import rhodecode
24 import rhodecode
25 from rhodecode.config.routing import ADMIN_PREFIX
25 from rhodecode.config.routing import ADMIN_PREFIX
26 from rhodecode.lib.utils2 import md5
26 from rhodecode.lib.utils2 import md5
27 from rhodecode.model.db import RhodeCodeUi
27 from rhodecode.model.db import RhodeCodeUi
28 from rhodecode.model.meta import Session
28 from rhodecode.model.meta import Session
29 from rhodecode.model.settings import SettingsModel, IssueTrackerSettingsModel
29 from rhodecode.model.settings import SettingsModel, IssueTrackerSettingsModel
30 from rhodecode.tests import url, assert_session_flash
30 from rhodecode.tests import url, assert_session_flash
31 from rhodecode.tests.utils import AssertResponse
31 from rhodecode.tests.utils import AssertResponse
32
32
33
33
34 UPDATE_DATA_QUALNAME = (
34 UPDATE_DATA_QUALNAME = (
35 'rhodecode.admin.views.system_info.AdminSystemInfoSettingsView.get_update_data')
35 'rhodecode.apps.admin.views.system_info.AdminSystemInfoSettingsView.get_update_data')
36
36
37
37
38 @pytest.mark.usefixtures('autologin_user', 'app')
38 @pytest.mark.usefixtures('autologin_user', 'app')
39 class TestAdminSettingsController(object):
39 class TestAdminSettingsController(object):
40
40
41 @pytest.mark.parametrize('urlname', [
41 @pytest.mark.parametrize('urlname', [
42 'admin_settings_vcs',
42 'admin_settings_vcs',
43 'admin_settings_mapping',
43 'admin_settings_mapping',
44 'admin_settings_global',
44 'admin_settings_global',
45 'admin_settings_visual',
45 'admin_settings_visual',
46 'admin_settings_email',
46 'admin_settings_email',
47 'admin_settings_hooks',
47 'admin_settings_hooks',
48 'admin_settings_search',
48 'admin_settings_search',
49 ])
49 ])
50 def test_simple_get(self, urlname, app):
50 def test_simple_get(self, urlname, app):
51 app.get(url(urlname))
51 app.get(url(urlname))
52
52
53 def test_create_custom_hook(self, csrf_token):
53 def test_create_custom_hook(self, csrf_token):
54 response = self.app.post(
54 response = self.app.post(
55 url('admin_settings_hooks'),
55 url('admin_settings_hooks'),
56 params={
56 params={
57 'new_hook_ui_key': 'test_hooks_1',
57 'new_hook_ui_key': 'test_hooks_1',
58 'new_hook_ui_value': 'cd /tmp',
58 'new_hook_ui_value': 'cd /tmp',
59 'csrf_token': csrf_token})
59 'csrf_token': csrf_token})
60
60
61 response = response.follow()
61 response = response.follow()
62 response.mustcontain('test_hooks_1')
62 response.mustcontain('test_hooks_1')
63 response.mustcontain('cd /tmp')
63 response.mustcontain('cd /tmp')
64
64
65 def test_create_custom_hook_delete(self, csrf_token):
65 def test_create_custom_hook_delete(self, csrf_token):
66 response = self.app.post(
66 response = self.app.post(
67 url('admin_settings_hooks'),
67 url('admin_settings_hooks'),
68 params={
68 params={
69 'new_hook_ui_key': 'test_hooks_2',
69 'new_hook_ui_key': 'test_hooks_2',
70 'new_hook_ui_value': 'cd /tmp2',
70 'new_hook_ui_value': 'cd /tmp2',
71 'csrf_token': csrf_token})
71 'csrf_token': csrf_token})
72
72
73 response = response.follow()
73 response = response.follow()
74 response.mustcontain('test_hooks_2')
74 response.mustcontain('test_hooks_2')
75 response.mustcontain('cd /tmp2')
75 response.mustcontain('cd /tmp2')
76
76
77 hook_id = SettingsModel().get_ui_by_key('test_hooks_2').ui_id
77 hook_id = SettingsModel().get_ui_by_key('test_hooks_2').ui_id
78
78
79 # delete
79 # delete
80 self.app.post(
80 self.app.post(
81 url('admin_settings_hooks'),
81 url('admin_settings_hooks'),
82 params={'hook_id': hook_id, 'csrf_token': csrf_token})
82 params={'hook_id': hook_id, 'csrf_token': csrf_token})
83 response = self.app.get(url('admin_settings_hooks'))
83 response = self.app.get(url('admin_settings_hooks'))
84 response.mustcontain(no=['test_hooks_2'])
84 response.mustcontain(no=['test_hooks_2'])
85 response.mustcontain(no=['cd /tmp2'])
85 response.mustcontain(no=['cd /tmp2'])
86
86
87
87
88 @pytest.mark.usefixtures('autologin_user', 'app')
88 @pytest.mark.usefixtures('autologin_user', 'app')
89 class TestAdminSettingsGlobal(object):
89 class TestAdminSettingsGlobal(object):
90
90
91 def test_pre_post_code_code_active(self, csrf_token):
91 def test_pre_post_code_code_active(self, csrf_token):
92 pre_code = 'rc-pre-code-187652122'
92 pre_code = 'rc-pre-code-187652122'
93 post_code = 'rc-postcode-98165231'
93 post_code = 'rc-postcode-98165231'
94
94
95 response = self.post_and_verify_settings({
95 response = self.post_and_verify_settings({
96 'rhodecode_pre_code': pre_code,
96 'rhodecode_pre_code': pre_code,
97 'rhodecode_post_code': post_code,
97 'rhodecode_post_code': post_code,
98 'csrf_token': csrf_token,
98 'csrf_token': csrf_token,
99 })
99 })
100
100
101 response = response.follow()
101 response = response.follow()
102 response.mustcontain(pre_code, post_code)
102 response.mustcontain(pre_code, post_code)
103
103
104 def test_pre_post_code_code_inactive(self, csrf_token):
104 def test_pre_post_code_code_inactive(self, csrf_token):
105 pre_code = 'rc-pre-code-187652122'
105 pre_code = 'rc-pre-code-187652122'
106 post_code = 'rc-postcode-98165231'
106 post_code = 'rc-postcode-98165231'
107 response = self.post_and_verify_settings({
107 response = self.post_and_verify_settings({
108 'rhodecode_pre_code': '',
108 'rhodecode_pre_code': '',
109 'rhodecode_post_code': '',
109 'rhodecode_post_code': '',
110 'csrf_token': csrf_token,
110 'csrf_token': csrf_token,
111 })
111 })
112
112
113 response = response.follow()
113 response = response.follow()
114 response.mustcontain(no=[pre_code, post_code])
114 response.mustcontain(no=[pre_code, post_code])
115
115
116 def test_captcha_activate(self, csrf_token):
116 def test_captcha_activate(self, csrf_token):
117 self.post_and_verify_settings({
117 self.post_and_verify_settings({
118 'rhodecode_captcha_private_key': '1234567890',
118 'rhodecode_captcha_private_key': '1234567890',
119 'rhodecode_captcha_public_key': '1234567890',
119 'rhodecode_captcha_public_key': '1234567890',
120 'csrf_token': csrf_token,
120 'csrf_token': csrf_token,
121 })
121 })
122
122
123 response = self.app.get(ADMIN_PREFIX + '/register')
123 response = self.app.get(ADMIN_PREFIX + '/register')
124 response.mustcontain('captcha')
124 response.mustcontain('captcha')
125
125
126 def test_captcha_deactivate(self, csrf_token):
126 def test_captcha_deactivate(self, csrf_token):
127 self.post_and_verify_settings({
127 self.post_and_verify_settings({
128 'rhodecode_captcha_private_key': '',
128 'rhodecode_captcha_private_key': '',
129 'rhodecode_captcha_public_key': '1234567890',
129 'rhodecode_captcha_public_key': '1234567890',
130 'csrf_token': csrf_token,
130 'csrf_token': csrf_token,
131 })
131 })
132
132
133 response = self.app.get(ADMIN_PREFIX + '/register')
133 response = self.app.get(ADMIN_PREFIX + '/register')
134 response.mustcontain(no=['captcha'])
134 response.mustcontain(no=['captcha'])
135
135
136 def test_title_change(self, csrf_token):
136 def test_title_change(self, csrf_token):
137 old_title = 'RhodeCode'
137 old_title = 'RhodeCode'
138 new_title = old_title + '_changed'
138 new_title = old_title + '_changed'
139
139
140 for new_title in ['Changed', 'Ε»Γ³Ε‚wik', old_title]:
140 for new_title in ['Changed', 'Ε»Γ³Ε‚wik', old_title]:
141 response = self.post_and_verify_settings({
141 response = self.post_and_verify_settings({
142 'rhodecode_title': new_title,
142 'rhodecode_title': new_title,
143 'csrf_token': csrf_token,
143 'csrf_token': csrf_token,
144 })
144 })
145
145
146 response = response.follow()
146 response = response.follow()
147 response.mustcontain(
147 response.mustcontain(
148 """<div class="branding">- %s</div>""" % new_title)
148 """<div class="branding">- %s</div>""" % new_title)
149
149
150 def post_and_verify_settings(self, settings):
150 def post_and_verify_settings(self, settings):
151 old_title = 'RhodeCode'
151 old_title = 'RhodeCode'
152 old_realm = 'RhodeCode authentication'
152 old_realm = 'RhodeCode authentication'
153 params = {
153 params = {
154 'rhodecode_title': old_title,
154 'rhodecode_title': old_title,
155 'rhodecode_realm': old_realm,
155 'rhodecode_realm': old_realm,
156 'rhodecode_pre_code': '',
156 'rhodecode_pre_code': '',
157 'rhodecode_post_code': '',
157 'rhodecode_post_code': '',
158 'rhodecode_captcha_private_key': '',
158 'rhodecode_captcha_private_key': '',
159 'rhodecode_captcha_public_key': '',
159 'rhodecode_captcha_public_key': '',
160 'rhodecode_create_personal_repo_group': False,
160 'rhodecode_create_personal_repo_group': False,
161 'rhodecode_personal_repo_group_pattern': '${username}',
161 'rhodecode_personal_repo_group_pattern': '${username}',
162 }
162 }
163 params.update(settings)
163 params.update(settings)
164 response = self.app.post(url('admin_settings_global'), params=params)
164 response = self.app.post(url('admin_settings_global'), params=params)
165
165
166 assert_session_flash(response, 'Updated application settings')
166 assert_session_flash(response, 'Updated application settings')
167 app_settings = SettingsModel().get_all_settings()
167 app_settings = SettingsModel().get_all_settings()
168 del settings['csrf_token']
168 del settings['csrf_token']
169 for key, value in settings.iteritems():
169 for key, value in settings.iteritems():
170 assert app_settings[key] == value.decode('utf-8')
170 assert app_settings[key] == value.decode('utf-8')
171
171
172 return response
172 return response
173
173
174
174
175 @pytest.mark.usefixtures('autologin_user', 'app')
175 @pytest.mark.usefixtures('autologin_user', 'app')
176 class TestAdminSettingsVcs(object):
176 class TestAdminSettingsVcs(object):
177
177
178 def test_contains_svn_default_patterns(self, app):
178 def test_contains_svn_default_patterns(self, app):
179 response = app.get(url('admin_settings_vcs'))
179 response = app.get(url('admin_settings_vcs'))
180 expected_patterns = [
180 expected_patterns = [
181 '/trunk',
181 '/trunk',
182 '/branches/*',
182 '/branches/*',
183 '/tags/*',
183 '/tags/*',
184 ]
184 ]
185 for pattern in expected_patterns:
185 for pattern in expected_patterns:
186 response.mustcontain(pattern)
186 response.mustcontain(pattern)
187
187
188 def test_add_new_svn_branch_and_tag_pattern(
188 def test_add_new_svn_branch_and_tag_pattern(
189 self, app, backend_svn, form_defaults, disable_sql_cache,
189 self, app, backend_svn, form_defaults, disable_sql_cache,
190 csrf_token):
190 csrf_token):
191 form_defaults.update({
191 form_defaults.update({
192 'new_svn_branch': '/exp/branches/*',
192 'new_svn_branch': '/exp/branches/*',
193 'new_svn_tag': '/important_tags/*',
193 'new_svn_tag': '/important_tags/*',
194 'csrf_token': csrf_token,
194 'csrf_token': csrf_token,
195 })
195 })
196
196
197 response = app.post(
197 response = app.post(
198 url('admin_settings_vcs'), params=form_defaults, status=302)
198 url('admin_settings_vcs'), params=form_defaults, status=302)
199 response = response.follow()
199 response = response.follow()
200
200
201 # Expect to find the new values on the page
201 # Expect to find the new values on the page
202 response.mustcontain('/exp/branches/*')
202 response.mustcontain('/exp/branches/*')
203 response.mustcontain('/important_tags/*')
203 response.mustcontain('/important_tags/*')
204
204
205 # Expect that those patterns are used to match branches and tags now
205 # Expect that those patterns are used to match branches and tags now
206 repo = backend_svn['svn-simple-layout'].scm_instance()
206 repo = backend_svn['svn-simple-layout'].scm_instance()
207 assert 'exp/branches/exp-sphinx-docs' in repo.branches
207 assert 'exp/branches/exp-sphinx-docs' in repo.branches
208 assert 'important_tags/v0.5' in repo.tags
208 assert 'important_tags/v0.5' in repo.tags
209
209
210 def test_add_same_svn_value_twice_shows_an_error_message(
210 def test_add_same_svn_value_twice_shows_an_error_message(
211 self, app, form_defaults, csrf_token, settings_util):
211 self, app, form_defaults, csrf_token, settings_util):
212 settings_util.create_rhodecode_ui('vcs_svn_branch', '/test')
212 settings_util.create_rhodecode_ui('vcs_svn_branch', '/test')
213 settings_util.create_rhodecode_ui('vcs_svn_tag', '/test')
213 settings_util.create_rhodecode_ui('vcs_svn_tag', '/test')
214
214
215 response = app.post(
215 response = app.post(
216 url('admin_settings_vcs'),
216 url('admin_settings_vcs'),
217 params={
217 params={
218 'paths_root_path': form_defaults['paths_root_path'],
218 'paths_root_path': form_defaults['paths_root_path'],
219 'new_svn_branch': '/test',
219 'new_svn_branch': '/test',
220 'new_svn_tag': '/test',
220 'new_svn_tag': '/test',
221 'csrf_token': csrf_token,
221 'csrf_token': csrf_token,
222 },
222 },
223 status=200)
223 status=200)
224
224
225 response.mustcontain("Pattern already exists")
225 response.mustcontain("Pattern already exists")
226 response.mustcontain("Some form inputs contain invalid data.")
226 response.mustcontain("Some form inputs contain invalid data.")
227
227
228 @pytest.mark.parametrize('section', [
228 @pytest.mark.parametrize('section', [
229 'vcs_svn_branch',
229 'vcs_svn_branch',
230 'vcs_svn_tag',
230 'vcs_svn_tag',
231 ])
231 ])
232 def test_delete_svn_patterns(
232 def test_delete_svn_patterns(
233 self, section, app, csrf_token, settings_util):
233 self, section, app, csrf_token, settings_util):
234 setting = settings_util.create_rhodecode_ui(
234 setting = settings_util.create_rhodecode_ui(
235 section, '/test_delete', cleanup=False)
235 section, '/test_delete', cleanup=False)
236
236
237 app.post(
237 app.post(
238 url('admin_settings_vcs'),
238 url('admin_settings_vcs'),
239 params={
239 params={
240 '_method': 'delete',
240 '_method': 'delete',
241 'delete_svn_pattern': setting.ui_id,
241 'delete_svn_pattern': setting.ui_id,
242 'csrf_token': csrf_token},
242 'csrf_token': csrf_token},
243 headers={'X-REQUESTED-WITH': 'XMLHttpRequest'})
243 headers={'X-REQUESTED-WITH': 'XMLHttpRequest'})
244
244
245 @pytest.mark.parametrize('section', [
245 @pytest.mark.parametrize('section', [
246 'vcs_svn_branch',
246 'vcs_svn_branch',
247 'vcs_svn_tag',
247 'vcs_svn_tag',
248 ])
248 ])
249 def test_delete_svn_patterns_raises_400_when_no_xhr(
249 def test_delete_svn_patterns_raises_400_when_no_xhr(
250 self, section, app, csrf_token, settings_util):
250 self, section, app, csrf_token, settings_util):
251 setting = settings_util.create_rhodecode_ui(section, '/test_delete')
251 setting = settings_util.create_rhodecode_ui(section, '/test_delete')
252
252
253 app.post(
253 app.post(
254 url('admin_settings_vcs'),
254 url('admin_settings_vcs'),
255 params={
255 params={
256 '_method': 'delete',
256 '_method': 'delete',
257 'delete_svn_pattern': setting.ui_id,
257 'delete_svn_pattern': setting.ui_id,
258 'csrf_token': csrf_token},
258 'csrf_token': csrf_token},
259 status=400)
259 status=400)
260
260
261 def test_extensions_hgsubversion(self, app, form_defaults, csrf_token):
261 def test_extensions_hgsubversion(self, app, form_defaults, csrf_token):
262 form_defaults.update({
262 form_defaults.update({
263 'csrf_token': csrf_token,
263 'csrf_token': csrf_token,
264 'extensions_hgsubversion': 'True',
264 'extensions_hgsubversion': 'True',
265 })
265 })
266 response = app.post(
266 response = app.post(
267 url('admin_settings_vcs'),
267 url('admin_settings_vcs'),
268 params=form_defaults,
268 params=form_defaults,
269 status=302)
269 status=302)
270
270
271 response = response.follow()
271 response = response.follow()
272 extensions_input = (
272 extensions_input = (
273 '<input id="extensions_hgsubversion" '
273 '<input id="extensions_hgsubversion" '
274 'name="extensions_hgsubversion" type="checkbox" '
274 'name="extensions_hgsubversion" type="checkbox" '
275 'value="True" checked="checked" />')
275 'value="True" checked="checked" />')
276 response.mustcontain(extensions_input)
276 response.mustcontain(extensions_input)
277
277
278 def test_has_a_section_for_pull_request_settings(self, app):
278 def test_has_a_section_for_pull_request_settings(self, app):
279 response = app.get(url('admin_settings_vcs'))
279 response = app.get(url('admin_settings_vcs'))
280 response.mustcontain('Pull Request Settings')
280 response.mustcontain('Pull Request Settings')
281
281
282 def test_has_an_input_for_invalidation_of_inline_comments(
282 def test_has_an_input_for_invalidation_of_inline_comments(
283 self, app):
283 self, app):
284 response = app.get(url('admin_settings_vcs'))
284 response = app.get(url('admin_settings_vcs'))
285 assert_response = AssertResponse(response)
285 assert_response = AssertResponse(response)
286 assert_response.one_element_exists(
286 assert_response.one_element_exists(
287 '[name=rhodecode_use_outdated_comments]')
287 '[name=rhodecode_use_outdated_comments]')
288
288
289 @pytest.mark.parametrize('new_value', [True, False])
289 @pytest.mark.parametrize('new_value', [True, False])
290 def test_allows_to_change_invalidation_of_inline_comments(
290 def test_allows_to_change_invalidation_of_inline_comments(
291 self, app, form_defaults, csrf_token, new_value):
291 self, app, form_defaults, csrf_token, new_value):
292 setting_key = 'use_outdated_comments'
292 setting_key = 'use_outdated_comments'
293 setting = SettingsModel().create_or_update_setting(
293 setting = SettingsModel().create_or_update_setting(
294 setting_key, not new_value, 'bool')
294 setting_key, not new_value, 'bool')
295 Session().add(setting)
295 Session().add(setting)
296 Session().commit()
296 Session().commit()
297
297
298 form_defaults.update({
298 form_defaults.update({
299 'csrf_token': csrf_token,
299 'csrf_token': csrf_token,
300 'rhodecode_use_outdated_comments': str(new_value),
300 'rhodecode_use_outdated_comments': str(new_value),
301 })
301 })
302 response = app.post(
302 response = app.post(
303 url('admin_settings_vcs'),
303 url('admin_settings_vcs'),
304 params=form_defaults,
304 params=form_defaults,
305 status=302)
305 status=302)
306 response = response.follow()
306 response = response.follow()
307 setting = SettingsModel().get_setting_by_name(setting_key)
307 setting = SettingsModel().get_setting_by_name(setting_key)
308 assert setting.app_settings_value is new_value
308 assert setting.app_settings_value is new_value
309
309
310 def test_has_a_section_for_labs_settings_if_enabled(self, app):
310 def test_has_a_section_for_labs_settings_if_enabled(self, app):
311 with mock.patch.dict(
311 with mock.patch.dict(
312 rhodecode.CONFIG, {'labs_settings_active': 'true'}):
312 rhodecode.CONFIG, {'labs_settings_active': 'true'}):
313 response = self.app.get(url('admin_settings_vcs'))
313 response = self.app.get(url('admin_settings_vcs'))
314 response.mustcontain('Labs Settings')
314 response.mustcontain('Labs Settings')
315
315
316 def test_has_not_a_section_for_labs_settings_if_disables(self, app):
316 def test_has_not_a_section_for_labs_settings_if_disables(self, app):
317 with mock.patch.dict(
317 with mock.patch.dict(
318 rhodecode.CONFIG, {'labs_settings_active': 'false'}):
318 rhodecode.CONFIG, {'labs_settings_active': 'false'}):
319 response = self.app.get(url('admin_settings_vcs'))
319 response = self.app.get(url('admin_settings_vcs'))
320 response.mustcontain(no='Labs Settings')
320 response.mustcontain(no='Labs Settings')
321
321
322 @pytest.mark.parametrize('new_value', [True, False])
322 @pytest.mark.parametrize('new_value', [True, False])
323 def test_allows_to_change_hg_rebase_merge_strategy(
323 def test_allows_to_change_hg_rebase_merge_strategy(
324 self, app, form_defaults, csrf_token, new_value):
324 self, app, form_defaults, csrf_token, new_value):
325 setting_key = 'hg_use_rebase_for_merging'
325 setting_key = 'hg_use_rebase_for_merging'
326
326
327 form_defaults.update({
327 form_defaults.update({
328 'csrf_token': csrf_token,
328 'csrf_token': csrf_token,
329 'rhodecode_' + setting_key: str(new_value),
329 'rhodecode_' + setting_key: str(new_value),
330 })
330 })
331
331
332 with mock.patch.dict(
332 with mock.patch.dict(
333 rhodecode.CONFIG, {'labs_settings_active': 'true'}):
333 rhodecode.CONFIG, {'labs_settings_active': 'true'}):
334 app.post(
334 app.post(
335 url('admin_settings_vcs'),
335 url('admin_settings_vcs'),
336 params=form_defaults,
336 params=form_defaults,
337 status=302)
337 status=302)
338
338
339 setting = SettingsModel().get_setting_by_name(setting_key)
339 setting = SettingsModel().get_setting_by_name(setting_key)
340 assert setting.app_settings_value is new_value
340 assert setting.app_settings_value is new_value
341
341
342 @pytest.fixture
342 @pytest.fixture
343 def disable_sql_cache(self, request):
343 def disable_sql_cache(self, request):
344 patcher = mock.patch(
344 patcher = mock.patch(
345 'rhodecode.lib.caching_query.FromCache.process_query')
345 'rhodecode.lib.caching_query.FromCache.process_query')
346 request.addfinalizer(patcher.stop)
346 request.addfinalizer(patcher.stop)
347 patcher.start()
347 patcher.start()
348
348
349 @pytest.fixture
349 @pytest.fixture
350 def form_defaults(self):
350 def form_defaults(self):
351 from rhodecode.controllers.admin.settings import SettingsController
351 from rhodecode.controllers.admin.settings import SettingsController
352 controller = SettingsController()
352 controller = SettingsController()
353 return controller._form_defaults()
353 return controller._form_defaults()
354
354
355 # TODO: johbo: What we really want is to checkpoint before a test run and
355 # TODO: johbo: What we really want is to checkpoint before a test run and
356 # reset the session afterwards.
356 # reset the session afterwards.
357 @pytest.fixture(scope='class', autouse=True)
357 @pytest.fixture(scope='class', autouse=True)
358 def cleanup_settings(self, request, pylonsapp):
358 def cleanup_settings(self, request, pylonsapp):
359 ui_id = RhodeCodeUi.ui_id
359 ui_id = RhodeCodeUi.ui_id
360 original_ids = list(
360 original_ids = list(
361 r.ui_id for r in RhodeCodeUi.query().values(ui_id))
361 r.ui_id for r in RhodeCodeUi.query().values(ui_id))
362
362
363 @request.addfinalizer
363 @request.addfinalizer
364 def cleanup():
364 def cleanup():
365 RhodeCodeUi.query().filter(
365 RhodeCodeUi.query().filter(
366 ui_id.notin_(original_ids)).delete(False)
366 ui_id.notin_(original_ids)).delete(False)
367
367
368
368
369 @pytest.mark.usefixtures('autologin_user', 'app')
369 @pytest.mark.usefixtures('autologin_user', 'app')
370 class TestLabsSettings(object):
370 class TestLabsSettings(object):
371 def test_get_settings_page_disabled(self):
371 def test_get_settings_page_disabled(self):
372 with mock.patch.dict(rhodecode.CONFIG,
372 with mock.patch.dict(rhodecode.CONFIG,
373 {'labs_settings_active': 'false'}):
373 {'labs_settings_active': 'false'}):
374 response = self.app.get(url('admin_settings_labs'), status=302)
374 response = self.app.get(url('admin_settings_labs'), status=302)
375
375
376 assert response.location.endswith(url('admin_settings'))
376 assert response.location.endswith(url('admin_settings'))
377
377
378 def test_get_settings_page_enabled(self):
378 def test_get_settings_page_enabled(self):
379 from rhodecode.controllers.admin import settings
379 from rhodecode.controllers.admin import settings
380 lab_settings = [
380 lab_settings = [
381 settings.LabSetting(
381 settings.LabSetting(
382 key='rhodecode_bool',
382 key='rhodecode_bool',
383 type='bool',
383 type='bool',
384 group='bool group',
384 group='bool group',
385 label='bool label',
385 label='bool label',
386 help='bool help'
386 help='bool help'
387 ),
387 ),
388 settings.LabSetting(
388 settings.LabSetting(
389 key='rhodecode_text',
389 key='rhodecode_text',
390 type='unicode',
390 type='unicode',
391 group='text group',
391 group='text group',
392 label='text label',
392 label='text label',
393 help='text help'
393 help='text help'
394 ),
394 ),
395 ]
395 ]
396 with mock.patch.dict(rhodecode.CONFIG,
396 with mock.patch.dict(rhodecode.CONFIG,
397 {'labs_settings_active': 'true'}):
397 {'labs_settings_active': 'true'}):
398 with mock.patch.object(settings, '_LAB_SETTINGS', lab_settings):
398 with mock.patch.object(settings, '_LAB_SETTINGS', lab_settings):
399 response = self.app.get(url('admin_settings_labs'))
399 response = self.app.get(url('admin_settings_labs'))
400
400
401 assert '<label>bool group:</label>' in response
401 assert '<label>bool group:</label>' in response
402 assert '<label for="rhodecode_bool">bool label</label>' in response
402 assert '<label for="rhodecode_bool">bool label</label>' in response
403 assert '<p class="help-block">bool help</p>' in response
403 assert '<p class="help-block">bool help</p>' in response
404 assert 'name="rhodecode_bool" type="checkbox"' in response
404 assert 'name="rhodecode_bool" type="checkbox"' in response
405
405
406 assert '<label>text group:</label>' in response
406 assert '<label>text group:</label>' in response
407 assert '<label for="rhodecode_text">text label</label>' in response
407 assert '<label for="rhodecode_text">text label</label>' in response
408 assert '<p class="help-block">text help</p>' in response
408 assert '<p class="help-block">text help</p>' in response
409 assert 'name="rhodecode_text" size="60" type="text"' in response
409 assert 'name="rhodecode_text" size="60" type="text"' in response
410
410
411
411
412 @pytest.mark.usefixtures('app')
412 @pytest.mark.usefixtures('app')
413 class TestOpenSourceLicenses(object):
413 class TestOpenSourceLicenses(object):
414
414
415 def _get_url(self):
415 def _get_url(self):
416 return ADMIN_PREFIX + '/settings/open_source'
416 return ADMIN_PREFIX + '/settings/open_source'
417
417
418 def test_records_are_displayed(self, autologin_user):
418 def test_records_are_displayed(self, autologin_user):
419 sample_licenses = {
419 sample_licenses = {
420 "python2.7-pytest-2.7.1": {
420 "python2.7-pytest-2.7.1": {
421 "UNKNOWN": None
421 "UNKNOWN": None
422 },
422 },
423 "python2.7-Markdown-2.6.2": {
423 "python2.7-Markdown-2.6.2": {
424 "BSD-3-Clause": "http://spdx.org/licenses/BSD-3-Clause"
424 "BSD-3-Clause": "http://spdx.org/licenses/BSD-3-Clause"
425 }
425 }
426 }
426 }
427 read_licenses_patch = mock.patch(
427 read_licenses_patch = mock.patch(
428 'rhodecode.admin.views.open_source_licenses.read_opensource_licenses',
428 'rhodecode.apps.admin.views.open_source_licenses.read_opensource_licenses',
429 return_value=sample_licenses)
429 return_value=sample_licenses)
430 with read_licenses_patch:
430 with read_licenses_patch:
431 response = self.app.get(self._get_url(), status=200)
431 response = self.app.get(self._get_url(), status=200)
432
432
433 assert_response = AssertResponse(response)
433 assert_response = AssertResponse(response)
434 assert_response.element_contains(
434 assert_response.element_contains(
435 '.panel-heading', 'Licenses of Third Party Packages')
435 '.panel-heading', 'Licenses of Third Party Packages')
436 for name in sample_licenses:
436 for name in sample_licenses:
437 response.mustcontain(name)
437 response.mustcontain(name)
438 for license in sample_licenses[name]:
438 for license in sample_licenses[name]:
439 assert_response.element_contains('.panel-body', license)
439 assert_response.element_contains('.panel-body', license)
440
440
441 def test_records_can_be_read(self, autologin_user):
441 def test_records_can_be_read(self, autologin_user):
442 response = self.app.get(self._get_url(), status=200)
442 response = self.app.get(self._get_url(), status=200)
443 assert_response = AssertResponse(response)
443 assert_response = AssertResponse(response)
444 assert_response.element_contains(
444 assert_response.element_contains(
445 '.panel-heading', 'Licenses of Third Party Packages')
445 '.panel-heading', 'Licenses of Third Party Packages')
446
446
447 def test_forbidden_when_normal_user(self, autologin_regular_user):
447 def test_forbidden_when_normal_user(self, autologin_regular_user):
448 self.app.get(self._get_url(), status=403)
448 self.app.get(self._get_url(), status=403)
449
449
450
450
451 @pytest.mark.usefixtures('app')
451 @pytest.mark.usefixtures('app')
452 class TestUserSessions(object):
452 class TestUserSessions(object):
453
453
454 def _get_url(self, name='admin_settings_sessions'):
454 def _get_url(self, name='admin_settings_sessions'):
455 return {
455 return {
456 'admin_settings_sessions': ADMIN_PREFIX + '/settings/sessions',
456 'admin_settings_sessions': ADMIN_PREFIX + '/settings/sessions',
457 'admin_settings_sessions_cleanup': ADMIN_PREFIX + '/settings/sessions/cleanup'
457 'admin_settings_sessions_cleanup': ADMIN_PREFIX + '/settings/sessions/cleanup'
458 }[name]
458 }[name]
459
459
460 def test_forbidden_when_normal_user(self, autologin_regular_user):
460 def test_forbidden_when_normal_user(self, autologin_regular_user):
461 self.app.get(self._get_url(), status=403)
461 self.app.get(self._get_url(), status=403)
462
462
463 def test_show_sessions_page(self, autologin_user):
463 def test_show_sessions_page(self, autologin_user):
464 response = self.app.get(self._get_url(), status=200)
464 response = self.app.get(self._get_url(), status=200)
465 response.mustcontain('file')
465 response.mustcontain('file')
466
466
467 def test_cleanup_old_sessions(self, autologin_user, csrf_token):
467 def test_cleanup_old_sessions(self, autologin_user, csrf_token):
468
468
469 post_data = {
469 post_data = {
470 'csrf_token': csrf_token,
470 'csrf_token': csrf_token,
471 'expire_days': '60'
471 'expire_days': '60'
472 }
472 }
473 response = self.app.post(
473 response = self.app.post(
474 self._get_url('admin_settings_sessions_cleanup'), params=post_data,
474 self._get_url('admin_settings_sessions_cleanup'), params=post_data,
475 status=302)
475 status=302)
476 assert_session_flash(response, 'Cleaned up old sessions')
476 assert_session_flash(response, 'Cleaned up old sessions')
477
477
478
478
479 @pytest.mark.usefixtures('app')
479 @pytest.mark.usefixtures('app')
480 class TestAdminSystemInfo(object):
480 class TestAdminSystemInfo(object):
481 def _get_url(self, name='admin_settings_system'):
481 def _get_url(self, name='admin_settings_system'):
482 return {
482 return {
483 'admin_settings_system': ADMIN_PREFIX + '/settings/system',
483 'admin_settings_system': ADMIN_PREFIX + '/settings/system',
484 'admin_settings_system_update': ADMIN_PREFIX + '/settings/system/updates',
484 'admin_settings_system_update': ADMIN_PREFIX + '/settings/system/updates',
485 }[name]
485 }[name]
486
486
487 def test_forbidden_when_normal_user(self, autologin_regular_user):
487 def test_forbidden_when_normal_user(self, autologin_regular_user):
488 self.app.get(self._get_url(), status=403)
488 self.app.get(self._get_url(), status=403)
489
489
490 def test_system_info_page(self, autologin_user):
490 def test_system_info_page(self, autologin_user):
491 response = self.app.get(self._get_url())
491 response = self.app.get(self._get_url())
492 response.mustcontain('RhodeCode Community Edition, version {}'.format(
492 response.mustcontain('RhodeCode Community Edition, version {}'.format(
493 rhodecode.__version__))
493 rhodecode.__version__))
494
494
495 def test_system_update_new_version(self, autologin_user):
495 def test_system_update_new_version(self, autologin_user):
496 update_data = {
496 update_data = {
497 'versions': [
497 'versions': [
498 {
498 {
499 'version': '100.3.1415926535',
499 'version': '100.3.1415926535',
500 'general': 'The latest version we are ever going to ship'
500 'general': 'The latest version we are ever going to ship'
501 },
501 },
502 {
502 {
503 'version': '0.0.0',
503 'version': '0.0.0',
504 'general': 'The first version we ever shipped'
504 'general': 'The first version we ever shipped'
505 }
505 }
506 ]
506 ]
507 }
507 }
508 with mock.patch(UPDATE_DATA_QUALNAME, return_value=update_data):
508 with mock.patch(UPDATE_DATA_QUALNAME, return_value=update_data):
509 response = self.app.get(self._get_url('admin_settings_system_update'))
509 response = self.app.get(self._get_url('admin_settings_system_update'))
510 response.mustcontain('A <b>new version</b> is available')
510 response.mustcontain('A <b>new version</b> is available')
511
511
512 def test_system_update_nothing_new(self, autologin_user):
512 def test_system_update_nothing_new(self, autologin_user):
513 update_data = {
513 update_data = {
514 'versions': [
514 'versions': [
515 {
515 {
516 'version': '0.0.0',
516 'version': '0.0.0',
517 'general': 'The first version we ever shipped'
517 'general': 'The first version we ever shipped'
518 }
518 }
519 ]
519 ]
520 }
520 }
521 with mock.patch(UPDATE_DATA_QUALNAME, return_value=update_data):
521 with mock.patch(UPDATE_DATA_QUALNAME, return_value=update_data):
522 response = self.app.get(self._get_url('admin_settings_system_update'))
522 response = self.app.get(self._get_url('admin_settings_system_update'))
523 response.mustcontain(
523 response.mustcontain(
524 'You already have the <b>latest</b> stable version.')
524 'You already have the <b>latest</b> stable version.')
525
525
526 def test_system_update_bad_response(self, autologin_user):
526 def test_system_update_bad_response(self, autologin_user):
527 with mock.patch(UPDATE_DATA_QUALNAME, side_effect=ValueError('foo')):
527 with mock.patch(UPDATE_DATA_QUALNAME, side_effect=ValueError('foo')):
528 response = self.app.get(self._get_url('admin_settings_system_update'))
528 response = self.app.get(self._get_url('admin_settings_system_update'))
529 response.mustcontain(
529 response.mustcontain(
530 'Bad data sent from update server')
530 'Bad data sent from update server')
531
531
532
532
533 @pytest.mark.usefixtures("app")
533 @pytest.mark.usefixtures("app")
534 class TestAdminSettingsIssueTracker(object):
534 class TestAdminSettingsIssueTracker(object):
535 RC_PREFIX = 'rhodecode_'
535 RC_PREFIX = 'rhodecode_'
536 SHORT_PATTERN_KEY = 'issuetracker_pat_'
536 SHORT_PATTERN_KEY = 'issuetracker_pat_'
537 PATTERN_KEY = RC_PREFIX + SHORT_PATTERN_KEY
537 PATTERN_KEY = RC_PREFIX + SHORT_PATTERN_KEY
538
538
539 def test_issuetracker_index(self, autologin_user):
539 def test_issuetracker_index(self, autologin_user):
540 response = self.app.get(url('admin_settings_issuetracker'))
540 response = self.app.get(url('admin_settings_issuetracker'))
541 assert response.status_code == 200
541 assert response.status_code == 200
542
542
543 def test_add_empty_issuetracker_pattern(
543 def test_add_empty_issuetracker_pattern(
544 self, request, autologin_user, csrf_token):
544 self, request, autologin_user, csrf_token):
545 post_url = url('admin_settings_issuetracker_save')
545 post_url = url('admin_settings_issuetracker_save')
546 post_data = {
546 post_data = {
547 'csrf_token': csrf_token
547 'csrf_token': csrf_token
548 }
548 }
549 self.app.post(post_url, post_data, status=302)
549 self.app.post(post_url, post_data, status=302)
550
550
551 def test_add_issuetracker_pattern(
551 def test_add_issuetracker_pattern(
552 self, request, autologin_user, csrf_token):
552 self, request, autologin_user, csrf_token):
553 pattern = 'issuetracker_pat'
553 pattern = 'issuetracker_pat'
554 another_pattern = pattern+'1'
554 another_pattern = pattern+'1'
555 post_url = url('admin_settings_issuetracker_save')
555 post_url = url('admin_settings_issuetracker_save')
556 post_data = {
556 post_data = {
557 'new_pattern_pattern_0': pattern,
557 'new_pattern_pattern_0': pattern,
558 'new_pattern_url_0': 'url',
558 'new_pattern_url_0': 'url',
559 'new_pattern_prefix_0': 'prefix',
559 'new_pattern_prefix_0': 'prefix',
560 'new_pattern_description_0': 'description',
560 'new_pattern_description_0': 'description',
561 'new_pattern_pattern_1': another_pattern,
561 'new_pattern_pattern_1': another_pattern,
562 'new_pattern_url_1': 'url1',
562 'new_pattern_url_1': 'url1',
563 'new_pattern_prefix_1': 'prefix1',
563 'new_pattern_prefix_1': 'prefix1',
564 'new_pattern_description_1': 'description1',
564 'new_pattern_description_1': 'description1',
565 'csrf_token': csrf_token
565 'csrf_token': csrf_token
566 }
566 }
567 self.app.post(post_url, post_data, status=302)
567 self.app.post(post_url, post_data, status=302)
568 settings = SettingsModel().get_all_settings()
568 settings = SettingsModel().get_all_settings()
569 self.uid = md5(pattern)
569 self.uid = md5(pattern)
570 assert settings[self.PATTERN_KEY+self.uid] == pattern
570 assert settings[self.PATTERN_KEY+self.uid] == pattern
571 self.another_uid = md5(another_pattern)
571 self.another_uid = md5(another_pattern)
572 assert settings[self.PATTERN_KEY+self.another_uid] == another_pattern
572 assert settings[self.PATTERN_KEY+self.another_uid] == another_pattern
573
573
574 @request.addfinalizer
574 @request.addfinalizer
575 def cleanup():
575 def cleanup():
576 defaults = SettingsModel().get_all_settings()
576 defaults = SettingsModel().get_all_settings()
577
577
578 entries = [name for name in defaults if (
578 entries = [name for name in defaults if (
579 (self.uid in name) or (self.another_uid) in name)]
579 (self.uid in name) or (self.another_uid) in name)]
580 start = len(self.RC_PREFIX)
580 start = len(self.RC_PREFIX)
581 for del_key in entries:
581 for del_key in entries:
582 # TODO: anderson: get_by_name needs name without prefix
582 # TODO: anderson: get_by_name needs name without prefix
583 entry = SettingsModel().get_setting_by_name(del_key[start:])
583 entry = SettingsModel().get_setting_by_name(del_key[start:])
584 Session().delete(entry)
584 Session().delete(entry)
585
585
586 Session().commit()
586 Session().commit()
587
587
588 def test_edit_issuetracker_pattern(
588 def test_edit_issuetracker_pattern(
589 self, autologin_user, backend, csrf_token, request):
589 self, autologin_user, backend, csrf_token, request):
590 old_pattern = 'issuetracker_pat'
590 old_pattern = 'issuetracker_pat'
591 old_uid = md5(old_pattern)
591 old_uid = md5(old_pattern)
592 pattern = 'issuetracker_pat_new'
592 pattern = 'issuetracker_pat_new'
593 self.new_uid = md5(pattern)
593 self.new_uid = md5(pattern)
594
594
595 SettingsModel().create_or_update_setting(
595 SettingsModel().create_or_update_setting(
596 self.SHORT_PATTERN_KEY+old_uid, old_pattern, 'unicode')
596 self.SHORT_PATTERN_KEY+old_uid, old_pattern, 'unicode')
597
597
598 post_url = url('admin_settings_issuetracker_save')
598 post_url = url('admin_settings_issuetracker_save')
599 post_data = {
599 post_data = {
600 'new_pattern_pattern_0': pattern,
600 'new_pattern_pattern_0': pattern,
601 'new_pattern_url_0': 'url',
601 'new_pattern_url_0': 'url',
602 'new_pattern_prefix_0': 'prefix',
602 'new_pattern_prefix_0': 'prefix',
603 'new_pattern_description_0': 'description',
603 'new_pattern_description_0': 'description',
604 'uid': old_uid,
604 'uid': old_uid,
605 'csrf_token': csrf_token
605 'csrf_token': csrf_token
606 }
606 }
607 self.app.post(post_url, post_data, status=302)
607 self.app.post(post_url, post_data, status=302)
608 settings = SettingsModel().get_all_settings()
608 settings = SettingsModel().get_all_settings()
609 assert settings[self.PATTERN_KEY+self.new_uid] == pattern
609 assert settings[self.PATTERN_KEY+self.new_uid] == pattern
610 assert self.PATTERN_KEY+old_uid not in settings
610 assert self.PATTERN_KEY+old_uid not in settings
611
611
612 @request.addfinalizer
612 @request.addfinalizer
613 def cleanup():
613 def cleanup():
614 IssueTrackerSettingsModel().delete_entries(self.new_uid)
614 IssueTrackerSettingsModel().delete_entries(self.new_uid)
615
615
616 def test_replace_issuetracker_pattern_description(
616 def test_replace_issuetracker_pattern_description(
617 self, autologin_user, csrf_token, request, settings_util):
617 self, autologin_user, csrf_token, request, settings_util):
618 prefix = 'issuetracker'
618 prefix = 'issuetracker'
619 pattern = 'issuetracker_pat'
619 pattern = 'issuetracker_pat'
620 self.uid = md5(pattern)
620 self.uid = md5(pattern)
621 pattern_key = '_'.join([prefix, 'pat', self.uid])
621 pattern_key = '_'.join([prefix, 'pat', self.uid])
622 rc_pattern_key = '_'.join(['rhodecode', pattern_key])
622 rc_pattern_key = '_'.join(['rhodecode', pattern_key])
623 desc_key = '_'.join([prefix, 'desc', self.uid])
623 desc_key = '_'.join([prefix, 'desc', self.uid])
624 rc_desc_key = '_'.join(['rhodecode', desc_key])
624 rc_desc_key = '_'.join(['rhodecode', desc_key])
625 new_description = 'new_description'
625 new_description = 'new_description'
626
626
627 settings_util.create_rhodecode_setting(
627 settings_util.create_rhodecode_setting(
628 pattern_key, pattern, 'unicode', cleanup=False)
628 pattern_key, pattern, 'unicode', cleanup=False)
629 settings_util.create_rhodecode_setting(
629 settings_util.create_rhodecode_setting(
630 desc_key, 'old description', 'unicode', cleanup=False)
630 desc_key, 'old description', 'unicode', cleanup=False)
631
631
632 post_url = url('admin_settings_issuetracker_save')
632 post_url = url('admin_settings_issuetracker_save')
633 post_data = {
633 post_data = {
634 'new_pattern_pattern_0': pattern,
634 'new_pattern_pattern_0': pattern,
635 'new_pattern_url_0': 'url',
635 'new_pattern_url_0': 'url',
636 'new_pattern_prefix_0': 'prefix',
636 'new_pattern_prefix_0': 'prefix',
637 'new_pattern_description_0': new_description,
637 'new_pattern_description_0': new_description,
638 'uid': self.uid,
638 'uid': self.uid,
639 'csrf_token': csrf_token
639 'csrf_token': csrf_token
640 }
640 }
641 self.app.post(post_url, post_data, status=302)
641 self.app.post(post_url, post_data, status=302)
642 settings = SettingsModel().get_all_settings()
642 settings = SettingsModel().get_all_settings()
643 assert settings[rc_pattern_key] == pattern
643 assert settings[rc_pattern_key] == pattern
644 assert settings[rc_desc_key] == new_description
644 assert settings[rc_desc_key] == new_description
645
645
646 @request.addfinalizer
646 @request.addfinalizer
647 def cleanup():
647 def cleanup():
648 IssueTrackerSettingsModel().delete_entries(self.uid)
648 IssueTrackerSettingsModel().delete_entries(self.uid)
649
649
650 def test_delete_issuetracker_pattern(
650 def test_delete_issuetracker_pattern(
651 self, autologin_user, backend, csrf_token, settings_util):
651 self, autologin_user, backend, csrf_token, settings_util):
652 pattern = 'issuetracker_pat'
652 pattern = 'issuetracker_pat'
653 uid = md5(pattern)
653 uid = md5(pattern)
654 settings_util.create_rhodecode_setting(
654 settings_util.create_rhodecode_setting(
655 self.SHORT_PATTERN_KEY+uid, pattern, 'unicode', cleanup=False)
655 self.SHORT_PATTERN_KEY+uid, pattern, 'unicode', cleanup=False)
656
656
657 post_url = url('admin_issuetracker_delete')
657 post_url = url('admin_issuetracker_delete')
658 post_data = {
658 post_data = {
659 '_method': 'delete',
659 '_method': 'delete',
660 'uid': uid,
660 'uid': uid,
661 'csrf_token': csrf_token
661 'csrf_token': csrf_token
662 }
662 }
663 self.app.post(post_url, post_data, status=302)
663 self.app.post(post_url, post_data, status=302)
664 settings = SettingsModel().get_all_settings()
664 settings = SettingsModel().get_all_settings()
665 assert 'rhodecode_%s%s' % (self.SHORT_PATTERN_KEY, uid) not in settings
665 assert 'rhodecode_%s%s' % (self.SHORT_PATTERN_KEY, uid) not in settings
General Comments 0
You need to be logged in to leave comments. Login now