##// END OF EJS Templates
integrations: fix bug with integrations view page not loading...
dan -
r935:83975818 default
parent child Browse files
Show More
@@ -1,392 +1,393 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2016 RhodeCode GmbH
3 # Copyright (C) 2012-2016 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.lib import auth
32 from rhodecode.lib import auth
33 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
33 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
34 from rhodecode.lib.utils2 import safe_int
34 from rhodecode.lib.utils2 import safe_int
35 from rhodecode.lib.helpers import Page
35 from rhodecode.lib.helpers import Page
36 from rhodecode.model.db import Repository, RepoGroup, Session, Integration
36 from rhodecode.model.db import Repository, RepoGroup, Session, Integration
37 from rhodecode.model.scm import ScmModel
37 from rhodecode.model.scm import ScmModel
38 from rhodecode.model.integration import IntegrationModel
38 from rhodecode.model.integration import IntegrationModel
39 from rhodecode.admin.navigation import navigation_list
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
82
83 if 'integration' in request.matchdict: # integration type context
83 if 'integration' in request.matchdict: # integration type context
84 integration_type = request.matchdict['integration']
84 integration_type = request.matchdict['integration']
85 self.IntegrationType = integration_type_registry[integration_type]
85 self.IntegrationType = integration_type_registry[integration_type]
86
86
87 if 'integration_id' in request.matchdict: # single integration context
87 if 'integration_id' in request.matchdict: # single integration context
88 integration_id = request.matchdict['integration_id']
88 integration_id = request.matchdict['integration_id']
89 self.integration = Integration.get(integration_id)
89 self.integration = Integration.get(integration_id)
90
90
91 # extra perms check just in case
91 # extra perms check just in case
92 if not self._has_perms_for_integration(self.integration):
92 if not self._has_perms_for_integration(self.integration):
93 raise HTTPForbidden()
93 raise HTTPForbidden()
94
94
95 self.settings = self.integration and self.integration.settings or {}
95 self.settings = self.integration and self.integration.settings or {}
96 self.admin_view = not (self.repo or self.repo_group)
96 self.admin_view = not (self.repo or self.repo_group)
97
97
98 def _has_perms_for_integration(self, integration):
98 def _has_perms_for_integration(self, integration):
99 perms = self.request.user.permissions
99 perms = self.request.user.permissions
100
100
101 if 'hg.admin' in perms['global']:
101 if 'hg.admin' in perms['global']:
102 return True
102 return True
103
103
104 if integration.repo:
104 if integration.repo:
105 return perms['repositories'].get(
105 return perms['repositories'].get(
106 integration.repo.repo_name) == 'repository.admin'
106 integration.repo.repo_name) == 'repository.admin'
107
107
108 if integration.repo_group:
108 if integration.repo_group:
109 return perms['repositories_groups'].get(
109 return perms['repositories_groups'].get(
110 integration.repo_group.group_name) == 'group.admin'
110 integration.repo_group.group_name) == 'group.admin'
111
111
112 return False
112 return False
113
113
114 def _template_c_context(self):
114 def _template_c_context(self):
115 # TODO: dan: this is a stopgap in order to inherit from current pylons
115 # TODO: dan: this is a stopgap in order to inherit from current pylons
116 # based admin/repo settings templates - this should be removed entirely
116 # based admin/repo settings templates - this should be removed entirely
117 # after port to pyramid
117 # after port to pyramid
118
118
119 c = pylons.tmpl_context
119 c = pylons.tmpl_context
120 c.active = 'integrations'
120 c.active = 'integrations'
121 c.rhodecode_user = self.request.user
121 c.rhodecode_user = self.request.user
122 c.repo = self.repo
122 c.repo = self.repo
123 c.repo_group = self.repo_group
123 c.repo_group = self.repo_group
124 c.repo_name = self.repo and self.repo.repo_name or None
124 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
125 c.repo_group_name = self.repo_group and self.repo_group.group_name or None
126
126
127 if self.repo:
127 if self.repo:
128 c.repo_info = self.repo
128 c.repo_info = self.repo
129 c.rhodecode_db_repo = self.repo
129 c.rhodecode_db_repo = self.repo
130 c.repository_pull_requests = ScmModel().get_pull_requests(self.repo)
130 c.repository_pull_requests = ScmModel().get_pull_requests(self.repo)
131 else:
131 else:
132 c.navlist = navigation_list(self.request)
132 c.navlist = navigation_list(self.request)
133
133
134 return c
134 return c
135
135
136 def _form_schema(self):
136 def _form_schema(self):
137 schema = make_integration_schema(IntegrationType=self.IntegrationType,
137 schema = make_integration_schema(IntegrationType=self.IntegrationType,
138 settings=self.settings)
138 settings=self.settings)
139
139
140 # returns a clone, important if mutating the schema later
140 # returns a clone, important if mutating the schema later
141 return schema.bind(
141 return schema.bind(
142 permissions=self.request.user.permissions,
142 permissions=self.request.user.permissions,
143 no_scope=not self.admin_view)
143 no_scope=not self.admin_view)
144
144
145
145
146 def _form_defaults(self):
146 def _form_defaults(self):
147 defaults = {}
147 defaults = {}
148
148
149 if self.integration:
149 if self.integration:
150 defaults['settings'] = self.integration.settings or {}
150 defaults['settings'] = self.integration.settings or {}
151 defaults['options'] = {
151 defaults['options'] = {
152 'name': self.integration.name,
152 'name': self.integration.name,
153 'enabled': self.integration.enabled,
153 'enabled': self.integration.enabled,
154 'scope': {
154 'scope': {
155 'repo': self.integration.repo,
155 'repo': self.integration.repo,
156 'repo_group': self.integration.repo_group,
156 'repo_group': self.integration.repo_group,
157 'child_repos_only': self.integration.child_repos_only,
157 'child_repos_only': self.integration.child_repos_only,
158 },
158 },
159 }
159 }
160 else:
160 else:
161 if self.repo:
161 if self.repo:
162 scope = _('{repo_name} repository').format(
162 scope = _('{repo_name} repository').format(
163 repo_name=self.repo.repo_name)
163 repo_name=self.repo.repo_name)
164 elif self.repo_group:
164 elif self.repo_group:
165 scope = _('{repo_group_name} repo group').format(
165 scope = _('{repo_group_name} repo group').format(
166 repo_group_name=self.repo_group.group_name)
166 repo_group_name=self.repo_group.group_name)
167 else:
167 else:
168 scope = _('Global')
168 scope = _('Global')
169
169
170 defaults['options'] = {
170 defaults['options'] = {
171 'enabled': True,
171 'enabled': True,
172 'name': _('{name} integration').format(
172 'name': _('{name} integration').format(
173 name=self.IntegrationType.display_name),
173 name=self.IntegrationType.display_name),
174 }
174 }
175 defaults['options']['scope'] = {
175 defaults['options']['scope'] = {
176 'repo': self.repo,
176 'repo': self.repo,
177 'repo_group': self.repo_group,
177 'repo_group': self.repo_group,
178 }
178 }
179
179
180 return defaults
180 return defaults
181
181
182 def _delete_integration(self, integration):
182 def _delete_integration(self, integration):
183 Session().delete(self.integration)
183 Session().delete(self.integration)
184 Session().commit()
184 Session().commit()
185 self.request.session.flash(
185 self.request.session.flash(
186 _('Integration {integration_name} deleted successfully.').format(
186 _('Integration {integration_name} deleted successfully.').format(
187 integration_name=self.integration.name),
187 integration_name=self.integration.name),
188 queue='success')
188 queue='success')
189
189
190 if self.repo:
190 if self.repo:
191 redirect_to = self.request.route_url(
191 redirect_to = self.request.route_url(
192 'repo_integrations_home', repo_name=self.repo.repo_name)
192 'repo_integrations_home', repo_name=self.repo.repo_name)
193 elif self.repo_group:
193 elif self.repo_group:
194 redirect_to = self.request.route_url(
194 redirect_to = self.request.route_url(
195 'repo_group_integrations_home',
195 'repo_group_integrations_home',
196 repo_group_name=self.repo_group.group_name)
196 repo_group_name=self.repo_group.group_name)
197 else:
197 else:
198 redirect_to = self.request.route_url('global_integrations_home')
198 redirect_to = self.request.route_url('global_integrations_home')
199 raise HTTPFound(redirect_to)
199 raise HTTPFound(redirect_to)
200
200
201 def settings_get(self, defaults=None, form=None):
201 def settings_get(self, defaults=None, form=None):
202 """
202 """
203 View that displays the integration settings as a form.
203 View that displays the integration settings as a form.
204 """
204 """
205
205
206 defaults = defaults or self._form_defaults()
206 defaults = defaults or self._form_defaults()
207 schema = self._form_schema()
207 schema = self._form_schema()
208
208
209 if self.integration:
209 if self.integration:
210 buttons = ('submit', 'delete')
210 buttons = ('submit', 'delete')
211 else:
211 else:
212 buttons = ('submit',)
212 buttons = ('submit',)
213
213
214 form = form or deform.Form(schema, appstruct=defaults, buttons=buttons)
214 form = form or deform.Form(schema, appstruct=defaults, buttons=buttons)
215
215
216 template_context = {
216 template_context = {
217 'form': form,
217 'form': form,
218 'current_IntegrationType': self.IntegrationType,
218 'current_IntegrationType': self.IntegrationType,
219 'integration': self.integration,
219 'integration': self.integration,
220 'c': self._template_c_context(),
220 'c': self._template_c_context(),
221 }
221 }
222
222
223 return template_context
223 return template_context
224
224
225 @auth.CSRFRequired()
225 @auth.CSRFRequired()
226 def settings_post(self):
226 def settings_post(self):
227 """
227 """
228 View that validates and stores the integration settings.
228 View that validates and stores the integration settings.
229 """
229 """
230 controls = self.request.POST.items()
230 controls = self.request.POST.items()
231 pstruct = peppercorn.parse(controls)
231 pstruct = peppercorn.parse(controls)
232
232
233 if self.integration and pstruct.get('delete'):
233 if self.integration and pstruct.get('delete'):
234 return self._delete_integration(self.integration)
234 return self._delete_integration(self.integration)
235
235
236 schema = self._form_schema()
236 schema = self._form_schema()
237
237
238 skip_settings_validation = False
238 skip_settings_validation = False
239 if self.integration and 'enabled' not in pstruct.get('options', {}):
239 if self.integration and 'enabled' not in pstruct.get('options', {}):
240 skip_settings_validation = True
240 skip_settings_validation = True
241 schema['settings'].validator = None
241 schema['settings'].validator = None
242 for field in schema['settings'].children:
242 for field in schema['settings'].children:
243 field.validator = None
243 field.validator = None
244 field.missing = ''
244 field.missing = ''
245
245
246 if self.integration:
246 if self.integration:
247 buttons = ('submit', 'delete')
247 buttons = ('submit', 'delete')
248 else:
248 else:
249 buttons = ('submit',)
249 buttons = ('submit',)
250
250
251 form = deform.Form(schema, buttons=buttons)
251 form = deform.Form(schema, buttons=buttons)
252
252
253 if not self.admin_view:
253 if not self.admin_view:
254 # scope is read only field in these cases, and has to be added
254 # scope is read only field in these cases, and has to be added
255 options = pstruct.setdefault('options', {})
255 options = pstruct.setdefault('options', {})
256 if 'scope' not in options:
256 if 'scope' not in options:
257 options['scope'] = IntegrationScopeType().serialize(None, {
257 options['scope'] = IntegrationScopeType().serialize(None, {
258 'repo': self.repo,
258 'repo': self.repo,
259 'repo_group': self.repo_group,
259 'repo_group': self.repo_group,
260 })
260 })
261
261
262 try:
262 try:
263 valid_data = form.validate_pstruct(pstruct)
263 valid_data = form.validate_pstruct(pstruct)
264 except deform.ValidationFailure as e:
264 except deform.ValidationFailure as e:
265 self.request.session.flash(
265 self.request.session.flash(
266 _('Errors exist when saving integration settings. '
266 _('Errors exist when saving integration settings. '
267 'Please check the form inputs.'),
267 'Please check the form inputs.'),
268 queue='error')
268 queue='error')
269 return self.settings_get(form=e)
269 return self.settings_get(form=e)
270
270
271 if not self.integration:
271 if not self.integration:
272 self.integration = Integration()
272 self.integration = Integration()
273 self.integration.integration_type = self.IntegrationType.key
273 self.integration.integration_type = self.IntegrationType.key
274 Session().add(self.integration)
274 Session().add(self.integration)
275
275
276 scope = valid_data['options']['scope']
276 scope = valid_data['options']['scope']
277
277
278 IntegrationModel().update_integration(self.integration,
278 IntegrationModel().update_integration(self.integration,
279 name=valid_data['options']['name'],
279 name=valid_data['options']['name'],
280 enabled=valid_data['options']['enabled'],
280 enabled=valid_data['options']['enabled'],
281 settings=valid_data['settings'],
281 settings=valid_data['settings'],
282 repo=scope['repo'],
282 repo=scope['repo'],
283 repo_group=scope['repo_group'],
283 repo_group=scope['repo_group'],
284 child_repos_only=scope['child_repos_only'],
284 child_repos_only=scope['child_repos_only'],
285 )
285 )
286
286
287
287
288 self.integration.settings = valid_data['settings']
288 self.integration.settings = valid_data['settings']
289 Session().commit()
289 Session().commit()
290 # Display success message and redirect.
290 # Display success message and redirect.
291 self.request.session.flash(
291 self.request.session.flash(
292 _('Integration {integration_name} updated successfully.').format(
292 _('Integration {integration_name} updated successfully.').format(
293 integration_name=self.IntegrationType.display_name),
293 integration_name=self.IntegrationType.display_name),
294 queue='success')
294 queue='success')
295
295
296
296
297 # if integration scope changes, we must redirect to the right place
297 # if integration scope changes, we must redirect to the right place
298 # keeping in mind if the original view was for /repo/ or /_admin/
298 # keeping in mind if the original view was for /repo/ or /_admin/
299 admin_view = not (self.repo or self.repo_group)
299 admin_view = not (self.repo or self.repo_group)
300
300
301 if self.integration.repo and not admin_view:
301 if self.integration.repo and not admin_view:
302 redirect_to = self.request.route_path(
302 redirect_to = self.request.route_path(
303 'repo_integrations_edit',
303 'repo_integrations_edit',
304 repo_name=self.integration.repo.repo_name,
304 repo_name=self.integration.repo.repo_name,
305 integration=self.integration.integration_type,
305 integration=self.integration.integration_type,
306 integration_id=self.integration.integration_id)
306 integration_id=self.integration.integration_id)
307 elif self.integration.repo_group and not admin_view:
307 elif self.integration.repo_group and not admin_view:
308 redirect_to = self.request.route_path(
308 redirect_to = self.request.route_path(
309 'repo_group_integrations_edit',
309 'repo_group_integrations_edit',
310 repo_group_name=self.integration.repo_group.group_name,
310 repo_group_name=self.integration.repo_group.group_name,
311 integration=self.integration.integration_type,
311 integration=self.integration.integration_type,
312 integration_id=self.integration.integration_id)
312 integration_id=self.integration.integration_id)
313 else:
313 else:
314 redirect_to = self.request.route_path(
314 redirect_to = self.request.route_path(
315 'global_integrations_edit',
315 'global_integrations_edit',
316 integration=self.integration.integration_type,
316 integration=self.integration.integration_type,
317 integration_id=self.integration.integration_id)
317 integration_id=self.integration.integration_id)
318
318
319 return HTTPFound(redirect_to)
319 return HTTPFound(redirect_to)
320
320
321 def index(self):
321 def index(self):
322 """ List integrations """
322 """ List integrations """
323 if self.repo:
323 if self.repo:
324 scope = self.repo
324 scope = self.repo
325 elif self.repo_group:
325 elif self.repo_group:
326 scope = self.repo_group
326 scope = self.repo_group
327 else:
327 else:
328 scope = 'all'
328 scope = 'all'
329
329
330 integrations = []
330 integrations = []
331
331
332 for integration in IntegrationModel().get_integrations(
332 for IntType, integration in IntegrationModel().get_integrations(
333 scope=scope, IntegrationType=self.IntegrationType):
333 scope=scope, IntegrationType=self.IntegrationType):
334
334
335 # extra permissions check *just in case*
335 # extra permissions check *just in case*
336 if not self._has_perms_for_integration(integration):
336 if not self._has_perms_for_integration(integration):
337 continue
337 continue
338 integrations.append(integration)
338
339 integrations.append((IntType, integration))
339
340
340 sort_arg = self.request.GET.get('sort', 'name:asc')
341 sort_arg = self.request.GET.get('sort', 'name:asc')
341 if ':' in sort_arg:
342 if ':' in sort_arg:
342 sort_field, sort_dir = sort_arg.split(':')
343 sort_field, sort_dir = sort_arg.split(':')
343 else:
344 else:
344 sort_field = sort_arg, 'asc'
345 sort_field = sort_arg, 'asc'
345
346
346 assert sort_field in ('name', 'integration_type', 'enabled', 'scope')
347 assert sort_field in ('name', 'integration_type', 'enabled', 'scope')
347
348
348 integrations.sort(
349 integrations.sort(
349 key=lambda x: getattr(x[1], sort_field), reverse=(sort_dir=='desc'))
350 key=lambda x: getattr(x[1], sort_field), reverse=(sort_dir=='desc'))
350
351
351
352
352 page_url = webhelpers.paginate.PageURL(
353 page_url = webhelpers.paginate.PageURL(
353 self.request.path, self.request.GET)
354 self.request.path, self.request.GET)
354 page = safe_int(self.request.GET.get('page', 1), 1)
355 page = safe_int(self.request.GET.get('page', 1), 1)
355
356
356 integrations = Page(integrations, page=page, items_per_page=10,
357 integrations = Page(integrations, page=page, items_per_page=10,
357 url=page_url)
358 url=page_url)
358
359
359 template_context = {
360 template_context = {
360 'sort_field': sort_field,
361 'sort_field': sort_field,
361 'rev_sort_dir': sort_dir != 'desc' and 'desc' or 'asc',
362 'rev_sort_dir': sort_dir != 'desc' and 'desc' or 'asc',
362 'current_IntegrationType': self.IntegrationType,
363 'current_IntegrationType': self.IntegrationType,
363 'integrations_list': integrations,
364 'integrations_list': integrations,
364 'available_integrations': integration_type_registry,
365 'available_integrations': integration_type_registry,
365 'c': self._template_c_context(),
366 'c': self._template_c_context(),
366 'request': self.request,
367 'request': self.request,
367 }
368 }
368 return template_context
369 return template_context
369
370
370 def new_integration(self):
371 def new_integration(self):
371 template_context = {
372 template_context = {
372 'available_integrations': integration_type_registry,
373 'available_integrations': integration_type_registry,
373 'c': self._template_c_context(),
374 'c': self._template_c_context(),
374 }
375 }
375 return template_context
376 return template_context
376
377
377 class GlobalIntegrationsView(IntegrationSettingsViewBase):
378 class GlobalIntegrationsView(IntegrationSettingsViewBase):
378 def perm_check(self, user):
379 def perm_check(self, user):
379 return auth.HasPermissionAll('hg.admin').check_permissions(user=user)
380 return auth.HasPermissionAll('hg.admin').check_permissions(user=user)
380
381
381
382
382 class RepoIntegrationsView(IntegrationSettingsViewBase):
383 class RepoIntegrationsView(IntegrationSettingsViewBase):
383 def perm_check(self, user):
384 def perm_check(self, user):
384 return auth.HasRepoPermissionAll('repository.admin'
385 return auth.HasRepoPermissionAll('repository.admin'
385 )(repo_name=self.repo.repo_name, user=user)
386 )(repo_name=self.repo.repo_name, user=user)
386
387
387
388
388 class RepoGroupIntegrationsView(IntegrationSettingsViewBase):
389 class RepoGroupIntegrationsView(IntegrationSettingsViewBase):
389 def perm_check(self, user):
390 def perm_check(self, user):
390 return auth.HasRepoGroupPermissionAll('group.admin'
391 return auth.HasRepoGroupPermissionAll('group.admin'
391 )(group_name=self.repo_group.group_name, user=user)
392 )(group_name=self.repo_group.group_name, user=user)
392
393
General Comments 0
You need to be logged in to leave comments. Login now