nautobot 2.4.0__py3-none-any.whl → 2.4.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 (40) hide show
  1. nautobot/core/celery/schedulers.py +1 -1
  2. nautobot/core/filters.py +48 -21
  3. nautobot/core/jobs/bulk_actions.py +56 -19
  4. nautobot/core/models/__init__.py +2 -0
  5. nautobot/core/tables.py +5 -1
  6. nautobot/core/testing/filters.py +25 -13
  7. nautobot/core/testing/integration.py +86 -4
  8. nautobot/core/tests/test_filters.py +209 -246
  9. nautobot/core/tests/test_jobs.py +250 -93
  10. nautobot/core/tests/test_models.py +9 -0
  11. nautobot/core/views/generic.py +80 -48
  12. nautobot/core/views/mixins.py +34 -6
  13. nautobot/dcim/api/serializers.py +2 -2
  14. nautobot/dcim/constants.py +6 -13
  15. nautobot/dcim/factory.py +6 -1
  16. nautobot/dcim/tests/integration/test_device_bulk_delete.py +189 -0
  17. nautobot/dcim/tests/integration/test_device_bulk_edit.py +181 -0
  18. nautobot/dcim/tests/test_api.py +0 -2
  19. nautobot/dcim/tests/test_models.py +42 -28
  20. nautobot/extras/forms/mixins.py +1 -1
  21. nautobot/extras/jobs.py +15 -6
  22. nautobot/extras/templatetags/job_buttons.py +4 -4
  23. nautobot/extras/tests/test_forms.py +13 -0
  24. nautobot/extras/tests/test_jobs.py +18 -13
  25. nautobot/extras/tests/test_models.py +6 -0
  26. nautobot/extras/tests/test_views.py +4 -3
  27. nautobot/ipam/tests/test_api.py +20 -0
  28. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +36 -1
  29. nautobot/project-static/docs/objects.inv +0 -0
  30. nautobot/project-static/docs/release-notes/version-2.4.html +108 -0
  31. nautobot/project-static/docs/search/search_index.json +1 -1
  32. nautobot/project-static/docs/sitemap.xml +288 -288
  33. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  34. nautobot/wireless/tests/test_views.py +22 -1
  35. {nautobot-2.4.0.dist-info → nautobot-2.4.1.dist-info}/METADATA +2 -2
  36. {nautobot-2.4.0.dist-info → nautobot-2.4.1.dist-info}/RECORD +40 -38
  37. {nautobot-2.4.0.dist-info → nautobot-2.4.1.dist-info}/LICENSE.txt +0 -0
  38. {nautobot-2.4.0.dist-info → nautobot-2.4.1.dist-info}/NOTICE +0 -0
  39. {nautobot-2.4.0.dist-info → nautobot-2.4.1.dist-info}/WHEEL +0 -0
  40. {nautobot-2.4.0.dist-info → nautobot-2.4.1.dist-info}/entry_points.txt +0 -0
@@ -37,7 +37,7 @@ from nautobot.extras.choices import (
37
37
  ObjectChangeEventContextChoices,
38
38
  )
39
39
  from nautobot.extras.context_managers import change_logging, JobHookChangeContext, web_request_context
40
- from nautobot.extras.jobs import get_job, get_jobs
40
+ from nautobot.extras.jobs import BaseJob, get_job, get_jobs
41
41
  from nautobot.extras.models import Job, JobQueue
42
42
  from nautobot.extras.models.jobs import JobLogEntry
43
43
 
@@ -47,6 +47,11 @@ class JobTest(TestCase):
47
47
  Test job features that don't require a transaction test case.
48
48
  """
49
49
 
50
+ def test_as_form_no_job_model(self):
51
+ """Job.as_form() test with no corresponding job_model (https://github.com/nautobot/nautobot/issues/6773)."""
52
+ form = BaseJob.as_form()
53
+ self.assertSequenceEqual(list(form.fields.keys()), ["_job_queue", "_profile"])
54
+
50
55
  def test_field_default(self):
51
56
  """
52
57
  Job test with field that is a default value that is falsey.
@@ -55,7 +60,7 @@ class JobTest(TestCase):
55
60
  module = "field_default"
56
61
  name = "TestFieldDefault"
57
62
  job_class = get_job(f"{module}.{name}")
58
- form = job_class().as_form()
63
+ form = job_class.as_form()
59
64
 
60
65
  self.assertInHTML(
61
66
  """<tr><th><label for="id_var_int">Var int:</label></th><td>
@@ -74,7 +79,7 @@ class JobTest(TestCase):
74
79
  module = "field_order"
75
80
  name = "TestFieldOrder"
76
81
  job_class = get_job(f"{module}.{name}")
77
- form = job_class().as_form()
82
+ form = job_class.as_form()
78
83
  self.assertSequenceEqual(list(form.fields.keys()), ["var1", "var2", "var23", "_job_queue", "_profile"])
79
84
 
80
85
  def test_no_field_order(self):
@@ -84,7 +89,7 @@ class JobTest(TestCase):
84
89
  module = "no_field_order"
85
90
  name = "TestNoFieldOrder"
86
91
  job_class = get_job(f"{module}.{name}")
87
- form = job_class().as_form()
92
+ form = job_class.as_form()
88
93
  self.assertSequenceEqual(list(form.fields.keys()), ["var23", "var2", "_job_queue", "_profile"])
89
94
 
90
95
  def test_no_field_order_inherited_variable(self):
@@ -94,7 +99,7 @@ class JobTest(TestCase):
94
99
  module = "no_field_order"
95
100
  name = "TestDefaultFieldOrderWithInheritance"
96
101
  job_class = get_job(f"{module}.{name}")
97
- form = job_class().as_form()
102
+ form = job_class.as_form()
98
103
  self.assertSequenceEqual(
99
104
  list(form.fields.keys()),
100
105
  ["testvar1", "b_testvar2", "a_testvar3", "_job_queue", "_profile"],
@@ -109,14 +114,14 @@ class JobTest(TestCase):
109
114
  # not overridden on job model, initial form field value should match job class
110
115
  job_model.dryrun_default_override = False
111
116
  job_model.save()
112
- form = job_class().as_form()
117
+ form = job_class.as_form()
113
118
  self.assertEqual(form.fields["dryrun"].initial, job_class.dryrun_default)
114
119
 
115
120
  # overridden on job model, initial form field value should match job model
116
121
  job_model.dryrun_default_override = True
117
122
  job_model.dryrun_default = not job_class.dryrun_default
118
123
  job_model.save()
119
- form = job_class().as_form()
124
+ form = job_class.as_form()
120
125
  self.assertEqual(form.fields["dryrun"].initial, job_model.dryrun_default)
121
126
 
122
127
  def test_job_task_queues_setter(self):
@@ -145,7 +150,7 @@ class JobTest(TestCase):
145
150
  name="irrelevant", defaults={"queue_type": JobQueueTypeChoices.TYPE_CELERY}
146
151
  )
147
152
  job_model.job_queues.set([jq_1, jq_2])
148
- form = job_class().as_form()
153
+ form = job_class.as_form()
149
154
  self.assertQuerySetEqual(
150
155
  form.fields["_job_queue"].queryset,
151
156
  models.JobQueue.objects.filter(jobs=job_model),
@@ -576,7 +581,7 @@ class JobTransactionTest(TransactionTestCase):
576
581
  "ipv6_with_mask": "2001:db8::1/64",
577
582
  "ipv6_network": "2001:db8::/64",
578
583
  }
579
- form = job_class().as_form(form_data)
584
+ form = job_class.as_form(form_data)
580
585
  self.assertTrue(form.is_valid())
581
586
 
582
587
  # Prepare the job data
@@ -786,7 +791,7 @@ class JobFileUploadTest(TransactionTestCase):
786
791
 
787
792
  # Serialize the file to FileProxy
788
793
  data = {"file": self.test_file}
789
- form = job_class().as_form(files=data)
794
+ form = job_class.as_form(files=data)
790
795
  self.assertTrue(form.is_valid())
791
796
  serialized_data = job_class.serialize_data(form.cleaned_data)
792
797
 
@@ -816,7 +821,7 @@ class JobFileUploadTest(TransactionTestCase):
816
821
 
817
822
  # Serialize the file to FileProxy
818
823
  data = {"file": self.test_file}
819
- form = job_class().as_form(files=data)
824
+ form = job_class.as_form(files=data)
820
825
  self.assertTrue(form.is_valid())
821
826
  serialized_data = job_class.serialize_data(form.cleaned_data)
822
827
 
@@ -1021,7 +1026,7 @@ class JobButtonReceiverTest(TestCase):
1021
1026
  module = "job_button_receiver"
1022
1027
  name = "TestJobButtonReceiverSimple"
1023
1028
  job_class, _job_model = get_job_class_and_model(module, name)
1024
- form = job_class().as_form()
1029
+ form = job_class.as_form()
1025
1030
  self.assertSequenceEqual(list(form.fields.keys()), ["object_pk", "object_model_name", "_job_queue", "_profile"])
1026
1031
 
1027
1032
  def test_hidden(self):
@@ -1084,7 +1089,7 @@ class JobHookReceiverTest(TestCase):
1084
1089
  module = "job_hook_receiver"
1085
1090
  name = "TestJobHookReceiverLog"
1086
1091
  job_class, _job_model = get_job_class_and_model(module, name)
1087
- form = job_class().as_form()
1092
+ form = job_class.as_form()
1088
1093
  self.assertSequenceEqual(list(form.fields.keys()), ["object_change", "_job_queue", "_profile"])
1089
1094
 
1090
1095
  def test_hidden(self):
@@ -2146,6 +2146,12 @@ class ScheduledJobTest(ModelTestCases.BaseModelTestCase):
2146
2146
  # entry = NautobotScheduleEntry(model=sj)
2147
2147
  # scheduler = NautobotDatabaseScheduler(app=entry.app)
2148
2148
  # scheduler.apply_async(entry=entry, producer=None, advance=False)
2149
+ # Check scheduled job runs correctly with no job queue
2150
+ # sj.job_queue = None
2151
+ # sj.save()
2152
+ # entry = NautobotScheduleEntry(model=sj)
2153
+ # scheduler = NautobotDatabaseScheduler(app=entry.app)
2154
+ # scheduler.apply_async(entry=entry, producer=None, advance=False)
2149
2155
 
2150
2156
 
2151
2157
  class SecretTest(ModelTestCases.BaseModelTestCase):
@@ -2955,8 +2955,9 @@ class JobButtonRenderingTestCase(TestCase):
2955
2955
  response = self.client.get(self.location_type.get_absolute_url(), follow=True)
2956
2956
  self.assertEqual(response.status_code, 200)
2957
2957
  content = extract_page_body(response.content.decode(response.charset))
2958
- job_queues = self.job.job_queues.all().values_list("name", flat=True)
2959
- self.assertIn(f'<input type="hidden" name="_job_queue" value="{job_queues[0]}">', content, content)
2958
+ job_queues = self.job.job_queues.all()
2959
+ _job_queue = job_queues[0]
2960
+ self.assertIn(f'<input type="hidden" name="_job_queue" value="{_job_queue.pk}">', content, content)
2960
2961
 
2961
2962
  self.job.job_queues_override = False
2962
2963
  self.job.save()
@@ -2965,7 +2966,7 @@ class JobButtonRenderingTestCase(TestCase):
2965
2966
  self.assertEqual(response.status_code, 200)
2966
2967
  content = extract_page_body(response.content.decode(response.charset))
2967
2968
  self.assertIn(
2968
- f'<input type="hidden" name="_job_queue" value="{self.job.default_job_queue.name}">', content, content
2969
+ f'<input type="hidden" name="_job_queue" value="{self.job.default_job_queue.pk}">', content, content
2969
2970
  )
2970
2971
 
2971
2972
  def test_view_object_with_unsafe_text(self):
@@ -965,6 +965,26 @@ class PrefixTest(APIViewTestCases.APIViewTestCase):
965
965
  self.assertHttpStatus(response, status.HTTP_200_OK)
966
966
  self.assertEqual(len(response.data), 0)
967
967
 
968
+ def test_prefix_type_filter(self):
969
+ url = reverse("ipam-api:prefix-list")
970
+ self.add_permissions("ipam.view_prefix")
971
+
972
+ test_cases = {
973
+ "ic": "WOR",
974
+ "isw": "NET",
975
+ "iew": "WORK",
976
+ "ie": "NETWORK",
977
+ }
978
+
979
+ for type_filter_lookup, type_filter_value in test_cases.items():
980
+ with self.subTest(render_as=type_filter_lookup):
981
+ response = self.client.get(f"{url}?type__{type_filter_lookup}={type_filter_value}", **self.header)
982
+
983
+ # Assert that the prefixes returned match the type filter
984
+ self.assertEqual(response.status_code, 200)
985
+ for result in response.data["results"]:
986
+ self.assertEqual(result["type"]["value"], "network")
987
+
968
988
 
969
989
  class PrefixLocationAssignmentTest(APIViewTestCases.APIViewTestCase):
970
990
  model = PrefixLocationAssignment
@@ -8394,6 +8394,15 @@
8394
8394
  </span>
8395
8395
  </a>
8396
8396
 
8397
+ </li>
8398
+
8399
+ <li class="md-nav__item">
8400
+ <a href="#nautobot.apps.testing.SeleniumTestCase.fill_filters_select2_field" class="md-nav__link">
8401
+ <span class="md-ellipsis">
8402
+ fill_filters_select2_field
8403
+ </span>
8404
+ </a>
8405
+
8397
8406
  </li>
8398
8407
 
8399
8408
  <li class="md-nav__item">
@@ -11570,6 +11579,15 @@
11570
11579
  </span>
11571
11580
  </a>
11572
11581
 
11582
+ </li>
11583
+
11584
+ <li class="md-nav__item">
11585
+ <a href="#nautobot.apps.testing.SeleniumTestCase.fill_filters_select2_field" class="md-nav__link">
11586
+ <span class="md-ellipsis">
11587
+ fill_filters_select2_field
11588
+ </span>
11589
+ </a>
11590
+
11573
11591
  </li>
11574
11592
 
11575
11593
  <li class="md-nav__item">
@@ -14769,6 +14787,23 @@ so there is no need to run <code>collectstatic</code> prior to running tests.</p
14769
14787
  <div class="doc doc-object doc-function">
14770
14788
 
14771
14789
 
14790
+ <h3 id="nautobot.apps.testing.SeleniumTestCase.fill_filters_select2_field" class="doc doc-heading">
14791
+ <code class="highlight language-python"><span class="n">fill_filters_select2_field</span><span class="p">(</span><span class="n">field_name</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span></code>
14792
+
14793
+ <a href="#nautobot.apps.testing.SeleniumTestCase.fill_filters_select2_field" class="headerlink" title="Permanent link">&para;</a></h3>
14794
+
14795
+
14796
+ <div class="doc doc-contents ">
14797
+
14798
+ <p>Helper function to fill a Select2 single selection field on filters modals.</p>
14799
+
14800
+ </div>
14801
+
14802
+ </div>
14803
+
14804
+ <div class="doc doc-object doc-function">
14805
+
14806
+
14772
14807
  <h3 id="nautobot.apps.testing.SeleniumTestCase.fill_select2_field" class="doc doc-heading">
14773
14808
  <code class="highlight language-python"><span class="n">fill_select2_field</span><span class="p">(</span><span class="n">field_name</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span></code>
14774
14809
 
@@ -14777,7 +14812,7 @@ so there is no need to run <code>collectstatic</code> prior to running tests.</p
14777
14812
 
14778
14813
  <div class="doc doc-contents ">
14779
14814
 
14780
- <p>Helper function to fill a Select2 single selection field.</p>
14815
+ <p>Helper function to fill a Select2 single selection field on add/edit forms.</p>
14781
14816
 
14782
14817
  </div>
14783
14818
 
Binary file
@@ -8686,6 +8686,48 @@
8686
8686
  </ul>
8687
8687
  </nav>
8688
8688
 
8689
+ </li>
8690
+
8691
+ <li class="md-nav__item">
8692
+ <a href="#v241-2025-01-21" class="md-nav__link">
8693
+ <span class="md-ellipsis">
8694
+ v2.4.1 (2025-01-21)
8695
+ </span>
8696
+ </a>
8697
+
8698
+ <nav class="md-nav" aria-label="v2.4.1 (2025-01-21)">
8699
+ <ul class="md-nav__list">
8700
+
8701
+ <li class="md-nav__item">
8702
+ <a href="#security-in-v241" class="md-nav__link">
8703
+ <span class="md-ellipsis">
8704
+ Security in v2.4.1
8705
+ </span>
8706
+ </a>
8707
+
8708
+ </li>
8709
+
8710
+ <li class="md-nav__item">
8711
+ <a href="#fixed-in-v241" class="md-nav__link">
8712
+ <span class="md-ellipsis">
8713
+ Fixed in v2.4.1
8714
+ </span>
8715
+ </a>
8716
+
8717
+ </li>
8718
+
8719
+ <li class="md-nav__item">
8720
+ <a href="#housekeeping-in-v241" class="md-nav__link">
8721
+ <span class="md-ellipsis">
8722
+ Housekeeping in v2.4.1
8723
+ </span>
8724
+ </a>
8725
+
8726
+ </li>
8727
+
8728
+ </ul>
8729
+ </nav>
8730
+
8689
8731
  </li>
8690
8732
 
8691
8733
  <li class="md-nav__item">
@@ -9672,6 +9714,48 @@
9672
9714
  </ul>
9673
9715
  </nav>
9674
9716
 
9717
+ </li>
9718
+
9719
+ <li class="md-nav__item">
9720
+ <a href="#v241-2025-01-21" class="md-nav__link">
9721
+ <span class="md-ellipsis">
9722
+ v2.4.1 (2025-01-21)
9723
+ </span>
9724
+ </a>
9725
+
9726
+ <nav class="md-nav" aria-label="v2.4.1 (2025-01-21)">
9727
+ <ul class="md-nav__list">
9728
+
9729
+ <li class="md-nav__item">
9730
+ <a href="#security-in-v241" class="md-nav__link">
9731
+ <span class="md-ellipsis">
9732
+ Security in v2.4.1
9733
+ </span>
9734
+ </a>
9735
+
9736
+ </li>
9737
+
9738
+ <li class="md-nav__item">
9739
+ <a href="#fixed-in-v241" class="md-nav__link">
9740
+ <span class="md-ellipsis">
9741
+ Fixed in v2.4.1
9742
+ </span>
9743
+ </a>
9744
+
9745
+ </li>
9746
+
9747
+ <li class="md-nav__item">
9748
+ <a href="#housekeeping-in-v241" class="md-nav__link">
9749
+ <span class="md-ellipsis">
9750
+ Housekeeping in v2.4.1
9751
+ </span>
9752
+ </a>
9753
+
9754
+ </li>
9755
+
9756
+ </ul>
9757
+ </nav>
9758
+
9675
9759
  </li>
9676
9760
 
9677
9761
  <li class="md-nav__item">
@@ -9946,6 +10030,30 @@
9946
10030
 
9947
10031
  <!-- towncrier release notes start -->
9948
10032
 
10033
+ <h2 id="v241-2025-01-21">v2.4.1 (2025-01-21)<a class="headerlink" href="#v241-2025-01-21" title="Permanent link">&para;</a></h2>
10034
+ <h3 id="security-in-v241">Security in v2.4.1<a class="headerlink" href="#security-in-v241" title="Permanent link">&para;</a></h3>
10035
+ <ul>
10036
+ <li><a href="https://github.com/nautobot/nautobot/issues/6780">#6780</a> - Updated <code>Django</code> to <code>4.2.18</code> to address <code>CVE-2024-56374</code>.</li>
10037
+ </ul>
10038
+ <h3 id="fixed-in-v241">Fixed in v2.4.1<a class="headerlink" href="#fixed-in-v241" title="Permanent link">&para;</a></h3>
10039
+ <ul>
10040
+ <li><a href="https://github.com/nautobot/nautobot/issues/6427">#6427</a> - Fixed a bug which allowed several wireless interface types to accept cables.</li>
10041
+ <li><a href="https://github.com/nautobot/nautobot/issues/6489">#6489</a> - Fixed partial-match filters (such as <code>__ic</code> and <code>__isw</code>) on fields that have restricted choices (<code>Prefix.type</code>, <code>Interface.type</code>, etc.) so that partial values are no longer rejected.</li>
10042
+ <li><a href="https://github.com/nautobot/nautobot/issues/6763">#6763</a> - Fixed the issue where the Wireless Network detail view fails to render when any record in the Controller Managed Device Groups table is missing a VLAN.</li>
10043
+ <li><a href="https://github.com/nautobot/nautobot/issues/6770">#6770</a> - Fixed breakage of JobButton functionality.</li>
10044
+ <li><a href="https://github.com/nautobot/nautobot/issues/6771">#6771</a> - Reverted breaking changes to various generic View base class attributes.</li>
10045
+ <li><a href="https://github.com/nautobot/nautobot/issues/6773">#6773</a> - Fixed an exception when trying to render a Job class to a form when no corresponding Job database record exists.</li>
10046
+ <li><a href="https://github.com/nautobot/nautobot/issues/6776">#6776</a> - Fixed <code>FilterTestCase.generic_filter_tests</code> to again be optional as intended.</li>
10047
+ <li><a href="https://github.com/nautobot/nautobot/issues/6779">#6779</a> - Fixed Object Bulk Delete and Object Bulk Edit functionalities.</li>
10048
+ <li><a href="https://github.com/nautobot/nautobot/issues/6783">#6783</a> - Fixed <code>NautobotDataBaseScheduler</code> unable to run Scheduled Jobs without job queues assigned.</li>
10049
+ <li><a href="https://github.com/nautobot/nautobot/issues/6786">#6786</a> - Fixed incorrect marking of <code>capabilities</code> field as required on Controller and ControllerManagedDeviceGroup REST APIs.</li>
10050
+ <li><a href="https://github.com/nautobot/nautobot/issues/6792">#6792</a> - Fixed <code>natural_key_field_lookups</code> for proxy models.</li>
10051
+ </ul>
10052
+ <h3 id="housekeeping-in-v241">Housekeeping in v2.4.1<a class="headerlink" href="#housekeeping-in-v241" title="Permanent link">&para;</a></h3>
10053
+ <ul>
10054
+ <li><a href="https://github.com/nautobot/nautobot/issues/6768">#6768</a> - Fixed link to changelog fragment documentation.</li>
10055
+ <li><a href="https://github.com/nautobot/nautobot/issues/6794">#6794</a> - Fixed Device factory to ensure that it only selects SoftwareImageFiles that are permitted for a given Device.</li>
10056
+ </ul>
9949
10057
  <h2 id="v240-2025-01-10">v2.4.0 (2025-01-10)<a class="headerlink" href="#v240-2025-01-10" title="Permanent link">&para;</a></h2>
9950
10058
  <h3 id="added-in-v240">Added in v2.4.0<a class="headerlink" href="#added-in-v240" title="Permanent link">&para;</a></h3>
9951
10059
  <ul>