nautobot 3.0.0rc2__py3-none-any.whl → 3.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of nautobot might be problematic. Click here for more details.

Files changed (33) hide show
  1. nautobot/core/celery/__init__.py +46 -1
  2. nautobot/core/cli/bootstrap_v3_to_v5.py +125 -44
  3. nautobot/core/jobs/bulk_actions.py +12 -6
  4. nautobot/core/settings.py +13 -0
  5. nautobot/core/settings.yaml +22 -0
  6. nautobot/core/templates/components/panel/body_wrapper_generic_table.html +1 -1
  7. nautobot/core/templates/nautobot_config.py.j2 +14 -1
  8. nautobot/core/templates/redoc_ui.html +3 -0
  9. nautobot/core/testing/views.py +1 -1
  10. nautobot/core/tests/test_jobs.py +118 -0
  11. nautobot/core/tests/test_views.py +24 -0
  12. nautobot/core/ui/bulk_buttons.py +2 -3
  13. nautobot/core/views/generic.py +1 -0
  14. nautobot/core/views/mixins.py +6 -7
  15. nautobot/core/views/renderers.py +1 -0
  16. nautobot/core/views/utils.py +3 -3
  17. nautobot/extras/jobs.py +48 -2
  18. nautobot/extras/models/models.py +19 -0
  19. nautobot/extras/tables.py +9 -6
  20. nautobot/extras/templates/extras/approval_workflow/approve.html +9 -2
  21. nautobot/extras/templates/extras/approval_workflow/deny.html +9 -3
  22. nautobot/extras/tests/test_customfields_filters.py +84 -4
  23. nautobot/extras/tests/test_views.py +40 -4
  24. nautobot/extras/views.py +63 -38
  25. nautobot/project-static/dist/css/nautobot.css +1 -1
  26. nautobot/project-static/dist/css/nautobot.css.map +1 -1
  27. nautobot/ui/src/scss/nautobot.scss +2 -1
  28. {nautobot-3.0.0rc2.dist-info → nautobot-3.0.1.dist-info}/METADATA +1 -1
  29. {nautobot-3.0.0rc2.dist-info → nautobot-3.0.1.dist-info}/RECORD +33 -33
  30. {nautobot-3.0.0rc2.dist-info → nautobot-3.0.1.dist-info}/LICENSE.txt +0 -0
  31. {nautobot-3.0.0rc2.dist-info → nautobot-3.0.1.dist-info}/NOTICE +0 -0
  32. {nautobot-3.0.0rc2.dist-info → nautobot-3.0.1.dist-info}/WHEEL +0 -0
  33. {nautobot-3.0.0rc2.dist-info → nautobot-3.0.1.dist-info}/entry_points.txt +0 -0
nautobot/extras/views.py CHANGED
@@ -420,27 +420,33 @@ class ApprovalWorkflowStageUIViewSet(
420
420
  ],
421
421
  )
422
422
 
423
- @action(detail=True, url_path="approve", methods=["get", "post"])
423
+ @action(
424
+ detail=True,
425
+ url_path="approve",
426
+ methods=["get", "post"],
427
+ custom_view_base_action="change",
428
+ custom_view_additional_permissions=["extras.view_approvalworkflowstage"],
429
+ )
424
430
  def approve(self, request, *args, **kwargs):
425
431
  """
426
432
  Approve the approval workflow stage response.
427
433
  """
428
434
  instance = self.get_object()
429
435
 
430
- try:
431
- approval_workflow_stage_response = ApprovalWorkflowStageResponse.objects.get(
432
- approval_workflow_stage=instance,
433
- user=request.user,
434
- )
435
- except ApprovalWorkflowStageResponse.DoesNotExist:
436
- approval_workflow_stage_response = ApprovalWorkflowStageResponse.objects.create(
437
- approval_workflow_stage=instance,
438
- user=request.user,
439
- )
436
+ if not (
437
+ request.user.is_superuser
438
+ or instance.approval_workflow_stage_definition.approver_group.user_set.filter(id=request.user.id).exists()
439
+ ):
440
+ messages.error(request, "You are not permitted to approve this workflow stage.")
441
+ return redirect(self.get_return_url(request, instance))
440
442
 
441
443
  if request.method == "GET":
442
- obj = approval_workflow_stage_response
443
- form = ApprovalForm(initial={"comments": obj.comments})
444
+ if existing_response := ApprovalWorkflowStageResponse.objects.filter(
445
+ approval_workflow_stage=instance, user=request.user
446
+ ).first():
447
+ form = ApprovalForm(initial={"comments": existing_response.comments})
448
+ else:
449
+ form = ApprovalForm()
444
450
 
445
451
  object_under_review = instance.approval_workflow.object_under_review
446
452
  template_name = getattr(object_under_review, "get_approval_template", lambda: None)()
@@ -451,15 +457,19 @@ class ApprovalWorkflowStageUIViewSet(
451
457
  request,
452
458
  template_name,
453
459
  {
454
- "obj": obj.approval_workflow_stage,
455
- "object_under_review": obj.approval_workflow_stage.approval_workflow.object_under_review,
460
+ "obj": instance,
461
+ "object_under_review": instance.approval_workflow.object_under_review,
456
462
  "form": form,
457
463
  "obj_type": ApprovalWorkflowStage._meta.verbose_name,
458
- "return_url": self.get_return_url(request, obj),
464
+ "return_url": self.get_return_url(request, instance),
459
465
  "card_class": "success",
460
466
  "button_class": "success",
461
467
  },
462
468
  )
469
+
470
+ approval_workflow_stage_response, _ = ApprovalWorkflowStageResponse.objects.get_or_create(
471
+ approval_workflow_stage=instance, user=request.user
472
+ )
463
473
  approval_workflow_stage_response.comments = request.data.get("comments")
464
474
  approval_workflow_stage_response.state = ApprovalWorkflowStateChoices.APPROVED
465
475
  approval_workflow_stage_response.save()
@@ -467,40 +477,49 @@ class ApprovalWorkflowStageUIViewSet(
467
477
  messages.success(request, f"You approved {instance}.")
468
478
  return redirect(self.get_return_url(request))
469
479
 
470
- @action(detail=True, url_path="deny", methods=["get", "post"])
480
+ @action(
481
+ detail=True,
482
+ url_path="deny",
483
+ methods=["get", "post"],
484
+ custom_view_base_action="change",
485
+ custom_view_additional_permissions=["extras.view_approvalworkflowstage"],
486
+ )
471
487
  def deny(self, request, *args, **kwargs):
472
488
  """
473
489
  Deny the approval workflow stage response.
474
490
  """
475
491
  instance = self.get_object()
476
492
 
477
- try:
478
- approval_workflow_stage_response = ApprovalWorkflowStageResponse.objects.get(
479
- approval_workflow_stage=instance,
480
- user=request.user,
481
- )
482
- except ApprovalWorkflowStageResponse.DoesNotExist:
483
- approval_workflow_stage_response = ApprovalWorkflowStageResponse.objects.create(
484
- approval_workflow_stage=instance,
485
- user=request.user,
486
- state=ApprovalWorkflowStateChoices.PENDING,
487
- )
493
+ if not (
494
+ request.user.is_superuser
495
+ or instance.approval_workflow_stage_definition.approver_group.user_set.filter(id=request.user.id).exists()
496
+ ):
497
+ messages.error(request, "You are not permitted to deny this workflow stage.")
498
+ return redirect(self.get_return_url(request, instance))
488
499
 
489
500
  if request.method == "GET":
490
- obj = approval_workflow_stage_response
491
- form = ApprovalForm(initial={"comments": obj.comments})
501
+ if existing_response := ApprovalWorkflowStageResponse.objects.filter(
502
+ approval_workflow_stage=instance, user=request.user
503
+ ).first():
504
+ form = ApprovalForm(initial={"comments": existing_response.comments})
505
+ else:
506
+ form = ApprovalForm()
492
507
 
493
508
  return render(
494
509
  request,
495
510
  "extras/approval_workflow/deny.html",
496
511
  {
497
- "obj": obj.approval_workflow_stage,
498
- "object_under_review": obj.approval_workflow_stage.approval_workflow.object_under_review,
512
+ "obj": instance,
513
+ "object_under_review": instance.approval_workflow.object_under_review,
499
514
  "form": form,
500
515
  "obj_type": ApprovalWorkflowStage._meta.verbose_name,
501
- "return_url": self.get_return_url(request, obj),
516
+ "return_url": self.get_return_url(request, instance),
502
517
  },
503
518
  )
519
+
520
+ approval_workflow_stage_response, _ = ApprovalWorkflowStageResponse.objects.get_or_create(
521
+ approval_workflow_stage=instance, user=request.user
522
+ )
504
523
  approval_workflow_stage_response.comments = request.data.get("comments")
505
524
  approval_workflow_stage_response.state = ApprovalWorkflowStateChoices.DENIED
506
525
  approval_workflow_stage_response.save()
@@ -508,7 +527,13 @@ class ApprovalWorkflowStageUIViewSet(
508
527
  messages.success(request, f"You denied {instance}.")
509
528
  return redirect(self.get_return_url(request))
510
529
 
511
- @action(detail=True, url_path="comment", methods=["get", "post"])
530
+ @action(
531
+ detail=True,
532
+ url_path="comment",
533
+ methods=["get", "post"],
534
+ custom_view_base_action="change",
535
+ custom_view_additional_permissions=["extras.view_approvalworkflowstage"],
536
+ )
512
537
  def comment(self, request, *args, **kwargs):
513
538
  """
514
539
  Comment the approval workflow stage response.
@@ -521,11 +546,12 @@ class ApprovalWorkflowStageUIViewSet(
521
546
  )
522
547
  return redirect(self.get_return_url(request, instance))
523
548
 
549
+ # We don't enforce approver-group/superuser check here, anyone can comment, not just an approver.
550
+
524
551
  if request.method == "GET":
525
- existing_response = ApprovalWorkflowStageResponse.objects.filter(
552
+ if existing_response := ApprovalWorkflowStageResponse.objects.filter(
526
553
  approval_workflow_stage=instance, user=request.user
527
- ).first()
528
- if existing_response is not None:
554
+ ).first():
529
555
  form = ApprovalForm(initial={"comments": existing_response.comments})
530
556
  else:
531
557
  form = ApprovalForm()
@@ -547,7 +573,6 @@ class ApprovalWorkflowStageUIViewSet(
547
573
  approval_workflow_stage_response, _ = ApprovalWorkflowStageResponse.objects.get_or_create(
548
574
  approval_workflow_stage=instance, user=request.user
549
575
  )
550
-
551
576
  approval_workflow_stage_response.comments = request.data.get("comments")
552
577
  # we don't want to change a state if is approved, denied or canceled
553
578
  if approval_workflow_stage_response.state == ApprovalWorkflowStateChoices.PENDING: