##// END OF EJS Templates
integrations: fixed view for integrations on repo.
marcink -
r3739:a3405fba new-ui
parent child Browse files
Show More
@@ -1,463 +1,464 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2012-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import deform
22 22 import logging
23 23 import peppercorn
24 24 import webhelpers.paginate
25 25
26 26 from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPNotFound
27 27
28 28 from rhodecode.integrations import integration_type_registry
29 29 from rhodecode.apps._base import BaseAppView
30 30 from rhodecode.apps._base.navigation import navigation_list
31 31 from rhodecode.lib.auth import (
32 32 LoginRequired, CSRFRequired, HasPermissionAnyDecorator,
33 33 HasRepoPermissionAnyDecorator, HasRepoGroupPermissionAnyDecorator)
34 34 from rhodecode.lib.utils2 import safe_int
35 35 from rhodecode.lib import helpers as h
36 36 from rhodecode.model.db import Repository, RepoGroup, Session, Integration
37 37 from rhodecode.model.scm import ScmModel
38 38 from rhodecode.model.integration import IntegrationModel
39 39 from rhodecode.model.validation_schema.schemas.integration_schema import (
40 40 make_integration_schema, IntegrationScopeType)
41 41
42 42 log = logging.getLogger(__name__)
43 43
44 44
45 45 class IntegrationSettingsViewBase(BaseAppView):
46 46 """
47 47 Base Integration settings view used by both repo / global settings
48 48 """
49 49
50 50 def __init__(self, context, request):
51 51 super(IntegrationSettingsViewBase, self).__init__(context, request)
52 52 self._load_view_context()
53 53
54 54 def _load_view_context(self):
55 55 """
56 56 This avoids boilerplate for repo/global+list/edit+views/templates
57 57 by doing all possible contexts at the same time however it should
58 58 be split up into separate functions once more "contexts" exist
59 59 """
60 60
61 61 self.IntegrationType = None
62 62 self.repo = None
63 63 self.repo_group = None
64 64 self.integration = None
65 65 self.integrations = {}
66 66
67 67 request = self.request
68 68
69 69 if 'repo_name' in request.matchdict: # in repo settings context
70 70 repo_name = request.matchdict['repo_name']
71 71 self.repo = Repository.get_by_repo_name(repo_name)
72 72
73 73 if 'repo_group_name' in request.matchdict: # in group settings context
74 74 repo_group_name = request.matchdict['repo_group_name']
75 75 self.repo_group = RepoGroup.get_by_group_name(repo_group_name)
76 76
77 77 if 'integration' in request.matchdict: # integration type context
78 78 integration_type = request.matchdict['integration']
79 79 if integration_type not in integration_type_registry:
80 80 raise HTTPNotFound()
81 81
82 82 self.IntegrationType = integration_type_registry[integration_type]
83 83 if self.IntegrationType.is_dummy:
84 84 raise HTTPNotFound()
85 85
86 86 if 'integration_id' in request.matchdict: # single integration context
87 87 integration_id = request.matchdict['integration_id']
88 88 self.integration = Integration.get(integration_id)
89 89
90 90 # extra perms check just in case
91 91 if not self._has_perms_for_integration(self.integration):
92 92 raise HTTPForbidden()
93 93
94 94 self.settings = self.integration and self.integration.settings or {}
95 95 self.admin_view = not (self.repo or self.repo_group)
96 96
97 97 def _has_perms_for_integration(self, integration):
98 98 perms = self.request.user.permissions
99 99
100 100 if 'hg.admin' in perms['global']:
101 101 return True
102 102
103 103 if integration.repo:
104 104 return perms['repositories'].get(
105 105 integration.repo.repo_name) == 'repository.admin'
106 106
107 107 if integration.repo_group:
108 108 return perms['repositories_groups'].get(
109 109 integration.repo_group.group_name) == 'group.admin'
110 110
111 111 return False
112 112
113 113 def _get_local_tmpl_context(self, include_app_defaults=True):
114 114 _ = self.request.translate
115 115 c = super(IntegrationSettingsViewBase, self)._get_local_tmpl_context(
116 116 include_app_defaults=include_app_defaults)
117 117 c.active = 'integrations'
118 118
119 119 return c
120 120
121 121 def _form_schema(self):
122 122 schema = make_integration_schema(IntegrationType=self.IntegrationType,
123 123 settings=self.settings)
124 124
125 125 # returns a clone, important if mutating the schema later
126 126 return schema.bind(
127 127 permissions=self.request.user.permissions,
128 128 no_scope=not self.admin_view)
129 129
130 130 def _form_defaults(self):
131 131 _ = self.request.translate
132 132 defaults = {}
133 133
134 134 if self.integration:
135 135 defaults['settings'] = self.integration.settings or {}
136 136 defaults['options'] = {
137 137 'name': self.integration.name,
138 138 'enabled': self.integration.enabled,
139 139 'scope': {
140 140 'repo': self.integration.repo,
141 141 'repo_group': self.integration.repo_group,
142 142 'child_repos_only': self.integration.child_repos_only,
143 143 },
144 144 }
145 145 else:
146 146 if self.repo:
147 147 scope = _('{repo_name} repository').format(
148 148 repo_name=self.repo.repo_name)
149 149 elif self.repo_group:
150 150 scope = _('{repo_group_name} repo group').format(
151 151 repo_group_name=self.repo_group.group_name)
152 152 else:
153 153 scope = _('Global')
154 154
155 155 defaults['options'] = {
156 156 'enabled': True,
157 157 'name': _('{name} integration').format(
158 158 name=self.IntegrationType.display_name),
159 159 }
160 160 defaults['options']['scope'] = {
161 161 'repo': self.repo,
162 162 'repo_group': self.repo_group,
163 163 }
164 164
165 165 return defaults
166 166
167 167 def _delete_integration(self, integration):
168 168 _ = self.request.translate
169 169 Session().delete(integration)
170 170 Session().commit()
171 171 h.flash(
172 172 _('Integration {integration_name} deleted successfully.').format(
173 173 integration_name=integration.name),
174 174 category='success')
175 175
176 176 if self.repo:
177 177 redirect_to = self.request.route_path(
178 178 'repo_integrations_home', repo_name=self.repo.repo_name)
179 179 elif self.repo_group:
180 180 redirect_to = self.request.route_path(
181 181 'repo_group_integrations_home',
182 182 repo_group_name=self.repo_group.group_name)
183 183 else:
184 184 redirect_to = self.request.route_path('global_integrations_home')
185 185 raise HTTPFound(redirect_to)
186 186
187 187 def _integration_list(self):
188 188 """ List integrations """
189 189
190 190 c = self.load_default_context()
191 191 if self.repo:
192 192 scope = self.repo
193 193 elif self.repo_group:
194 194 scope = self.repo_group
195 195 else:
196 196 scope = 'all'
197 197
198 198 integrations = []
199 199
200 200 for IntType, integration in IntegrationModel().get_integrations(
201 201 scope=scope, IntegrationType=self.IntegrationType):
202 202
203 203 # extra permissions check *just in case*
204 204 if not self._has_perms_for_integration(integration):
205 205 continue
206 206
207 207 integrations.append((IntType, integration))
208 208
209 209 sort_arg = self.request.GET.get('sort', 'name:asc')
210 210 sort_dir = 'asc'
211 211 if ':' in sort_arg:
212 212 sort_field, sort_dir = sort_arg.split(':')
213 213 else:
214 214 sort_field = sort_arg, 'asc'
215 215
216 216 assert sort_field in ('name', 'integration_type', 'enabled', 'scope')
217 217
218 218 integrations.sort(
219 219 key=lambda x: getattr(x[1], sort_field),
220 220 reverse=(sort_dir == 'desc'))
221 221
222 222 page_url = webhelpers.paginate.PageURL(
223 223 self.request.path, self.request.GET)
224 224 page = safe_int(self.request.GET.get('page', 1), 1)
225 225
226 226 integrations = h.Page(
227 227 integrations, page=page, items_per_page=10, url=page_url)
228 228
229 229 c.rev_sort_dir = sort_dir != 'desc' and 'desc' or 'asc'
230 230
231 231 c.current_IntegrationType = self.IntegrationType
232 232 c.integrations_list = integrations
233 233 c.available_integrations = integration_type_registry
234 234
235 235 return self._get_template_context(c)
236 236
237 237 def _settings_get(self, defaults=None, form=None):
238 238 """
239 239 View that displays the integration settings as a form.
240 240 """
241 241 c = self.load_default_context()
242 242
243 243 defaults = defaults or self._form_defaults()
244 244 schema = self._form_schema()
245 245
246 246 if self.integration:
247 247 buttons = ('submit', 'delete')
248 248 else:
249 249 buttons = ('submit',)
250 250
251 251 form = form or deform.Form(schema, appstruct=defaults, buttons=buttons)
252 252
253 253 c.form = form
254 254 c.current_IntegrationType = self.IntegrationType
255 255 c.integration = self.integration
256 256
257 257 return self._get_template_context(c)
258 258
259 259 def _settings_post(self):
260 260 """
261 261 View that validates and stores the integration settings.
262 262 """
263 263 _ = self.request.translate
264 264
265 265 controls = self.request.POST.items()
266 266 pstruct = peppercorn.parse(controls)
267 267
268 268 if self.integration and pstruct.get('delete'):
269 269 return self._delete_integration(self.integration)
270 270
271 271 schema = self._form_schema()
272 272
273 273 skip_settings_validation = False
274 274 if self.integration and 'enabled' not in pstruct.get('options', {}):
275 275 skip_settings_validation = True
276 276 schema['settings'].validator = None
277 277 for field in schema['settings'].children:
278 278 field.validator = None
279 279 field.missing = ''
280 280
281 281 if self.integration:
282 282 buttons = ('submit', 'delete')
283 283 else:
284 284 buttons = ('submit',)
285 285
286 286 form = deform.Form(schema, buttons=buttons)
287 287
288 288 if not self.admin_view:
289 289 # scope is read only field in these cases, and has to be added
290 290 options = pstruct.setdefault('options', {})
291 291 if 'scope' not in options:
292 292 options['scope'] = IntegrationScopeType().serialize(None, {
293 293 'repo': self.repo,
294 294 'repo_group': self.repo_group,
295 295 })
296 296
297 297 try:
298 298 valid_data = form.validate_pstruct(pstruct)
299 299 except deform.ValidationFailure as e:
300 300 h.flash(
301 301 _('Errors exist when saving integration settings. '
302 302 'Please check the form inputs.'),
303 303 category='error')
304 304 return self._settings_get(form=e)
305 305
306 306 if not self.integration:
307 307 self.integration = Integration()
308 308 self.integration.integration_type = self.IntegrationType.key
309 309 Session().add(self.integration)
310 310
311 311 scope = valid_data['options']['scope']
312 312
313 313 IntegrationModel().update_integration(self.integration,
314 314 name=valid_data['options']['name'],
315 315 enabled=valid_data['options']['enabled'],
316 316 settings=valid_data['settings'],
317 317 repo=scope['repo'],
318 318 repo_group=scope['repo_group'],
319 319 child_repos_only=scope['child_repos_only'],
320 320 )
321 321
322 322 self.integration.settings = valid_data['settings']
323 323 Session().commit()
324 324 # Display success message and redirect.
325 325 h.flash(
326 326 _('Integration {integration_name} updated successfully.').format(
327 327 integration_name=self.IntegrationType.display_name),
328 328 category='success')
329 329
330 330 # if integration scope changes, we must redirect to the right place
331 331 # keeping in mind if the original view was for /repo/ or /_admin/
332 332 admin_view = not (self.repo or self.repo_group)
333 333
334 334 if self.integration.repo and not admin_view:
335 335 redirect_to = self.request.route_path(
336 336 'repo_integrations_edit',
337 337 repo_name=self.integration.repo.repo_name,
338 338 integration=self.integration.integration_type,
339 339 integration_id=self.integration.integration_id)
340 340 elif self.integration.repo_group and not admin_view:
341 341 redirect_to = self.request.route_path(
342 342 'repo_group_integrations_edit',
343 343 repo_group_name=self.integration.repo_group.group_name,
344 344 integration=self.integration.integration_type,
345 345 integration_id=self.integration.integration_id)
346 346 else:
347 347 redirect_to = self.request.route_path(
348 348 'global_integrations_edit',
349 349 integration=self.integration.integration_type,
350 350 integration_id=self.integration.integration_id)
351 351
352 352 return HTTPFound(redirect_to)
353 353
354 354 def _new_integration(self):
355 355 c = self.load_default_context()
356 356 c.available_integrations = integration_type_registry
357 357 return self._get_template_context(c)
358 358
359 359 def load_default_context(self):
360 360 raise NotImplementedError()
361 361
362 362
363 363 class GlobalIntegrationsView(IntegrationSettingsViewBase):
364 364 def load_default_context(self):
365 365 c = self._get_local_tmpl_context()
366 366 c.repo = self.repo
367 367 c.repo_group = self.repo_group
368 368 c.navlist = navigation_list(self.request)
369 369
370 370 return c
371 371
372 372 @LoginRequired()
373 373 @HasPermissionAnyDecorator('hg.admin')
374 374 def integration_list(self):
375 375 return self._integration_list()
376 376
377 377 @LoginRequired()
378 378 @HasPermissionAnyDecorator('hg.admin')
379 379 def settings_get(self):
380 380 return self._settings_get()
381 381
382 382 @LoginRequired()
383 383 @HasPermissionAnyDecorator('hg.admin')
384 384 @CSRFRequired()
385 385 def settings_post(self):
386 386 return self._settings_post()
387 387
388 388 @LoginRequired()
389 389 @HasPermissionAnyDecorator('hg.admin')
390 390 def new_integration(self):
391 391 return self._new_integration()
392 392
393 393
394 394 class RepoIntegrationsView(IntegrationSettingsViewBase):
395 395 def load_default_context(self):
396 396 c = self._get_local_tmpl_context()
397 397
398 398 c.repo = self.repo
399 399 c.repo_group = self.repo_group
400 400
401 401 self.db_repo = self.repo
402 402 c.rhodecode_db_repo = self.repo
403 403 c.repo_name = self.db_repo.repo_name
404 404 c.repository_pull_requests = ScmModel().get_pull_requests(self.repo)
405
405 c.repository_is_user_following = ScmModel().is_following_repo(
406 c.repo_name, self._rhodecode_user.user_id)
406 407 c.has_origin_repo_read_perm = False
407 408 if self.db_repo.fork:
408 409 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
409 410 'repository.write', 'repository.read', 'repository.admin')(
410 411 self.db_repo.fork.repo_name, 'summary fork link')
411 412 return c
412 413
413 414 @LoginRequired()
414 415 @HasRepoPermissionAnyDecorator('repository.admin')
415 416 def integration_list(self):
416 417 return self._integration_list()
417 418
418 419 @LoginRequired()
419 420 @HasRepoPermissionAnyDecorator('repository.admin')
420 421 def settings_get(self):
421 422 return self._settings_get()
422 423
423 424 @LoginRequired()
424 425 @HasRepoPermissionAnyDecorator('repository.admin')
425 426 @CSRFRequired()
426 427 def settings_post(self):
427 428 return self._settings_post()
428 429
429 430 @LoginRequired()
430 431 @HasRepoPermissionAnyDecorator('repository.admin')
431 432 def new_integration(self):
432 433 return self._new_integration()
433 434
434 435
435 436 class RepoGroupIntegrationsView(IntegrationSettingsViewBase):
436 437 def load_default_context(self):
437 438 c = self._get_local_tmpl_context()
438 439 c.repo = self.repo
439 440 c.repo_group = self.repo_group
440 441 c.navlist = navigation_list(self.request)
441 442
442 443 return c
443 444
444 445 @LoginRequired()
445 446 @HasRepoGroupPermissionAnyDecorator('group.admin')
446 447 def integration_list(self):
447 448 return self._integration_list()
448 449
449 450 @LoginRequired()
450 451 @HasRepoGroupPermissionAnyDecorator('group.admin')
451 452 def settings_get(self):
452 453 return self._settings_get()
453 454
454 455 @LoginRequired()
455 456 @HasRepoGroupPermissionAnyDecorator('group.admin')
456 457 @CSRFRequired()
457 458 def settings_post(self):
458 459 return self._settings_post()
459 460
460 461 @LoginRequired()
461 462 @HasRepoGroupPermissionAnyDecorator('group.admin')
462 463 def new_integration(self):
463 464 return self._new_integration()
General Comments 0
You need to be logged in to leave comments. Login now