nautobot 3.0.0rc1__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 (103) hide show
  1. nautobot/apps/forms.py +8 -0
  2. nautobot/apps/templatetags.py +231 -0
  3. nautobot/apps/testing.py +11 -1
  4. nautobot/apps/ui.py +21 -1
  5. nautobot/apps/utils.py +26 -1
  6. nautobot/core/celery/__init__.py +46 -1
  7. nautobot/core/cli/bootstrap_v3_to_v5.py +185 -44
  8. nautobot/core/cli/bootstrap_v3_to_v5_changes.yaml +314 -0
  9. nautobot/core/graphql/generators.py +2 -2
  10. nautobot/core/jobs/bulk_actions.py +12 -6
  11. nautobot/core/jobs/cleanup.py +13 -1
  12. nautobot/core/settings.py +13 -0
  13. nautobot/core/settings.yaml +22 -0
  14. nautobot/core/settings_funcs.py +11 -1
  15. nautobot/core/tables.py +19 -1
  16. nautobot/core/templates/components/panel/body_wrapper_generic_table.html +1 -1
  17. nautobot/core/templates/components/panel/header_extra_content_table.html +9 -3
  18. nautobot/core/templates/generic/object_create.html +1 -1
  19. nautobot/core/templates/inc/header.html +9 -10
  20. nautobot/core/templates/login.html +16 -1
  21. nautobot/core/templates/nautobot_config.py.j2 +14 -1
  22. nautobot/core/templates/redoc_ui.html +3 -0
  23. nautobot/core/templatetags/helpers.py +3 -3
  24. nautobot/core/testing/views.py +3 -1
  25. nautobot/core/tests/test_graphql.py +13 -0
  26. nautobot/core/tests/test_jobs.py +118 -0
  27. nautobot/core/tests/test_views.py +24 -0
  28. nautobot/core/ui/bulk_buttons.py +2 -3
  29. nautobot/core/utils/lookup.py +2 -3
  30. nautobot/core/utils/permissions.py +1 -1
  31. nautobot/core/views/generic.py +1 -0
  32. nautobot/core/views/mixins.py +37 -10
  33. nautobot/core/views/renderers.py +1 -0
  34. nautobot/core/views/utils.py +3 -3
  35. nautobot/data_validation/views.py +1 -9
  36. nautobot/dcim/forms.py +9 -9
  37. nautobot/dcim/models/devices.py +3 -3
  38. nautobot/dcim/tables/power.py +3 -0
  39. nautobot/dcim/templates/dcim/controllermanageddevicegroup_create.html +1 -1
  40. nautobot/dcim/views.py +30 -44
  41. nautobot/extras/api/views.py +14 -3
  42. nautobot/extras/choices.py +3 -0
  43. nautobot/extras/jobs.py +48 -2
  44. nautobot/extras/migrations/0132_approval_workflow_seed_data.py +127 -0
  45. nautobot/extras/models/approvals.py +11 -1
  46. nautobot/extras/models/models.py +19 -0
  47. nautobot/extras/models/relationships.py +3 -1
  48. nautobot/extras/tables.py +35 -18
  49. nautobot/extras/templates/extras/approval_workflow/approve.html +9 -2
  50. nautobot/extras/templates/extras/approval_workflow/deny.html +9 -3
  51. nautobot/extras/templates/extras/approvalworkflowdefinition_update.html +1 -1
  52. nautobot/extras/templates/extras/customfield_update.html +1 -1
  53. nautobot/extras/templates/extras/dynamicgroup_update.html +2 -2
  54. nautobot/extras/templates/extras/inc/approval_buttons_column.html +10 -2
  55. nautobot/extras/templates/extras/inc/job_tiles.html +2 -2
  56. nautobot/extras/templates/extras/inc/jobresult.html +1 -1
  57. nautobot/extras/templates/extras/metadatatype_create.html +1 -1
  58. nautobot/extras/templates/extras/object_approvalworkflow.html +2 -3
  59. nautobot/extras/templates/extras/secretsgroup_update.html +1 -1
  60. nautobot/extras/tests/test_api.py +57 -3
  61. nautobot/extras/tests/test_customfields_filters.py +84 -4
  62. nautobot/extras/tests/test_views.py +323 -6
  63. nautobot/extras/views.py +114 -39
  64. nautobot/ipam/constants.py +2 -2
  65. nautobot/ipam/tables.py +7 -6
  66. nautobot/load_balancers/constants.py +6 -0
  67. nautobot/load_balancers/migrations/0001_initial.py +14 -3
  68. nautobot/load_balancers/models.py +5 -4
  69. nautobot/load_balancers/tables.py +5 -0
  70. nautobot/project-static/dist/css/nautobot.css +1 -1
  71. nautobot/project-static/dist/css/nautobot.css.map +1 -1
  72. nautobot/project-static/dist/js/graphql-libraries.js +1 -1
  73. nautobot/project-static/dist/js/graphql-libraries.js.map +1 -1
  74. nautobot/project-static/dist/js/libraries.js +1 -1
  75. nautobot/project-static/dist/js/libraries.js.LICENSE.txt +38 -2
  76. nautobot/project-static/dist/js/libraries.js.map +1 -1
  77. nautobot/project-static/dist/js/nautobot-graphiql.js +1 -1
  78. nautobot/project-static/dist/js/nautobot-graphiql.js.map +1 -1
  79. nautobot/project-static/dist/js/nautobot.js +1 -1
  80. nautobot/project-static/dist/js/nautobot.js.map +1 -1
  81. nautobot/project-static/img/dark-theme.png +0 -0
  82. nautobot/project-static/img/light-theme.png +0 -0
  83. nautobot/project-static/img/system-theme.png +0 -0
  84. nautobot/project-static/js/forms.js +1 -85
  85. nautobot/tenancy/tables.py +3 -2
  86. nautobot/tenancy/views.py +3 -2
  87. nautobot/ui/package-lock.json +553 -569
  88. nautobot/ui/package.json +10 -10
  89. nautobot/ui/src/js/checkbox.js +132 -0
  90. nautobot/ui/src/js/nautobot.js +6 -0
  91. nautobot/ui/src/js/select2.js +69 -73
  92. nautobot/ui/src/js/theme.js +129 -39
  93. nautobot/ui/src/scss/nautobot.scss +11 -1
  94. nautobot/vpn/templates/vpn/vpnprofile_create.html +2 -2
  95. nautobot/wireless/filters.py +15 -1
  96. nautobot/wireless/tables.py +18 -14
  97. nautobot/wireless/templates/wireless/wirelessnetwork_create.html +1 -1
  98. {nautobot-3.0.0rc1.dist-info → nautobot-3.0.1.dist-info}/METADATA +2 -2
  99. {nautobot-3.0.0rc1.dist-info → nautobot-3.0.1.dist-info}/RECORD +103 -98
  100. {nautobot-3.0.0rc1.dist-info → nautobot-3.0.1.dist-info}/LICENSE.txt +0 -0
  101. {nautobot-3.0.0rc1.dist-info → nautobot-3.0.1.dist-info}/NOTICE +0 -0
  102. {nautobot-3.0.0rc1.dist-info → nautobot-3.0.1.dist-info}/WHEEL +0 -0
  103. {nautobot-3.0.0rc1.dist-info → nautobot-3.0.1.dist-info}/entry_points.txt +0 -0
nautobot/extras/views.py CHANGED
@@ -325,7 +325,7 @@ class ApprovalWorkflowUIViewSet(
325
325
  table_title="Responses",
326
326
  table_class=tables.RelatedApprovalWorkflowStageResponseTable,
327
327
  table_filter="approval_workflow_stage__approval_workflow",
328
- section=SectionChoices.RIGHT_HALF,
328
+ section=SectionChoices.FULL_WIDTH,
329
329
  exclude_columns=["approval_workflow"],
330
330
  add_button_route=None,
331
331
  enable_related_link=False,
@@ -412,7 +412,7 @@ class ApprovalWorkflowStageUIViewSet(
412
412
  weight=200,
413
413
  table_class=tables.ApprovalWorkflowStageResponseTable,
414
414
  table_filter="approval_workflow_stage",
415
- section=SectionChoices.RIGHT_HALF,
415
+ section=SectionChoices.FULL_WIDTH,
416
416
  exclude_columns=["approval_workflow_stage"],
417
417
  table_title="Responses",
418
418
  enable_related_link=False,
@@ -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,6 +527,61 @@ class ApprovalWorkflowStageUIViewSet(
508
527
  messages.success(request, f"You denied {instance}.")
509
528
  return redirect(self.get_return_url(request))
510
529
 
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
+ )
537
+ def comment(self, request, *args, **kwargs):
538
+ """
539
+ Comment the approval workflow stage response.
540
+ """
541
+ instance = self.get_object()
542
+
543
+ if not instance.is_not_done_stage:
544
+ messages.error(
545
+ request, f"This stage is in {instance.state} state. Can't comment on an approved or denied stage."
546
+ )
547
+ return redirect(self.get_return_url(request, instance))
548
+
549
+ # We don't enforce approver-group/superuser check here, anyone can comment, not just an approver.
550
+
551
+ if request.method == "GET":
552
+ if existing_response := ApprovalWorkflowStageResponse.objects.filter(
553
+ approval_workflow_stage=instance, user=request.user
554
+ ).first():
555
+ form = ApprovalForm(initial={"comments": existing_response.comments})
556
+ else:
557
+ form = ApprovalForm()
558
+
559
+ template_name = "extras/approval_workflow/comment.html"
560
+
561
+ return render(
562
+ request,
563
+ template_name,
564
+ {
565
+ "obj": instance,
566
+ "object_under_review": instance.approval_workflow.object_under_review,
567
+ "form": form,
568
+ "obj_type": ApprovalWorkflowStage._meta.verbose_name,
569
+ "return_url": self.get_return_url(request, instance),
570
+ },
571
+ )
572
+
573
+ approval_workflow_stage_response, _ = ApprovalWorkflowStageResponse.objects.get_or_create(
574
+ approval_workflow_stage=instance, user=request.user
575
+ )
576
+ approval_workflow_stage_response.comments = request.data.get("comments")
577
+ # we don't want to change a state if is approved, denied or canceled
578
+ if approval_workflow_stage_response.state == ApprovalWorkflowStateChoices.PENDING:
579
+ approval_workflow_stage_response.state = ApprovalWorkflowStateChoices.COMMENT
580
+ approval_workflow_stage_response.save()
581
+ instance.refresh_from_db()
582
+ messages.success(request, f"You commented {instance}.")
583
+ return redirect(self.get_return_url(request))
584
+
511
585
 
512
586
  class ApprovalWorkflowStageResponseUIViewSet(
513
587
  ObjectBulkDestroyViewMixin,
@@ -659,6 +733,9 @@ class ObjectApprovalWorkflowView(generic.GenericView):
659
733
  "default_time_zone": get_current_timezone(),
660
734
  "stage_table": stage_table,
661
735
  "response_table": response_table,
736
+ "view_titles": self.get_view_titles(model=obj, view_type=""),
737
+ "breadcrumbs": self.get_breadcrumbs(model=obj, view_type=""),
738
+ "detail": True,
662
739
  **common_detail_view_context(request, obj),
663
740
  },
664
741
  )
@@ -1516,8 +1593,6 @@ class ObjectDynamicGroupsView(generic.GenericView):
1516
1593
  """
1517
1594
 
1518
1595
  base_template: Optional[str] = None
1519
- breadcrumbs = Breadcrumbs()
1520
- view_titles = Titles()
1521
1596
 
1522
1597
  def get(self, request, model, **kwargs):
1523
1598
  # Handle QuerySet restriction of parent object if needed
@@ -1551,8 +1626,8 @@ class ObjectDynamicGroupsView(generic.GenericView):
1551
1626
  "table": dynamicgroups_table,
1552
1627
  "base_template": base_template,
1553
1628
  "active_tab": "dynamic-groups",
1554
- "breadcrumbs": self.breadcrumbs,
1555
- "view_titles": self.view_titles,
1629
+ "view_titles": self.get_view_titles(model=obj, view_type=""),
1630
+ "breadcrumbs": self.get_breadcrumbs(model=obj, view_type=""),
1556
1631
  "detail": True,
1557
1632
  },
1558
1633
  )
@@ -22,8 +22,8 @@ VRF_RD_MAX_LENGTH = 21
22
22
  # Prefixes
23
23
  #
24
24
 
25
- PREFIX_LENGTH_MIN = 1
26
- PREFIX_LENGTH_MAX = 127 # IPv6
25
+ PREFIX_LENGTH_MIN = 0
26
+ PREFIX_LENGTH_MAX = 128 # IPv6
27
27
  PREFIX_ALLOWED_PARENT_TYPES = {
28
28
  PrefixTypeChoices.TYPE_CONTAINER: PrefixTypeChoices.TYPE_CONTAINER,
29
29
  PrefixTypeChoices.TYPE_NETWORK: PrefixTypeChoices.TYPE_CONTAINER,
nautobot/ipam/tables.py CHANGED
@@ -207,10 +207,11 @@ class NamespaceTable(BaseTable):
207
207
  name = tables.LinkColumn()
208
208
  tenant = TenantColumn()
209
209
  tags = TagColumn(url_name="ipam:namespace_list")
210
+ actions = ButtonsColumn(Namespace)
210
211
 
211
212
  class Meta(BaseTable.Meta):
212
213
  model = Namespace
213
- fields = ("pk", "name", "description", "tenant", "location")
214
+ fields = ("pk", "name", "description", "tenant", "location", "actions")
214
215
 
215
216
 
216
217
  #
@@ -247,11 +248,11 @@ class VRFTable(StatusTableMixin, BaseTable):
247
248
  class VRFDeviceAssignmentTable(BaseTable):
248
249
  """Table for displaying VRF Device Assignments with RD."""
249
250
 
250
- vrf = tables.Column(verbose_name="VRF", linkify=lambda record: record.vrf.get_absolute_url(), accessor="vrf.name")
251
+ vrf = tables.Column(verbose_name="VRF", linkify=lambda record: record.vrf.get_absolute_url(), accessor="vrf__name")
251
252
  namespace = tables.Column(
252
253
  verbose_name="Namespace",
253
254
  linkify=lambda record: record.vrf.namespace.get_absolute_url(),
254
- accessor="vrf.namespace.name",
255
+ accessor="vrf__namespace__name",
255
256
  )
256
257
  related_object_type = tables.TemplateColumn(
257
258
  template_code="""
@@ -287,10 +288,10 @@ class VRFDeviceAssignmentTable(BaseTable):
287
288
  class VRFPrefixAssignmentTable(BaseTable):
288
289
  """Table for displaying VRF Prefix Assignments."""
289
290
 
290
- vrf = tables.Column(verbose_name="VRF", linkify=lambda record: record.vrf.get_absolute_url(), accessor="vrf.name")
291
+ vrf = tables.Column(verbose_name="VRF", linkify=lambda record: record.vrf.get_absolute_url(), accessor="vrf__name")
291
292
  prefix = tables.Column(linkify=True)
292
- rd = tables.Column(accessor="vrf.rd", verbose_name="RD")
293
- tenant = TenantColumn(accessor="vrf.tenant")
293
+ rd = tables.Column(accessor="vrf__rd", verbose_name="RD")
294
+ tenant = TenantColumn(accessor="vrf__tenant")
294
295
 
295
296
  class Meta(BaseTable.Meta):
296
297
  model = VRFPrefixAssignment
@@ -0,0 +1,6 @@
1
+ #
2
+ # TCP/UDP Ports
3
+ #
4
+
5
+ PORT_VALUE_MIN = 0
6
+ PORT_VALUE_MAX = 65535
@@ -3,6 +3,7 @@
3
3
  import uuid
4
4
 
5
5
  import django.core.serializers.json
6
+ import django.core.validators
6
7
  from django.db import migrations, models
7
8
  import django.db.models.deletion
8
9
 
@@ -86,7 +87,12 @@ class Migration(migrations.Migration):
86
87
  ("interval", models.PositiveIntegerField(blank=True, null=True)),
87
88
  ("retry", models.PositiveIntegerField(blank=True, null=True)),
88
89
  ("timeout", models.PositiveIntegerField(blank=True, null=True)),
89
- ("port", models.PositiveIntegerField(blank=True, null=True)),
90
+ (
91
+ "port",
92
+ models.PositiveIntegerField(
93
+ blank=True, null=True, validators=[django.core.validators.MaxValueValidator(65535)]
94
+ ),
95
+ ),
90
96
  ("health_check_type", models.CharField(blank=True, max_length=255)),
91
97
  ("tags", nautobot.core.models.fields.TagsField(through="extras.TaggedItem", to="extras.Tag")),
92
98
  (
@@ -175,7 +181,7 @@ class Migration(migrations.Migration):
175
181
  models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
176
182
  ),
177
183
  ("label", models.CharField(blank=True, max_length=255)),
178
- ("port", models.PositiveIntegerField()),
184
+ ("port", models.PositiveIntegerField(validators=[django.core.validators.MaxValueValidator(65535)])),
179
185
  ("ssl_offload", models.BooleanField(default=False)),
180
186
  ],
181
187
  options={
@@ -203,7 +209,12 @@ class Migration(migrations.Migration):
203
209
  models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
204
210
  ),
205
211
  ("name", models.CharField(max_length=255)),
206
- ("port", models.PositiveIntegerField(blank=True, null=True)),
212
+ (
213
+ "port",
214
+ models.PositiveIntegerField(
215
+ blank=True, null=True, validators=[django.core.validators.MaxValueValidator(65535)]
216
+ ),
217
+ ),
207
218
  ("protocol", models.CharField(blank=True, max_length=255)),
208
219
  ("source_nat_type", models.CharField(blank=True, max_length=255)),
209
220
  ("load_balancer_type", models.CharField(blank=True, max_length=255)),
@@ -1,5 +1,6 @@
1
1
  """Models for Load Balancer Models."""
2
2
 
3
+ from django.core.validators import MaxValueValidator
3
4
  from django.db import models
4
5
 
5
6
  from nautobot.core.constants import CHARFIELD_MAX_LENGTH
@@ -7,7 +8,7 @@ from nautobot.core.models import BaseModel
7
8
  from nautobot.core.models.generics import PrimaryModel
8
9
  from nautobot.extras.models import StatusField
9
10
  from nautobot.extras.utils import extras_features
10
- from nautobot.load_balancers import choices
11
+ from nautobot.load_balancers import choices, constants
11
12
 
12
13
 
13
14
  @extras_features("custom_links", "custom_validators", "export_templates", "graphql", "webhooks")
@@ -18,7 +19,7 @@ class VirtualServer(PrimaryModel): # pylint: disable=too-many-ancestors
18
19
  to="ipam.IPAddress", on_delete=models.PROTECT, related_name="virtual_servers", verbose_name="VIP"
19
20
  )
20
21
  name = models.CharField(max_length=CHARFIELD_MAX_LENGTH)
21
- port = models.PositiveIntegerField(blank=True, null=True)
22
+ port = models.PositiveIntegerField(blank=True, null=True, validators=[MaxValueValidator(constants.PORT_VALUE_MAX)])
22
23
  protocol = models.CharField(max_length=CHARFIELD_MAX_LENGTH, blank=True, choices=choices.ProtocolChoices)
23
24
  source_nat_pool = models.ForeignKey(
24
25
  to="ipam.Prefix",
@@ -200,7 +201,7 @@ class LoadBalancerPoolMember(PrimaryModel): # pylint: disable=too-many-ancestor
200
201
  related_name="load_balancer_pool_members",
201
202
  on_delete=models.PROTECT,
202
203
  )
203
- port = models.PositiveIntegerField()
204
+ port = models.PositiveIntegerField(validators=[MaxValueValidator(constants.PORT_VALUE_MAX)])
204
205
  ssl_offload = models.BooleanField(
205
206
  default=False,
206
207
  verbose_name="SSL Offload",
@@ -262,7 +263,7 @@ class HealthCheckMonitor(PrimaryModel): # pylint: disable=too-many-ancestors
262
263
  interval = models.PositiveIntegerField(blank=True, null=True)
263
264
  retry = models.PositiveIntegerField(blank=True, null=True, help_text="Number of retries before marking as down")
264
265
  timeout = models.PositiveIntegerField(blank=True, null=True)
265
- port = models.PositiveIntegerField(blank=True, null=True)
266
+ port = models.PositiveIntegerField(blank=True, null=True, validators=[MaxValueValidator(constants.PORT_VALUE_MAX)])
266
267
  health_check_type = models.CharField(
267
268
  max_length=CHARFIELD_MAX_LENGTH,
268
269
  choices=choices.HealthCheckTypeChoices,
@@ -64,6 +64,7 @@ class VirtualServerTable(BaseTable):
64
64
  "ssl_offload",
65
65
  "certificate_profiles_count",
66
66
  "tags",
67
+ "actions",
67
68
  )
68
69
 
69
70
  default_columns = (
@@ -108,6 +109,7 @@ class LoadBalancerPoolTable(BaseTable):
108
109
  "load_balancer_pool_member_count",
109
110
  "health_check_monitor",
110
111
  "tenant",
112
+ "actions",
111
113
  )
112
114
  default_columns = (
113
115
  "pk",
@@ -153,6 +155,7 @@ class LoadBalancerPoolMemberTable(StatusTableMixin, BaseTable):
153
155
  "health_check_monitor",
154
156
  "certificate_profiles_count",
155
157
  "tenant",
158
+ "actions",
156
159
  )
157
160
  default_columns = (
158
161
  "pk",
@@ -206,6 +209,7 @@ class HealthCheckMonitorTable(BaseTable):
206
209
  "load_balancer_pool_count",
207
210
  "load_balancer_pool_member_count",
208
211
  "tenant",
212
+ "actions",
209
213
  )
210
214
  default_columns = (
211
215
  "pk",
@@ -243,6 +247,7 @@ class CertificateProfileTable(BaseTable):
243
247
  "expiration_date",
244
248
  "cipher",
245
249
  "tenant",
250
+ "actions",
246
251
  )
247
252
  default_columns = (
248
253
  "pk",