##// END OF EJS Templates
issue-tracker: use stored issueTracker patterns for repo instead of initializing model every time.
bart -
r4201:ecf6f84c stable
parent child Browse files
Show More
@@ -1,802 +1,803 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2019 RhodeCode GmbH
3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import time
21 import time
22 import logging
22 import logging
23 import operator
23 import operator
24
24
25 from pyramid import compat
25 from pyramid import compat
26 from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPBadRequest
26 from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPBadRequest
27
27
28 from rhodecode.lib import helpers as h, diffs, rc_cache
28 from rhodecode.lib import helpers as h, diffs, rc_cache
29 from rhodecode.lib.utils2 import (
29 from rhodecode.lib.utils2 import (
30 StrictAttributeDict, str2bool, safe_int, datetime_to_time, safe_unicode)
30 StrictAttributeDict, str2bool, safe_int, datetime_to_time, safe_unicode)
31 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
31 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
32 from rhodecode.lib.vcs.backends.base import EmptyCommit
32 from rhodecode.lib.vcs.backends.base import EmptyCommit
33 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
33 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
34 from rhodecode.model import repo
34 from rhodecode.model import repo
35 from rhodecode.model import repo_group
35 from rhodecode.model import repo_group
36 from rhodecode.model import user_group
36 from rhodecode.model import user_group
37 from rhodecode.model import user
37 from rhodecode.model import user
38 from rhodecode.model.db import User
38 from rhodecode.model.db import User
39 from rhodecode.model.scm import ScmModel
39 from rhodecode.model.scm import ScmModel
40 from rhodecode.model.settings import VcsSettingsModel
40 from rhodecode.model.settings import VcsSettingsModel, IssueTrackerSettingsModel
41 from rhodecode.model.repo import ReadmeFinder
41 from rhodecode.model.repo import ReadmeFinder
42
42
43 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
44
44
45
45
46 ADMIN_PREFIX = '/_admin'
46 ADMIN_PREFIX = '/_admin'
47 STATIC_FILE_PREFIX = '/_static'
47 STATIC_FILE_PREFIX = '/_static'
48
48
49 URL_NAME_REQUIREMENTS = {
49 URL_NAME_REQUIREMENTS = {
50 # group name can have a slash in them, but they must not end with a slash
50 # group name can have a slash in them, but they must not end with a slash
51 'group_name': r'.*?[^/]',
51 'group_name': r'.*?[^/]',
52 'repo_group_name': r'.*?[^/]',
52 'repo_group_name': r'.*?[^/]',
53 # repo names can have a slash in them, but they must not end with a slash
53 # repo names can have a slash in them, but they must not end with a slash
54 'repo_name': r'.*?[^/]',
54 'repo_name': r'.*?[^/]',
55 # file path eats up everything at the end
55 # file path eats up everything at the end
56 'f_path': r'.*',
56 'f_path': r'.*',
57 # reference types
57 # reference types
58 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
58 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
59 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
59 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
60 }
60 }
61
61
62
62
63 def add_route_with_slash(config,name, pattern, **kw):
63 def add_route_with_slash(config,name, pattern, **kw):
64 config.add_route(name, pattern, **kw)
64 config.add_route(name, pattern, **kw)
65 if not pattern.endswith('/'):
65 if not pattern.endswith('/'):
66 config.add_route(name + '_slash', pattern + '/', **kw)
66 config.add_route(name + '_slash', pattern + '/', **kw)
67
67
68
68
69 def add_route_requirements(route_path, requirements=None):
69 def add_route_requirements(route_path, requirements=None):
70 """
70 """
71 Adds regex requirements to pyramid routes using a mapping dict
71 Adds regex requirements to pyramid routes using a mapping dict
72 e.g::
72 e.g::
73 add_route_requirements('{repo_name}/settings')
73 add_route_requirements('{repo_name}/settings')
74 """
74 """
75 requirements = requirements or URL_NAME_REQUIREMENTS
75 requirements = requirements or URL_NAME_REQUIREMENTS
76 for key, regex in requirements.items():
76 for key, regex in requirements.items():
77 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
77 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
78 return route_path
78 return route_path
79
79
80
80
81 def get_format_ref_id(repo):
81 def get_format_ref_id(repo):
82 """Returns a `repo` specific reference formatter function"""
82 """Returns a `repo` specific reference formatter function"""
83 if h.is_svn(repo):
83 if h.is_svn(repo):
84 return _format_ref_id_svn
84 return _format_ref_id_svn
85 else:
85 else:
86 return _format_ref_id
86 return _format_ref_id
87
87
88
88
89 def _format_ref_id(name, raw_id):
89 def _format_ref_id(name, raw_id):
90 """Default formatting of a given reference `name`"""
90 """Default formatting of a given reference `name`"""
91 return name
91 return name
92
92
93
93
94 def _format_ref_id_svn(name, raw_id):
94 def _format_ref_id_svn(name, raw_id):
95 """Special way of formatting a reference for Subversion including path"""
95 """Special way of formatting a reference for Subversion including path"""
96 return '%s@%s' % (name, raw_id)
96 return '%s@%s' % (name, raw_id)
97
97
98
98
99 class TemplateArgs(StrictAttributeDict):
99 class TemplateArgs(StrictAttributeDict):
100 pass
100 pass
101
101
102
102
103 class BaseAppView(object):
103 class BaseAppView(object):
104
104
105 def __init__(self, context, request):
105 def __init__(self, context, request):
106 self.request = request
106 self.request = request
107 self.context = context
107 self.context = context
108 self.session = request.session
108 self.session = request.session
109 if not hasattr(request, 'user'):
109 if not hasattr(request, 'user'):
110 # NOTE(marcink): edge case, we ended up in matched route
110 # NOTE(marcink): edge case, we ended up in matched route
111 # but probably of web-app context, e.g API CALL/VCS CALL
111 # but probably of web-app context, e.g API CALL/VCS CALL
112 if hasattr(request, 'vcs_call') or hasattr(request, 'rpc_method'):
112 if hasattr(request, 'vcs_call') or hasattr(request, 'rpc_method'):
113 log.warning('Unable to process request `%s` in this scope', request)
113 log.warning('Unable to process request `%s` in this scope', request)
114 raise HTTPBadRequest()
114 raise HTTPBadRequest()
115
115
116 self._rhodecode_user = request.user # auth user
116 self._rhodecode_user = request.user # auth user
117 self._rhodecode_db_user = self._rhodecode_user.get_instance()
117 self._rhodecode_db_user = self._rhodecode_user.get_instance()
118 self._maybe_needs_password_change(
118 self._maybe_needs_password_change(
119 request.matched_route.name, self._rhodecode_db_user)
119 request.matched_route.name, self._rhodecode_db_user)
120
120
121 def _maybe_needs_password_change(self, view_name, user_obj):
121 def _maybe_needs_password_change(self, view_name, user_obj):
122 log.debug('Checking if user %s needs password change on view %s',
122 log.debug('Checking if user %s needs password change on view %s',
123 user_obj, view_name)
123 user_obj, view_name)
124 skip_user_views = [
124 skip_user_views = [
125 'logout', 'login',
125 'logout', 'login',
126 'my_account_password', 'my_account_password_update'
126 'my_account_password', 'my_account_password_update'
127 ]
127 ]
128
128
129 if not user_obj:
129 if not user_obj:
130 return
130 return
131
131
132 if user_obj.username == User.DEFAULT_USER:
132 if user_obj.username == User.DEFAULT_USER:
133 return
133 return
134
134
135 now = time.time()
135 now = time.time()
136 should_change = user_obj.user_data.get('force_password_change')
136 should_change = user_obj.user_data.get('force_password_change')
137 change_after = safe_int(should_change) or 0
137 change_after = safe_int(should_change) or 0
138 if should_change and now > change_after:
138 if should_change and now > change_after:
139 log.debug('User %s requires password change', user_obj)
139 log.debug('User %s requires password change', user_obj)
140 h.flash('You are required to change your password', 'warning',
140 h.flash('You are required to change your password', 'warning',
141 ignore_duplicate=True)
141 ignore_duplicate=True)
142
142
143 if view_name not in skip_user_views:
143 if view_name not in skip_user_views:
144 raise HTTPFound(
144 raise HTTPFound(
145 self.request.route_path('my_account_password'))
145 self.request.route_path('my_account_password'))
146
146
147 def _log_creation_exception(self, e, repo_name):
147 def _log_creation_exception(self, e, repo_name):
148 _ = self.request.translate
148 _ = self.request.translate
149 reason = None
149 reason = None
150 if len(e.args) == 2:
150 if len(e.args) == 2:
151 reason = e.args[1]
151 reason = e.args[1]
152
152
153 if reason == 'INVALID_CERTIFICATE':
153 if reason == 'INVALID_CERTIFICATE':
154 log.exception(
154 log.exception(
155 'Exception creating a repository: invalid certificate')
155 'Exception creating a repository: invalid certificate')
156 msg = (_('Error creating repository %s: invalid certificate')
156 msg = (_('Error creating repository %s: invalid certificate')
157 % repo_name)
157 % repo_name)
158 else:
158 else:
159 log.exception("Exception creating a repository")
159 log.exception("Exception creating a repository")
160 msg = (_('Error creating repository %s')
160 msg = (_('Error creating repository %s')
161 % repo_name)
161 % repo_name)
162 return msg
162 return msg
163
163
164 def _get_local_tmpl_context(self, include_app_defaults=True):
164 def _get_local_tmpl_context(self, include_app_defaults=True):
165 c = TemplateArgs()
165 c = TemplateArgs()
166 c.auth_user = self.request.user
166 c.auth_user = self.request.user
167 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
167 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
168 c.rhodecode_user = self.request.user
168 c.rhodecode_user = self.request.user
169
169
170 if include_app_defaults:
170 if include_app_defaults:
171 from rhodecode.lib.base import attach_context_attributes
171 from rhodecode.lib.base import attach_context_attributes
172 attach_context_attributes(c, self.request, self.request.user.user_id)
172 attach_context_attributes(c, self.request, self.request.user.user_id)
173
173
174 c.is_super_admin = c.auth_user.is_admin
174 c.is_super_admin = c.auth_user.is_admin
175
175
176 c.can_create_repo = c.is_super_admin
176 c.can_create_repo = c.is_super_admin
177 c.can_create_repo_group = c.is_super_admin
177 c.can_create_repo_group = c.is_super_admin
178 c.can_create_user_group = c.is_super_admin
178 c.can_create_user_group = c.is_super_admin
179
179
180 c.is_delegated_admin = False
180 c.is_delegated_admin = False
181
181
182 if not c.auth_user.is_default and not c.is_super_admin:
182 if not c.auth_user.is_default and not c.is_super_admin:
183 c.can_create_repo = h.HasPermissionAny('hg.create.repository')(
183 c.can_create_repo = h.HasPermissionAny('hg.create.repository')(
184 user=self.request.user)
184 user=self.request.user)
185 repositories = c.auth_user.repositories_admin or c.can_create_repo
185 repositories = c.auth_user.repositories_admin or c.can_create_repo
186
186
187 c.can_create_repo_group = h.HasPermissionAny('hg.repogroup.create.true')(
187 c.can_create_repo_group = h.HasPermissionAny('hg.repogroup.create.true')(
188 user=self.request.user)
188 user=self.request.user)
189 repository_groups = c.auth_user.repository_groups_admin or c.can_create_repo_group
189 repository_groups = c.auth_user.repository_groups_admin or c.can_create_repo_group
190
190
191 c.can_create_user_group = h.HasPermissionAny('hg.usergroup.create.true')(
191 c.can_create_user_group = h.HasPermissionAny('hg.usergroup.create.true')(
192 user=self.request.user)
192 user=self.request.user)
193 user_groups = c.auth_user.user_groups_admin or c.can_create_user_group
193 user_groups = c.auth_user.user_groups_admin or c.can_create_user_group
194 # delegated admin can create, or manage some objects
194 # delegated admin can create, or manage some objects
195 c.is_delegated_admin = repositories or repository_groups or user_groups
195 c.is_delegated_admin = repositories or repository_groups or user_groups
196 return c
196 return c
197
197
198 def _get_template_context(self, tmpl_args, **kwargs):
198 def _get_template_context(self, tmpl_args, **kwargs):
199
199
200 local_tmpl_args = {
200 local_tmpl_args = {
201 'defaults': {},
201 'defaults': {},
202 'errors': {},
202 'errors': {},
203 'c': tmpl_args
203 'c': tmpl_args
204 }
204 }
205 local_tmpl_args.update(kwargs)
205 local_tmpl_args.update(kwargs)
206 return local_tmpl_args
206 return local_tmpl_args
207
207
208 def load_default_context(self):
208 def load_default_context(self):
209 """
209 """
210 example:
210 example:
211
211
212 def load_default_context(self):
212 def load_default_context(self):
213 c = self._get_local_tmpl_context()
213 c = self._get_local_tmpl_context()
214 c.custom_var = 'foobar'
214 c.custom_var = 'foobar'
215
215
216 return c
216 return c
217 """
217 """
218 raise NotImplementedError('Needs implementation in view class')
218 raise NotImplementedError('Needs implementation in view class')
219
219
220
220
221 class RepoAppView(BaseAppView):
221 class RepoAppView(BaseAppView):
222
222
223 def __init__(self, context, request):
223 def __init__(self, context, request):
224 super(RepoAppView, self).__init__(context, request)
224 super(RepoAppView, self).__init__(context, request)
225 self.db_repo = request.db_repo
225 self.db_repo = request.db_repo
226 self.db_repo_name = self.db_repo.repo_name
226 self.db_repo_name = self.db_repo.repo_name
227 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
227 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
228 self.db_repo_artifacts = ScmModel().get_artifacts(self.db_repo)
228 self.db_repo_artifacts = ScmModel().get_artifacts(self.db_repo)
229 self.db_repo_patterns = IssueTrackerSettingsModel(repo=self.db_repo)
229
230
230 def _handle_missing_requirements(self, error):
231 def _handle_missing_requirements(self, error):
231 log.error(
232 log.error(
232 'Requirements are missing for repository %s: %s',
233 'Requirements are missing for repository %s: %s',
233 self.db_repo_name, safe_unicode(error))
234 self.db_repo_name, safe_unicode(error))
234
235
235 def _get_local_tmpl_context(self, include_app_defaults=True):
236 def _get_local_tmpl_context(self, include_app_defaults=True):
236 _ = self.request.translate
237 _ = self.request.translate
237 c = super(RepoAppView, self)._get_local_tmpl_context(
238 c = super(RepoAppView, self)._get_local_tmpl_context(
238 include_app_defaults=include_app_defaults)
239 include_app_defaults=include_app_defaults)
239
240
240 # register common vars for this type of view
241 # register common vars for this type of view
241 c.rhodecode_db_repo = self.db_repo
242 c.rhodecode_db_repo = self.db_repo
242 c.repo_name = self.db_repo_name
243 c.repo_name = self.db_repo_name
243 c.repository_pull_requests = self.db_repo_pull_requests
244 c.repository_pull_requests = self.db_repo_pull_requests
244 c.repository_artifacts = self.db_repo_artifacts
245 c.repository_artifacts = self.db_repo_artifacts
245 c.repository_is_user_following = ScmModel().is_following_repo(
246 c.repository_is_user_following = ScmModel().is_following_repo(
246 self.db_repo_name, self._rhodecode_user.user_id)
247 self.db_repo_name, self._rhodecode_user.user_id)
247 self.path_filter = PathFilter(None)
248 self.path_filter = PathFilter(None)
248
249
249 c.repository_requirements_missing = {}
250 c.repository_requirements_missing = {}
250 try:
251 try:
251 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
252 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
252 # NOTE(marcink):
253 # NOTE(marcink):
253 # comparison to None since if it's an object __bool__ is expensive to
254 # comparison to None since if it's an object __bool__ is expensive to
254 # calculate
255 # calculate
255 if self.rhodecode_vcs_repo is not None:
256 if self.rhodecode_vcs_repo is not None:
256 path_perms = self.rhodecode_vcs_repo.get_path_permissions(
257 path_perms = self.rhodecode_vcs_repo.get_path_permissions(
257 c.auth_user.username)
258 c.auth_user.username)
258 self.path_filter = PathFilter(path_perms)
259 self.path_filter = PathFilter(path_perms)
259 except RepositoryRequirementError as e:
260 except RepositoryRequirementError as e:
260 c.repository_requirements_missing = {'error': str(e)}
261 c.repository_requirements_missing = {'error': str(e)}
261 self._handle_missing_requirements(e)
262 self._handle_missing_requirements(e)
262 self.rhodecode_vcs_repo = None
263 self.rhodecode_vcs_repo = None
263
264
264 c.path_filter = self.path_filter # used by atom_feed_entry.mako
265 c.path_filter = self.path_filter # used by atom_feed_entry.mako
265
266
266 if self.rhodecode_vcs_repo is None:
267 if self.rhodecode_vcs_repo is None:
267 # unable to fetch this repo as vcs instance, report back to user
268 # unable to fetch this repo as vcs instance, report back to user
268 h.flash(_(
269 h.flash(_(
269 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
270 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
270 "Please check if it exist, or is not damaged.") %
271 "Please check if it exist, or is not damaged.") %
271 {'repo_name': c.repo_name},
272 {'repo_name': c.repo_name},
272 category='error', ignore_duplicate=True)
273 category='error', ignore_duplicate=True)
273 if c.repository_requirements_missing:
274 if c.repository_requirements_missing:
274 route = self.request.matched_route.name
275 route = self.request.matched_route.name
275 if route.startswith(('edit_repo', 'repo_summary')):
276 if route.startswith(('edit_repo', 'repo_summary')):
276 # allow summary and edit repo on missing requirements
277 # allow summary and edit repo on missing requirements
277 return c
278 return c
278
279
279 raise HTTPFound(
280 raise HTTPFound(
280 h.route_path('repo_summary', repo_name=self.db_repo_name))
281 h.route_path('repo_summary', repo_name=self.db_repo_name))
281
282
282 else: # redirect if we don't show missing requirements
283 else: # redirect if we don't show missing requirements
283 raise HTTPFound(h.route_path('home'))
284 raise HTTPFound(h.route_path('home'))
284
285
285 c.has_origin_repo_read_perm = False
286 c.has_origin_repo_read_perm = False
286 if self.db_repo.fork:
287 if self.db_repo.fork:
287 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
288 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
288 'repository.write', 'repository.read', 'repository.admin')(
289 'repository.write', 'repository.read', 'repository.admin')(
289 self.db_repo.fork.repo_name, 'summary fork link')
290 self.db_repo.fork.repo_name, 'summary fork link')
290
291
291 return c
292 return c
292
293
293 def _get_f_path_unchecked(self, matchdict, default=None):
294 def _get_f_path_unchecked(self, matchdict, default=None):
294 """
295 """
295 Should only be used by redirects, everything else should call _get_f_path
296 Should only be used by redirects, everything else should call _get_f_path
296 """
297 """
297 f_path = matchdict.get('f_path')
298 f_path = matchdict.get('f_path')
298 if f_path:
299 if f_path:
299 # fix for multiple initial slashes that causes errors for GIT
300 # fix for multiple initial slashes that causes errors for GIT
300 return f_path.lstrip('/')
301 return f_path.lstrip('/')
301
302
302 return default
303 return default
303
304
304 def _get_f_path(self, matchdict, default=None):
305 def _get_f_path(self, matchdict, default=None):
305 f_path_match = self._get_f_path_unchecked(matchdict, default)
306 f_path_match = self._get_f_path_unchecked(matchdict, default)
306 return self.path_filter.assert_path_permissions(f_path_match)
307 return self.path_filter.assert_path_permissions(f_path_match)
307
308
308 def _get_general_setting(self, target_repo, settings_key, default=False):
309 def _get_general_setting(self, target_repo, settings_key, default=False):
309 settings_model = VcsSettingsModel(repo=target_repo)
310 settings_model = VcsSettingsModel(repo=target_repo)
310 settings = settings_model.get_general_settings()
311 settings = settings_model.get_general_settings()
311 return settings.get(settings_key, default)
312 return settings.get(settings_key, default)
312
313
313 def _get_repo_setting(self, target_repo, settings_key, default=False):
314 def _get_repo_setting(self, target_repo, settings_key, default=False):
314 settings_model = VcsSettingsModel(repo=target_repo)
315 settings_model = VcsSettingsModel(repo=target_repo)
315 settings = settings_model.get_repo_settings_inherited()
316 settings = settings_model.get_repo_settings_inherited()
316 return settings.get(settings_key, default)
317 return settings.get(settings_key, default)
317
318
318 def _get_readme_data(self, db_repo, renderer_type, commit_id=None, path='/'):
319 def _get_readme_data(self, db_repo, renderer_type, commit_id=None, path='/'):
319 log.debug('Looking for README file at path %s', path)
320 log.debug('Looking for README file at path %s', path)
320 if commit_id:
321 if commit_id:
321 landing_commit_id = commit_id
322 landing_commit_id = commit_id
322 else:
323 else:
323 landing_commit = db_repo.get_landing_commit()
324 landing_commit = db_repo.get_landing_commit()
324 if isinstance(landing_commit, EmptyCommit):
325 if isinstance(landing_commit, EmptyCommit):
325 return None, None
326 return None, None
326 landing_commit_id = landing_commit.raw_id
327 landing_commit_id = landing_commit.raw_id
327
328
328 cache_namespace_uid = 'cache_repo.{}'.format(db_repo.repo_id)
329 cache_namespace_uid = 'cache_repo.{}'.format(db_repo.repo_id)
329 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
330 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
330 start = time.time()
331 start = time.time()
331
332
332 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
333 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
333 def generate_repo_readme(repo_id, _commit_id, _repo_name, _readme_search_path, _renderer_type):
334 def generate_repo_readme(repo_id, _commit_id, _repo_name, _readme_search_path, _renderer_type):
334 readme_data = None
335 readme_data = None
335 readme_filename = None
336 readme_filename = None
336
337
337 commit = db_repo.get_commit(_commit_id)
338 commit = db_repo.get_commit(_commit_id)
338 log.debug("Searching for a README file at commit %s.", _commit_id)
339 log.debug("Searching for a README file at commit %s.", _commit_id)
339 readme_node = ReadmeFinder(_renderer_type).search(commit, path=_readme_search_path)
340 readme_node = ReadmeFinder(_renderer_type).search(commit, path=_readme_search_path)
340
341
341 if readme_node:
342 if readme_node:
342 log.debug('Found README node: %s', readme_node)
343 log.debug('Found README node: %s', readme_node)
343 relative_urls = {
344 relative_urls = {
344 'raw': h.route_path(
345 'raw': h.route_path(
345 'repo_file_raw', repo_name=_repo_name,
346 'repo_file_raw', repo_name=_repo_name,
346 commit_id=commit.raw_id, f_path=readme_node.path),
347 commit_id=commit.raw_id, f_path=readme_node.path),
347 'standard': h.route_path(
348 'standard': h.route_path(
348 'repo_files', repo_name=_repo_name,
349 'repo_files', repo_name=_repo_name,
349 commit_id=commit.raw_id, f_path=readme_node.path),
350 commit_id=commit.raw_id, f_path=readme_node.path),
350 }
351 }
351 readme_data = self._render_readme_or_none(commit, readme_node, relative_urls)
352 readme_data = self._render_readme_or_none(commit, readme_node, relative_urls)
352 readme_filename = readme_node.unicode_path
353 readme_filename = readme_node.unicode_path
353
354
354 return readme_data, readme_filename
355 return readme_data, readme_filename
355
356
356 readme_data, readme_filename = generate_repo_readme(
357 readme_data, readme_filename = generate_repo_readme(
357 db_repo.repo_id, landing_commit_id, db_repo.repo_name, path, renderer_type,)
358 db_repo.repo_id, landing_commit_id, db_repo.repo_name, path, renderer_type,)
358 compute_time = time.time() - start
359 compute_time = time.time() - start
359 log.debug('Repo README for path %s generated and computed in %.4fs',
360 log.debug('Repo README for path %s generated and computed in %.4fs',
360 path, compute_time)
361 path, compute_time)
361 return readme_data, readme_filename
362 return readme_data, readme_filename
362
363
363 def _render_readme_or_none(self, commit, readme_node, relative_urls):
364 def _render_readme_or_none(self, commit, readme_node, relative_urls):
364 log.debug('Found README file `%s` rendering...', readme_node.path)
365 log.debug('Found README file `%s` rendering...', readme_node.path)
365 renderer = MarkupRenderer()
366 renderer = MarkupRenderer()
366 try:
367 try:
367 html_source = renderer.render(
368 html_source = renderer.render(
368 readme_node.content, filename=readme_node.path)
369 readme_node.content, filename=readme_node.path)
369 if relative_urls:
370 if relative_urls:
370 return relative_links(html_source, relative_urls)
371 return relative_links(html_source, relative_urls)
371 return html_source
372 return html_source
372 except Exception:
373 except Exception:
373 log.exception(
374 log.exception(
374 "Exception while trying to render the README")
375 "Exception while trying to render the README")
375
376
376 def get_recache_flag(self):
377 def get_recache_flag(self):
377 for flag_name in ['force_recache', 'force-recache', 'no-cache']:
378 for flag_name in ['force_recache', 'force-recache', 'no-cache']:
378 flag_val = self.request.GET.get(flag_name)
379 flag_val = self.request.GET.get(flag_name)
379 if str2bool(flag_val):
380 if str2bool(flag_val):
380 return True
381 return True
381 return False
382 return False
382
383
383
384
384 class PathFilter(object):
385 class PathFilter(object):
385
386
386 # Expects and instance of BasePathPermissionChecker or None
387 # Expects and instance of BasePathPermissionChecker or None
387 def __init__(self, permission_checker):
388 def __init__(self, permission_checker):
388 self.permission_checker = permission_checker
389 self.permission_checker = permission_checker
389
390
390 def assert_path_permissions(self, path):
391 def assert_path_permissions(self, path):
391 if self.path_access_allowed(path):
392 if self.path_access_allowed(path):
392 return path
393 return path
393 raise HTTPForbidden()
394 raise HTTPForbidden()
394
395
395 def path_access_allowed(self, path):
396 def path_access_allowed(self, path):
396 log.debug('Checking ACL permissions for PathFilter for `%s`', path)
397 log.debug('Checking ACL permissions for PathFilter for `%s`', path)
397 if self.permission_checker:
398 if self.permission_checker:
398 return path and self.permission_checker.has_access(path)
399 return path and self.permission_checker.has_access(path)
399 return True
400 return True
400
401
401 def filter_patchset(self, patchset):
402 def filter_patchset(self, patchset):
402 if not self.permission_checker or not patchset:
403 if not self.permission_checker or not patchset:
403 return patchset, False
404 return patchset, False
404 had_filtered = False
405 had_filtered = False
405 filtered_patchset = []
406 filtered_patchset = []
406 for patch in patchset:
407 for patch in patchset:
407 filename = patch.get('filename', None)
408 filename = patch.get('filename', None)
408 if not filename or self.permission_checker.has_access(filename):
409 if not filename or self.permission_checker.has_access(filename):
409 filtered_patchset.append(patch)
410 filtered_patchset.append(patch)
410 else:
411 else:
411 had_filtered = True
412 had_filtered = True
412 if had_filtered:
413 if had_filtered:
413 if isinstance(patchset, diffs.LimitedDiffContainer):
414 if isinstance(patchset, diffs.LimitedDiffContainer):
414 filtered_patchset = diffs.LimitedDiffContainer(patchset.diff_limit, patchset.cur_diff_size, filtered_patchset)
415 filtered_patchset = diffs.LimitedDiffContainer(patchset.diff_limit, patchset.cur_diff_size, filtered_patchset)
415 return filtered_patchset, True
416 return filtered_patchset, True
416 else:
417 else:
417 return patchset, False
418 return patchset, False
418
419
419 def render_patchset_filtered(self, diffset, patchset, source_ref=None, target_ref=None):
420 def render_patchset_filtered(self, diffset, patchset, source_ref=None, target_ref=None):
420 filtered_patchset, has_hidden_changes = self.filter_patchset(patchset)
421 filtered_patchset, has_hidden_changes = self.filter_patchset(patchset)
421 result = diffset.render_patchset(
422 result = diffset.render_patchset(
422 filtered_patchset, source_ref=source_ref, target_ref=target_ref)
423 filtered_patchset, source_ref=source_ref, target_ref=target_ref)
423 result.has_hidden_changes = has_hidden_changes
424 result.has_hidden_changes = has_hidden_changes
424 return result
425 return result
425
426
426 def get_raw_patch(self, diff_processor):
427 def get_raw_patch(self, diff_processor):
427 if self.permission_checker is None:
428 if self.permission_checker is None:
428 return diff_processor.as_raw()
429 return diff_processor.as_raw()
429 elif self.permission_checker.has_full_access:
430 elif self.permission_checker.has_full_access:
430 return diff_processor.as_raw()
431 return diff_processor.as_raw()
431 else:
432 else:
432 return '# Repository has user-specific filters, raw patch generation is disabled.'
433 return '# Repository has user-specific filters, raw patch generation is disabled.'
433
434
434 @property
435 @property
435 def is_enabled(self):
436 def is_enabled(self):
436 return self.permission_checker is not None
437 return self.permission_checker is not None
437
438
438
439
439 class RepoGroupAppView(BaseAppView):
440 class RepoGroupAppView(BaseAppView):
440 def __init__(self, context, request):
441 def __init__(self, context, request):
441 super(RepoGroupAppView, self).__init__(context, request)
442 super(RepoGroupAppView, self).__init__(context, request)
442 self.db_repo_group = request.db_repo_group
443 self.db_repo_group = request.db_repo_group
443 self.db_repo_group_name = self.db_repo_group.group_name
444 self.db_repo_group_name = self.db_repo_group.group_name
444
445
445 def _get_local_tmpl_context(self, include_app_defaults=True):
446 def _get_local_tmpl_context(self, include_app_defaults=True):
446 _ = self.request.translate
447 _ = self.request.translate
447 c = super(RepoGroupAppView, self)._get_local_tmpl_context(
448 c = super(RepoGroupAppView, self)._get_local_tmpl_context(
448 include_app_defaults=include_app_defaults)
449 include_app_defaults=include_app_defaults)
449 c.repo_group = self.db_repo_group
450 c.repo_group = self.db_repo_group
450 return c
451 return c
451
452
452 def _revoke_perms_on_yourself(self, form_result):
453 def _revoke_perms_on_yourself(self, form_result):
453 _updates = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
454 _updates = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
454 form_result['perm_updates'])
455 form_result['perm_updates'])
455 _additions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
456 _additions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
456 form_result['perm_additions'])
457 form_result['perm_additions'])
457 _deletions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
458 _deletions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
458 form_result['perm_deletions'])
459 form_result['perm_deletions'])
459 admin_perm = 'group.admin'
460 admin_perm = 'group.admin'
460 if _updates and _updates[0][1] != admin_perm or \
461 if _updates and _updates[0][1] != admin_perm or \
461 _additions and _additions[0][1] != admin_perm or \
462 _additions and _additions[0][1] != admin_perm or \
462 _deletions and _deletions[0][1] != admin_perm:
463 _deletions and _deletions[0][1] != admin_perm:
463 return True
464 return True
464 return False
465 return False
465
466
466
467
467 class UserGroupAppView(BaseAppView):
468 class UserGroupAppView(BaseAppView):
468 def __init__(self, context, request):
469 def __init__(self, context, request):
469 super(UserGroupAppView, self).__init__(context, request)
470 super(UserGroupAppView, self).__init__(context, request)
470 self.db_user_group = request.db_user_group
471 self.db_user_group = request.db_user_group
471 self.db_user_group_name = self.db_user_group.users_group_name
472 self.db_user_group_name = self.db_user_group.users_group_name
472
473
473
474
474 class UserAppView(BaseAppView):
475 class UserAppView(BaseAppView):
475 def __init__(self, context, request):
476 def __init__(self, context, request):
476 super(UserAppView, self).__init__(context, request)
477 super(UserAppView, self).__init__(context, request)
477 self.db_user = request.db_user
478 self.db_user = request.db_user
478 self.db_user_id = self.db_user.user_id
479 self.db_user_id = self.db_user.user_id
479
480
480 _ = self.request.translate
481 _ = self.request.translate
481 if not request.db_user_supports_default:
482 if not request.db_user_supports_default:
482 if self.db_user.username == User.DEFAULT_USER:
483 if self.db_user.username == User.DEFAULT_USER:
483 h.flash(_("Editing user `{}` is disabled.".format(
484 h.flash(_("Editing user `{}` is disabled.".format(
484 User.DEFAULT_USER)), category='warning')
485 User.DEFAULT_USER)), category='warning')
485 raise HTTPFound(h.route_path('users'))
486 raise HTTPFound(h.route_path('users'))
486
487
487
488
488 class DataGridAppView(object):
489 class DataGridAppView(object):
489 """
490 """
490 Common class to have re-usable grid rendering components
491 Common class to have re-usable grid rendering components
491 """
492 """
492
493
493 def _extract_ordering(self, request, column_map=None):
494 def _extract_ordering(self, request, column_map=None):
494 column_map = column_map or {}
495 column_map = column_map or {}
495 column_index = safe_int(request.GET.get('order[0][column]'))
496 column_index = safe_int(request.GET.get('order[0][column]'))
496 order_dir = request.GET.get(
497 order_dir = request.GET.get(
497 'order[0][dir]', 'desc')
498 'order[0][dir]', 'desc')
498 order_by = request.GET.get(
499 order_by = request.GET.get(
499 'columns[%s][data][sort]' % column_index, 'name_raw')
500 'columns[%s][data][sort]' % column_index, 'name_raw')
500
501
501 # translate datatable to DB columns
502 # translate datatable to DB columns
502 order_by = column_map.get(order_by) or order_by
503 order_by = column_map.get(order_by) or order_by
503
504
504 search_q = request.GET.get('search[value]')
505 search_q = request.GET.get('search[value]')
505 return search_q, order_by, order_dir
506 return search_q, order_by, order_dir
506
507
507 def _extract_chunk(self, request):
508 def _extract_chunk(self, request):
508 start = safe_int(request.GET.get('start'), 0)
509 start = safe_int(request.GET.get('start'), 0)
509 length = safe_int(request.GET.get('length'), 25)
510 length = safe_int(request.GET.get('length'), 25)
510 draw = safe_int(request.GET.get('draw'))
511 draw = safe_int(request.GET.get('draw'))
511 return draw, start, length
512 return draw, start, length
512
513
513 def _get_order_col(self, order_by, model):
514 def _get_order_col(self, order_by, model):
514 if isinstance(order_by, compat.string_types):
515 if isinstance(order_by, compat.string_types):
515 try:
516 try:
516 return operator.attrgetter(order_by)(model)
517 return operator.attrgetter(order_by)(model)
517 except AttributeError:
518 except AttributeError:
518 return None
519 return None
519 else:
520 else:
520 return order_by
521 return order_by
521
522
522
523
523 class BaseReferencesView(RepoAppView):
524 class BaseReferencesView(RepoAppView):
524 """
525 """
525 Base for reference view for branches, tags and bookmarks.
526 Base for reference view for branches, tags and bookmarks.
526 """
527 """
527 def load_default_context(self):
528 def load_default_context(self):
528 c = self._get_local_tmpl_context()
529 c = self._get_local_tmpl_context()
529
530
530
531
531 return c
532 return c
532
533
533 def load_refs_context(self, ref_items, partials_template):
534 def load_refs_context(self, ref_items, partials_template):
534 _render = self.request.get_partial_renderer(partials_template)
535 _render = self.request.get_partial_renderer(partials_template)
535 pre_load = ["author", "date", "message", "parents"]
536 pre_load = ["author", "date", "message", "parents"]
536
537
537 is_svn = h.is_svn(self.rhodecode_vcs_repo)
538 is_svn = h.is_svn(self.rhodecode_vcs_repo)
538 is_hg = h.is_hg(self.rhodecode_vcs_repo)
539 is_hg = h.is_hg(self.rhodecode_vcs_repo)
539
540
540 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
541 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
541
542
542 closed_refs = {}
543 closed_refs = {}
543 if is_hg:
544 if is_hg:
544 closed_refs = self.rhodecode_vcs_repo.branches_closed
545 closed_refs = self.rhodecode_vcs_repo.branches_closed
545
546
546 data = []
547 data = []
547 for ref_name, commit_id in ref_items:
548 for ref_name, commit_id in ref_items:
548 commit = self.rhodecode_vcs_repo.get_commit(
549 commit = self.rhodecode_vcs_repo.get_commit(
549 commit_id=commit_id, pre_load=pre_load)
550 commit_id=commit_id, pre_load=pre_load)
550 closed = ref_name in closed_refs
551 closed = ref_name in closed_refs
551
552
552 # TODO: johbo: Unify generation of reference links
553 # TODO: johbo: Unify generation of reference links
553 use_commit_id = '/' in ref_name or is_svn
554 use_commit_id = '/' in ref_name or is_svn
554
555
555 if use_commit_id:
556 if use_commit_id:
556 files_url = h.route_path(
557 files_url = h.route_path(
557 'repo_files',
558 'repo_files',
558 repo_name=self.db_repo_name,
559 repo_name=self.db_repo_name,
559 f_path=ref_name if is_svn else '',
560 f_path=ref_name if is_svn else '',
560 commit_id=commit_id)
561 commit_id=commit_id)
561
562
562 else:
563 else:
563 files_url = h.route_path(
564 files_url = h.route_path(
564 'repo_files',
565 'repo_files',
565 repo_name=self.db_repo_name,
566 repo_name=self.db_repo_name,
566 f_path=ref_name if is_svn else '',
567 f_path=ref_name if is_svn else '',
567 commit_id=ref_name,
568 commit_id=ref_name,
568 _query=dict(at=ref_name))
569 _query=dict(at=ref_name))
569
570
570 data.append({
571 data.append({
571 "name": _render('name', ref_name, files_url, closed),
572 "name": _render('name', ref_name, files_url, closed),
572 "name_raw": ref_name,
573 "name_raw": ref_name,
573 "date": _render('date', commit.date),
574 "date": _render('date', commit.date),
574 "date_raw": datetime_to_time(commit.date),
575 "date_raw": datetime_to_time(commit.date),
575 "author": _render('author', commit.author),
576 "author": _render('author', commit.author),
576 "commit": _render(
577 "commit": _render(
577 'commit', commit.message, commit.raw_id, commit.idx),
578 'commit', commit.message, commit.raw_id, commit.idx),
578 "commit_raw": commit.idx,
579 "commit_raw": commit.idx,
579 "compare": _render(
580 "compare": _render(
580 'compare', format_ref_id(ref_name, commit.raw_id)),
581 'compare', format_ref_id(ref_name, commit.raw_id)),
581 })
582 })
582
583
583 return data
584 return data
584
585
585
586
586 class RepoRoutePredicate(object):
587 class RepoRoutePredicate(object):
587 def __init__(self, val, config):
588 def __init__(self, val, config):
588 self.val = val
589 self.val = val
589
590
590 def text(self):
591 def text(self):
591 return 'repo_route = %s' % self.val
592 return 'repo_route = %s' % self.val
592
593
593 phash = text
594 phash = text
594
595
595 def __call__(self, info, request):
596 def __call__(self, info, request):
596 if hasattr(request, 'vcs_call'):
597 if hasattr(request, 'vcs_call'):
597 # skip vcs calls
598 # skip vcs calls
598 return
599 return
599
600
600 repo_name = info['match']['repo_name']
601 repo_name = info['match']['repo_name']
601 repo_model = repo.RepoModel()
602 repo_model = repo.RepoModel()
602
603
603 by_name_match = repo_model.get_by_repo_name(repo_name, cache=False)
604 by_name_match = repo_model.get_by_repo_name(repo_name, cache=False)
604
605
605 def redirect_if_creating(route_info, db_repo):
606 def redirect_if_creating(route_info, db_repo):
606 skip_views = ['edit_repo_advanced_delete']
607 skip_views = ['edit_repo_advanced_delete']
607 route = route_info['route']
608 route = route_info['route']
608 # we should skip delete view so we can actually "remove" repositories
609 # we should skip delete view so we can actually "remove" repositories
609 # if they get stuck in creating state.
610 # if they get stuck in creating state.
610 if route.name in skip_views:
611 if route.name in skip_views:
611 return
612 return
612
613
613 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
614 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
614 repo_creating_url = request.route_path(
615 repo_creating_url = request.route_path(
615 'repo_creating', repo_name=db_repo.repo_name)
616 'repo_creating', repo_name=db_repo.repo_name)
616 raise HTTPFound(repo_creating_url)
617 raise HTTPFound(repo_creating_url)
617
618
618 if by_name_match:
619 if by_name_match:
619 # register this as request object we can re-use later
620 # register this as request object we can re-use later
620 request.db_repo = by_name_match
621 request.db_repo = by_name_match
621 redirect_if_creating(info, by_name_match)
622 redirect_if_creating(info, by_name_match)
622 return True
623 return True
623
624
624 by_id_match = repo_model.get_repo_by_id(repo_name)
625 by_id_match = repo_model.get_repo_by_id(repo_name)
625 if by_id_match:
626 if by_id_match:
626 request.db_repo = by_id_match
627 request.db_repo = by_id_match
627 redirect_if_creating(info, by_id_match)
628 redirect_if_creating(info, by_id_match)
628 return True
629 return True
629
630
630 return False
631 return False
631
632
632
633
633 class RepoForbidArchivedRoutePredicate(object):
634 class RepoForbidArchivedRoutePredicate(object):
634 def __init__(self, val, config):
635 def __init__(self, val, config):
635 self.val = val
636 self.val = val
636
637
637 def text(self):
638 def text(self):
638 return 'repo_forbid_archived = %s' % self.val
639 return 'repo_forbid_archived = %s' % self.val
639
640
640 phash = text
641 phash = text
641
642
642 def __call__(self, info, request):
643 def __call__(self, info, request):
643 _ = request.translate
644 _ = request.translate
644 rhodecode_db_repo = request.db_repo
645 rhodecode_db_repo = request.db_repo
645
646
646 log.debug(
647 log.debug(
647 '%s checking if archived flag for repo for %s',
648 '%s checking if archived flag for repo for %s',
648 self.__class__.__name__, rhodecode_db_repo.repo_name)
649 self.__class__.__name__, rhodecode_db_repo.repo_name)
649
650
650 if rhodecode_db_repo.archived:
651 if rhodecode_db_repo.archived:
651 log.warning('Current view is not supported for archived repo:%s',
652 log.warning('Current view is not supported for archived repo:%s',
652 rhodecode_db_repo.repo_name)
653 rhodecode_db_repo.repo_name)
653
654
654 h.flash(
655 h.flash(
655 h.literal(_('Action not supported for archived repository.')),
656 h.literal(_('Action not supported for archived repository.')),
656 category='warning')
657 category='warning')
657 summary_url = request.route_path(
658 summary_url = request.route_path(
658 'repo_summary', repo_name=rhodecode_db_repo.repo_name)
659 'repo_summary', repo_name=rhodecode_db_repo.repo_name)
659 raise HTTPFound(summary_url)
660 raise HTTPFound(summary_url)
660 return True
661 return True
661
662
662
663
663 class RepoTypeRoutePredicate(object):
664 class RepoTypeRoutePredicate(object):
664 def __init__(self, val, config):
665 def __init__(self, val, config):
665 self.val = val or ['hg', 'git', 'svn']
666 self.val = val or ['hg', 'git', 'svn']
666
667
667 def text(self):
668 def text(self):
668 return 'repo_accepted_type = %s' % self.val
669 return 'repo_accepted_type = %s' % self.val
669
670
670 phash = text
671 phash = text
671
672
672 def __call__(self, info, request):
673 def __call__(self, info, request):
673 if hasattr(request, 'vcs_call'):
674 if hasattr(request, 'vcs_call'):
674 # skip vcs calls
675 # skip vcs calls
675 return
676 return
676
677
677 rhodecode_db_repo = request.db_repo
678 rhodecode_db_repo = request.db_repo
678
679
679 log.debug(
680 log.debug(
680 '%s checking repo type for %s in %s',
681 '%s checking repo type for %s in %s',
681 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
682 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
682
683
683 if rhodecode_db_repo.repo_type in self.val:
684 if rhodecode_db_repo.repo_type in self.val:
684 return True
685 return True
685 else:
686 else:
686 log.warning('Current view is not supported for repo type:%s',
687 log.warning('Current view is not supported for repo type:%s',
687 rhodecode_db_repo.repo_type)
688 rhodecode_db_repo.repo_type)
688 return False
689 return False
689
690
690
691
691 class RepoGroupRoutePredicate(object):
692 class RepoGroupRoutePredicate(object):
692 def __init__(self, val, config):
693 def __init__(self, val, config):
693 self.val = val
694 self.val = val
694
695
695 def text(self):
696 def text(self):
696 return 'repo_group_route = %s' % self.val
697 return 'repo_group_route = %s' % self.val
697
698
698 phash = text
699 phash = text
699
700
700 def __call__(self, info, request):
701 def __call__(self, info, request):
701 if hasattr(request, 'vcs_call'):
702 if hasattr(request, 'vcs_call'):
702 # skip vcs calls
703 # skip vcs calls
703 return
704 return
704
705
705 repo_group_name = info['match']['repo_group_name']
706 repo_group_name = info['match']['repo_group_name']
706 repo_group_model = repo_group.RepoGroupModel()
707 repo_group_model = repo_group.RepoGroupModel()
707 by_name_match = repo_group_model.get_by_group_name(repo_group_name, cache=False)
708 by_name_match = repo_group_model.get_by_group_name(repo_group_name, cache=False)
708
709
709 if by_name_match:
710 if by_name_match:
710 # register this as request object we can re-use later
711 # register this as request object we can re-use later
711 request.db_repo_group = by_name_match
712 request.db_repo_group = by_name_match
712 return True
713 return True
713
714
714 return False
715 return False
715
716
716
717
717 class UserGroupRoutePredicate(object):
718 class UserGroupRoutePredicate(object):
718 def __init__(self, val, config):
719 def __init__(self, val, config):
719 self.val = val
720 self.val = val
720
721
721 def text(self):
722 def text(self):
722 return 'user_group_route = %s' % self.val
723 return 'user_group_route = %s' % self.val
723
724
724 phash = text
725 phash = text
725
726
726 def __call__(self, info, request):
727 def __call__(self, info, request):
727 if hasattr(request, 'vcs_call'):
728 if hasattr(request, 'vcs_call'):
728 # skip vcs calls
729 # skip vcs calls
729 return
730 return
730
731
731 user_group_id = info['match']['user_group_id']
732 user_group_id = info['match']['user_group_id']
732 user_group_model = user_group.UserGroup()
733 user_group_model = user_group.UserGroup()
733 by_id_match = user_group_model.get(user_group_id, cache=False)
734 by_id_match = user_group_model.get(user_group_id, cache=False)
734
735
735 if by_id_match:
736 if by_id_match:
736 # register this as request object we can re-use later
737 # register this as request object we can re-use later
737 request.db_user_group = by_id_match
738 request.db_user_group = by_id_match
738 return True
739 return True
739
740
740 return False
741 return False
741
742
742
743
743 class UserRoutePredicateBase(object):
744 class UserRoutePredicateBase(object):
744 supports_default = None
745 supports_default = None
745
746
746 def __init__(self, val, config):
747 def __init__(self, val, config):
747 self.val = val
748 self.val = val
748
749
749 def text(self):
750 def text(self):
750 raise NotImplementedError()
751 raise NotImplementedError()
751
752
752 def __call__(self, info, request):
753 def __call__(self, info, request):
753 if hasattr(request, 'vcs_call'):
754 if hasattr(request, 'vcs_call'):
754 # skip vcs calls
755 # skip vcs calls
755 return
756 return
756
757
757 user_id = info['match']['user_id']
758 user_id = info['match']['user_id']
758 user_model = user.User()
759 user_model = user.User()
759 by_id_match = user_model.get(user_id, cache=False)
760 by_id_match = user_model.get(user_id, cache=False)
760
761
761 if by_id_match:
762 if by_id_match:
762 # register this as request object we can re-use later
763 # register this as request object we can re-use later
763 request.db_user = by_id_match
764 request.db_user = by_id_match
764 request.db_user_supports_default = self.supports_default
765 request.db_user_supports_default = self.supports_default
765 return True
766 return True
766
767
767 return False
768 return False
768
769
769
770
770 class UserRoutePredicate(UserRoutePredicateBase):
771 class UserRoutePredicate(UserRoutePredicateBase):
771 supports_default = False
772 supports_default = False
772
773
773 def text(self):
774 def text(self):
774 return 'user_route = %s' % self.val
775 return 'user_route = %s' % self.val
775
776
776 phash = text
777 phash = text
777
778
778
779
779 class UserRouteWithDefaultPredicate(UserRoutePredicateBase):
780 class UserRouteWithDefaultPredicate(UserRoutePredicateBase):
780 supports_default = True
781 supports_default = True
781
782
782 def text(self):
783 def text(self):
783 return 'user_with_default_route = %s' % self.val
784 return 'user_with_default_route = %s' % self.val
784
785
785 phash = text
786 phash = text
786
787
787
788
788 def includeme(config):
789 def includeme(config):
789 config.add_route_predicate(
790 config.add_route_predicate(
790 'repo_route', RepoRoutePredicate)
791 'repo_route', RepoRoutePredicate)
791 config.add_route_predicate(
792 config.add_route_predicate(
792 'repo_accepted_types', RepoTypeRoutePredicate)
793 'repo_accepted_types', RepoTypeRoutePredicate)
793 config.add_route_predicate(
794 config.add_route_predicate(
794 'repo_forbid_when_archived', RepoForbidArchivedRoutePredicate)
795 'repo_forbid_when_archived', RepoForbidArchivedRoutePredicate)
795 config.add_route_predicate(
796 config.add_route_predicate(
796 'repo_group_route', RepoGroupRoutePredicate)
797 'repo_group_route', RepoGroupRoutePredicate)
797 config.add_route_predicate(
798 config.add_route_predicate(
798 'user_group_route', UserGroupRoutePredicate)
799 'user_group_route', UserGroupRoutePredicate)
799 config.add_route_predicate(
800 config.add_route_predicate(
800 'user_route_with_default', UserRouteWithDefaultPredicate)
801 'user_route_with_default', UserRouteWithDefaultPredicate)
801 config.add_route_predicate(
802 config.add_route_predicate(
802 'user_route', UserRoutePredicate)
803 'user_route', UserRoutePredicate)
@@ -1,139 +1,139 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2017-2019 RhodeCode GmbH
3 # Copyright (C) 2017-2019 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.httpexceptions import HTTPFound, HTTPNotFound
23 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
24 from pyramid.view import view_config
24 from pyramid.view import view_config
25 import formencode
25 import formencode
26
26
27 from rhodecode.apps._base import RepoAppView
27 from rhodecode.apps._base import RepoAppView
28 from rhodecode.lib import audit_logger
28 from rhodecode.lib import audit_logger
29 from rhodecode.lib import helpers as h
29 from rhodecode.lib import helpers as h
30 from rhodecode.lib.auth import (
30 from rhodecode.lib.auth import (
31 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
31 LoginRequired, HasRepoPermissionAnyDecorator, CSRFRequired)
32 from rhodecode.model.forms import IssueTrackerPatternsForm
32 from rhodecode.model.forms import IssueTrackerPatternsForm
33 from rhodecode.model.meta import Session
33 from rhodecode.model.meta import Session
34 from rhodecode.model.settings import IssueTrackerSettingsModel, SettingsModel
34 from rhodecode.model.settings import SettingsModel
35
35
36 log = logging.getLogger(__name__)
36 log = logging.getLogger(__name__)
37
37
38
38
39 class RepoSettingsIssueTrackersView(RepoAppView):
39 class RepoSettingsIssueTrackersView(RepoAppView):
40 def load_default_context(self):
40 def load_default_context(self):
41 c = self._get_local_tmpl_context()
41 c = self._get_local_tmpl_context()
42
42
43
43
44 return c
44 return c
45
45
46 @LoginRequired()
46 @LoginRequired()
47 @HasRepoPermissionAnyDecorator('repository.admin')
47 @HasRepoPermissionAnyDecorator('repository.admin')
48 @view_config(
48 @view_config(
49 route_name='edit_repo_issuetracker', request_method='GET',
49 route_name='edit_repo_issuetracker', request_method='GET',
50 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
50 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
51 def repo_issuetracker(self):
51 def repo_issuetracker(self):
52 c = self.load_default_context()
52 c = self.load_default_context()
53 c.active = 'issuetracker'
53 c.active = 'issuetracker'
54 c.data = 'data'
54 c.data = 'data'
55
55
56 c.settings_model = IssueTrackerSettingsModel(repo=self.db_repo)
56 c.settings_model = self.db_repo_patterns
57 c.global_patterns = c.settings_model.get_global_settings()
57 c.global_patterns = c.settings_model.get_global_settings()
58 c.repo_patterns = c.settings_model.get_repo_settings()
58 c.repo_patterns = c.settings_model.get_repo_settings()
59
59
60 return self._get_template_context(c)
60 return self._get_template_context(c)
61
61
62 @LoginRequired()
62 @LoginRequired()
63 @HasRepoPermissionAnyDecorator('repository.admin')
63 @HasRepoPermissionAnyDecorator('repository.admin')
64 @CSRFRequired()
64 @CSRFRequired()
65 @view_config(
65 @view_config(
66 route_name='edit_repo_issuetracker_test', request_method='POST',
66 route_name='edit_repo_issuetracker_test', request_method='POST',
67 renderer='string', xhr=True)
67 renderer='string', xhr=True)
68 def repo_issuetracker_test(self):
68 def repo_issuetracker_test(self):
69 return h.urlify_commit_message(
69 return h.urlify_commit_message(
70 self.request.POST.get('test_text', ''),
70 self.request.POST.get('test_text', ''),
71 self.db_repo_name)
71 self.db_repo_name)
72
72
73 @LoginRequired()
73 @LoginRequired()
74 @HasRepoPermissionAnyDecorator('repository.admin')
74 @HasRepoPermissionAnyDecorator('repository.admin')
75 @CSRFRequired()
75 @CSRFRequired()
76 @view_config(
76 @view_config(
77 route_name='edit_repo_issuetracker_delete', request_method='POST',
77 route_name='edit_repo_issuetracker_delete', request_method='POST',
78 renderer='json_ext', xhr=True)
78 renderer='json_ext', xhr=True)
79 def repo_issuetracker_delete(self):
79 def repo_issuetracker_delete(self):
80 _ = self.request.translate
80 _ = self.request.translate
81 uid = self.request.POST.get('uid')
81 uid = self.request.POST.get('uid')
82 repo_settings = IssueTrackerSettingsModel(repo=self.db_repo_name)
82 repo_settings = self.db_repo_patterns
83 try:
83 try:
84 repo_settings.delete_entries(uid)
84 repo_settings.delete_entries(uid)
85 except Exception:
85 except Exception:
86 h.flash(_('Error occurred during deleting issue tracker entry'),
86 h.flash(_('Error occurred during deleting issue tracker entry'),
87 category='error')
87 category='error')
88 raise HTTPNotFound()
88 raise HTTPNotFound()
89
89
90 SettingsModel().invalidate_settings_cache()
90 SettingsModel().invalidate_settings_cache()
91 h.flash(_('Removed issue tracker entry.'), category='success')
91 h.flash(_('Removed issue tracker entry.'), category='success')
92
92
93 return {'deleted': uid}
93 return {'deleted': uid}
94
94
95 def _update_patterns(self, form, repo_settings):
95 def _update_patterns(self, form, repo_settings):
96 for uid in form['delete_patterns']:
96 for uid in form['delete_patterns']:
97 repo_settings.delete_entries(uid)
97 repo_settings.delete_entries(uid)
98
98
99 for pattern_data in form['patterns']:
99 for pattern_data in form['patterns']:
100 for setting_key, pattern, type_ in pattern_data:
100 for setting_key, pattern, type_ in pattern_data:
101 sett = repo_settings.create_or_update_setting(
101 sett = repo_settings.create_or_update_setting(
102 setting_key, pattern.strip(), type_)
102 setting_key, pattern.strip(), type_)
103 Session().add(sett)
103 Session().add(sett)
104
104
105 Session().commit()
105 Session().commit()
106
106
107 @LoginRequired()
107 @LoginRequired()
108 @HasRepoPermissionAnyDecorator('repository.admin')
108 @HasRepoPermissionAnyDecorator('repository.admin')
109 @CSRFRequired()
109 @CSRFRequired()
110 @view_config(
110 @view_config(
111 route_name='edit_repo_issuetracker_update', request_method='POST',
111 route_name='edit_repo_issuetracker_update', request_method='POST',
112 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
112 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
113 def repo_issuetracker_update(self):
113 def repo_issuetracker_update(self):
114 _ = self.request.translate
114 _ = self.request.translate
115 # Save inheritance
115 # Save inheritance
116 repo_settings = IssueTrackerSettingsModel(repo=self.db_repo_name)
116 repo_settings = self.db_repo_patterns
117 inherited = (
117 inherited = (
118 self.request.POST.get('inherit_global_issuetracker') == "inherited")
118 self.request.POST.get('inherit_global_issuetracker') == "inherited")
119 repo_settings.inherit_global_settings = inherited
119 repo_settings.inherit_global_settings = inherited
120 Session().commit()
120 Session().commit()
121
121
122 try:
122 try:
123 form = IssueTrackerPatternsForm(self.request.translate)().to_python(self.request.POST)
123 form = IssueTrackerPatternsForm(self.request.translate)().to_python(self.request.POST)
124 except formencode.Invalid as errors:
124 except formencode.Invalid as errors:
125 log.exception('Failed to add new pattern')
125 log.exception('Failed to add new pattern')
126 error = errors
126 error = errors
127 h.flash(_('Invalid issue tracker pattern: {}'.format(error)),
127 h.flash(_('Invalid issue tracker pattern: {}'.format(error)),
128 category='error')
128 category='error')
129 raise HTTPFound(
129 raise HTTPFound(
130 h.route_path('edit_repo_issuetracker',
130 h.route_path('edit_repo_issuetracker',
131 repo_name=self.db_repo_name))
131 repo_name=self.db_repo_name))
132
132
133 if form:
133 if form:
134 self._update_patterns(form, repo_settings)
134 self._update_patterns(form, repo_settings)
135
135
136 h.flash(_('Updated issue tracker entries'), category='success')
136 h.flash(_('Updated issue tracker entries'), category='success')
137 raise HTTPFound(
137 raise HTTPFound(
138 h.route_path('edit_repo_issuetracker', repo_name=self.db_repo_name))
138 h.route_path('edit_repo_issuetracker', repo_name=self.db_repo_name))
139
139
General Comments 0
You need to be logged in to leave comments. Login now