nautobot 2.2.2__py3-none-any.whl → 2.2.3__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.
Files changed (88) hide show
  1. nautobot/apps/jobs.py +2 -0
  2. nautobot/core/api/utils.py +12 -9
  3. nautobot/core/apps/__init__.py +2 -2
  4. nautobot/core/celery/__init__.py +79 -68
  5. nautobot/core/celery/backends.py +9 -1
  6. nautobot/core/celery/control.py +4 -7
  7. nautobot/core/celery/schedulers.py +4 -2
  8. nautobot/core/celery/task.py +78 -5
  9. nautobot/core/graphql/schema.py +2 -1
  10. nautobot/core/jobs/__init__.py +2 -1
  11. nautobot/core/templates/generic/object_list.html +3 -3
  12. nautobot/core/templatetags/helpers.py +66 -9
  13. nautobot/core/testing/__init__.py +6 -1
  14. nautobot/core/testing/api.py +12 -13
  15. nautobot/core/testing/mixins.py +2 -2
  16. nautobot/core/testing/views.py +50 -51
  17. nautobot/core/tests/test_api.py +23 -2
  18. nautobot/core/tests/test_templatetags_helpers.py +32 -0
  19. nautobot/core/tests/test_views.py +19 -0
  20. nautobot/core/tests/test_views_utils.py +22 -1
  21. nautobot/core/utils/module_loading.py +89 -0
  22. nautobot/core/views/utils.py +3 -2
  23. nautobot/dcim/choices.py +14 -0
  24. nautobot/dcim/forms.py +51 -1
  25. nautobot/dcim/models/device_components.py +9 -5
  26. nautobot/dcim/templates/dcim/location.html +32 -13
  27. nautobot/dcim/templates/dcim/location_migrate_data_to_contact.html +102 -0
  28. nautobot/dcim/tests/test_views.py +137 -0
  29. nautobot/dcim/urls.py +5 -0
  30. nautobot/dcim/views.py +149 -1
  31. nautobot/extras/api/views.py +21 -10
  32. nautobot/extras/constants.py +3 -3
  33. nautobot/extras/datasources/git.py +47 -58
  34. nautobot/extras/forms/forms.py +3 -1
  35. nautobot/extras/jobs.py +79 -146
  36. nautobot/extras/models/datasources.py +0 -2
  37. nautobot/extras/models/jobs.py +36 -18
  38. nautobot/extras/plugins/__init__.py +1 -20
  39. nautobot/extras/signals.py +6 -9
  40. nautobot/extras/test_jobs/__init__.py +8 -0
  41. nautobot/extras/test_jobs/dry_run.py +3 -2
  42. nautobot/extras/test_jobs/fail.py +43 -0
  43. nautobot/extras/test_jobs/ipaddress_vars.py +40 -1
  44. nautobot/extras/test_jobs/jobs_module/__init__.py +5 -0
  45. nautobot/extras/test_jobs/jobs_module/jobs_submodule/__init__.py +1 -0
  46. nautobot/extras/test_jobs/jobs_module/jobs_submodule/jobs.py +6 -0
  47. nautobot/extras/test_jobs/pass.py +40 -0
  48. nautobot/extras/test_jobs/relative_import.py +11 -0
  49. nautobot/extras/tests/test_api.py +3 -0
  50. nautobot/extras/tests/test_datasources.py +125 -118
  51. nautobot/extras/tests/test_job_variables.py +57 -15
  52. nautobot/extras/tests/test_jobs.py +135 -1
  53. nautobot/extras/tests/test_models.py +26 -19
  54. nautobot/extras/tests/test_plugins.py +1 -3
  55. nautobot/extras/tests/test_views.py +2 -4
  56. nautobot/extras/views.py +47 -95
  57. nautobot/ipam/api/views.py +8 -1
  58. nautobot/ipam/graphql/types.py +11 -0
  59. nautobot/ipam/mixins.py +32 -0
  60. nautobot/ipam/models.py +2 -1
  61. nautobot/ipam/querysets.py +6 -1
  62. nautobot/ipam/tests/test_models.py +82 -0
  63. nautobot/project-static/docs/assets/extra.css +4 -0
  64. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +1 -1
  65. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +180 -211
  66. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +1 -1
  67. nautobot/project-static/docs/development/core/application-registry.html +126 -84
  68. nautobot/project-static/docs/development/core/model-checklist.html +49 -1
  69. nautobot/project-static/docs/development/core/model-features.html +1 -1
  70. nautobot/project-static/docs/development/jobs/index.html +334 -58
  71. nautobot/project-static/docs/development/jobs/migration/from-v1.html +1 -1
  72. nautobot/project-static/docs/objects.inv +0 -0
  73. nautobot/project-static/docs/release-notes/version-2.2.html +237 -55
  74. nautobot/project-static/docs/search/search_index.json +1 -1
  75. nautobot/project-static/docs/sitemap.xml +254 -254
  76. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  77. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +7 -4
  78. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +111 -0
  79. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +15 -28
  80. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +4 -4
  81. nautobot/project-static/js/forms.js +18 -11
  82. {nautobot-2.2.2.dist-info → nautobot-2.2.3.dist-info}/METADATA +3 -3
  83. {nautobot-2.2.2.dist-info → nautobot-2.2.3.dist-info}/RECORD +87 -81
  84. nautobot/extras/test_jobs/job_variables.py +0 -93
  85. {nautobot-2.2.2.dist-info → nautobot-2.2.3.dist-info}/LICENSE.txt +0 -0
  86. {nautobot-2.2.2.dist-info → nautobot-2.2.3.dist-info}/NOTICE +0 -0
  87. {nautobot-2.2.2.dist-info → nautobot-2.2.3.dist-info}/WHEEL +0 -0
  88. {nautobot-2.2.2.dist-info → nautobot-2.2.3.dist-info}/entry_points.txt +0 -0
@@ -3,26 +3,32 @@ from django.test import TestCase
3
3
  from netaddr import IPAddress, IPNetwork
4
4
 
5
5
  from nautobot.dcim.models import Device
6
- from nautobot.extras.models import Role
7
- from nautobot.extras.test_jobs.job_variables import (
8
- BooleanVarJob,
9
- ChoiceVarJob,
10
- FileVarJob,
11
- IntegerVarJob,
12
- IPAddressVarJob,
13
- IPAddressWithMaskVarJob,
14
- IPNetworkVarJob,
15
- JSONVarJob,
16
- MultiChoiceVarJob,
17
- MultiObjectVarJob,
18
- ObjectVarJob,
19
- StringVarJob,
20
- TextVarJob,
6
+ from nautobot.extras.jobs import (
7
+ BooleanVar,
8
+ ChoiceVar,
9
+ FileVar,
10
+ IntegerVar,
11
+ IPAddressVar,
12
+ IPAddressWithMaskVar,
13
+ IPNetworkVar,
14
+ Job,
15
+ JSONVar,
16
+ MultiChoiceVar,
17
+ MultiObjectVar,
18
+ ObjectVar,
19
+ StringVar,
20
+ TextVar,
21
21
  )
22
+ from nautobot.extras.models import Role
23
+
24
+ CHOICES = (("ff0000", "Red"), ("00ff00", "Green"), ("0000ff", "Blue"))
22
25
 
23
26
 
24
27
  class JobVariablesTest(TestCase):
25
28
  def test_stringvar(self):
29
+ class StringVarJob(Job):
30
+ var1 = StringVar(min_length=3, max_length=3, regex=r"[a-z]+")
31
+
26
32
  # Validate min_length enforcement
27
33
  data = {"var1": "xx"}
28
34
  form = StringVarJob().as_form(data)
@@ -48,6 +54,9 @@ class JobVariablesTest(TestCase):
48
54
  self.assertEqual(form.cleaned_data["var1"], data["var1"])
49
55
 
50
56
  def test_textvar(self):
57
+ class TextVarJob(Job):
58
+ var1 = TextVar()
59
+
51
60
  # Validate valid data
52
61
  data = {"var1": "This is a test string"}
53
62
  form = TextVarJob().as_form(data)
@@ -55,6 +64,9 @@ class JobVariablesTest(TestCase):
55
64
  self.assertEqual(form.cleaned_data["var1"], data["var1"])
56
65
 
57
66
  def test_integervar(self):
67
+ class IntegerVarJob(Job):
68
+ var1 = IntegerVar(min_value=5, max_value=10)
69
+
58
70
  # Validate min_value enforcement
59
71
  data = {"var1": 4}
60
72
  form = IntegerVarJob().as_form(data)
@@ -74,6 +86,9 @@ class JobVariablesTest(TestCase):
74
86
  self.assertEqual(form.cleaned_data["var1"], data["var1"])
75
87
 
76
88
  def test_booleanvar(self):
89
+ class BooleanVarJob(Job):
90
+ var1 = BooleanVar()
91
+
77
92
  # Validate True
78
93
  data = {"var1": True}
79
94
  form = BooleanVarJob().as_form(data)
@@ -87,6 +102,9 @@ class JobVariablesTest(TestCase):
87
102
  self.assertEqual(form.cleaned_data["var1"], False)
88
103
 
89
104
  def test_choicevar(self):
105
+ class ChoiceVarJob(Job):
106
+ var1 = ChoiceVar(choices=CHOICES)
107
+
90
108
  # Validate valid choice
91
109
  data = {"var1": "ff0000"}
92
110
  form = ChoiceVarJob().as_form(data)
@@ -100,6 +118,9 @@ class JobVariablesTest(TestCase):
100
118
  self.assertIn("var1", form.errors)
101
119
 
102
120
  def test_multichoicevar(self):
121
+ class MultiChoiceVarJob(Job):
122
+ var1 = MultiChoiceVar(choices=CHOICES)
123
+
103
124
  # Validate single choice
104
125
  data = {"var1": ["ff0000"]}
105
126
  form = MultiChoiceVarJob().as_form(data)
@@ -119,6 +140,9 @@ class JobVariablesTest(TestCase):
119
140
  self.assertIn("var1", form.errors)
120
141
 
121
142
  def test_objectvar(self):
143
+ class ObjectVarJob(Job):
144
+ var1 = ObjectVar(model=Role)
145
+
122
146
  # Validate valid data
123
147
  data = {"var1": Role.objects.get_for_model(Device).first().pk}
124
148
  form = ObjectVarJob().as_form(data)
@@ -126,6 +150,9 @@ class JobVariablesTest(TestCase):
126
150
  self.assertEqual(form.cleaned_data["var1"].pk, data["var1"])
127
151
 
128
152
  def test_multiobjectvar(self):
153
+ class MultiObjectVarJob(Job):
154
+ var1 = MultiObjectVar(model=Role)
155
+
129
156
  # Validate valid data
130
157
  data = {"var1": [role.pk for role in Role.objects.all()[:3]]}
131
158
  form = MultiObjectVarJob().as_form(data)
@@ -135,6 +162,9 @@ class JobVariablesTest(TestCase):
135
162
  self.assertEqual(form.cleaned_data["var1"][2].pk, data["var1"][2])
136
163
 
137
164
  def test_filevar(self):
165
+ class FileVarJob(Job):
166
+ var1 = FileVar()
167
+
138
168
  # Test file
139
169
  testfile = SimpleUploadedFile(name="test_file.txt", content=b"This is an test file for testing")
140
170
 
@@ -145,6 +175,9 @@ class JobVariablesTest(TestCase):
145
175
  self.assertEqual(form.cleaned_data["var1"], testfile)
146
176
 
147
177
  def test_ipaddressvar(self):
178
+ class IPAddressVarJob(Job):
179
+ var1 = IPAddressVar()
180
+
148
181
  # Validate IP network enforcement
149
182
  data = {"var1": "1.2.3"}
150
183
  form = IPAddressVarJob().as_form(data)
@@ -164,6 +197,9 @@ class JobVariablesTest(TestCase):
164
197
  self.assertEqual(form.cleaned_data["var1"], IPAddress(data["var1"]))
165
198
 
166
199
  def test_ipaddresswithmaskvar(self):
200
+ class IPAddressWithMaskVarJob(Job):
201
+ var1 = IPAddressWithMaskVar()
202
+
167
203
  # Validate IP network enforcement
168
204
  data = {"var1": "1.2.3"}
169
205
  form = IPAddressWithMaskVarJob().as_form(data)
@@ -183,6 +219,9 @@ class JobVariablesTest(TestCase):
183
219
  self.assertEqual(form.cleaned_data["var1"], IPNetwork(data["var1"]))
184
220
 
185
221
  def test_ipnetworkvar(self):
222
+ class IPNetworkVarJob(Job):
223
+ var1 = IPNetworkVar()
224
+
186
225
  # Validate IP network enforcement
187
226
  data = {"var1": "1.2.3"}
188
227
  form = IPNetworkVarJob().as_form(data)
@@ -202,6 +241,9 @@ class JobVariablesTest(TestCase):
202
241
  self.assertEqual(form.cleaned_data["var1"], IPNetwork(data["var1"]))
203
242
 
204
243
  def test_jsonvar(self):
244
+ class JSONVarJob(Job):
245
+ var1 = JSONVar()
246
+
205
247
  # Valid JSON value as dictionary
206
248
  data = {"var1": {"key1": "value1"}}
207
249
  form = JSONVarJob().as_form(data)
@@ -1,6 +1,7 @@
1
1
  import datetime
2
2
  from io import StringIO
3
3
  import json
4
+ import os
4
5
  from pathlib import Path
5
6
  import re
6
7
  import tempfile
@@ -34,7 +35,7 @@ from nautobot.extras.choices import (
34
35
  ObjectChangeEventContextChoices,
35
36
  )
36
37
  from nautobot.extras.context_managers import change_logging, JobHookChangeContext, web_request_context
37
- from nautobot.extras.jobs import get_job
38
+ from nautobot.extras.jobs import get_job, get_jobs
38
39
 
39
40
 
40
41
  class JobTest(TestCase):
@@ -174,6 +175,111 @@ class JobTest(TestCase):
174
175
  self.assertFalse(job_class.supports_dryrun)
175
176
  self.assertFalse(job_model.supports_dryrun)
176
177
 
178
+ def test_submodule_in_jobs_root(self):
179
+ """
180
+ Test that a subdirectory/submodule in JOBS_ROOT can contain Jobs.
181
+ """
182
+ job_class, job_model = get_job_class_and_model("jobs_module.jobs_submodule.jobs", "ChildJob")
183
+ self.assertIsNotNone(job_class)
184
+ self.assertIsNotNone(job_model)
185
+
186
+ def test_relative_import_among_files_in_jobs_root(self):
187
+ """
188
+ Test that a module in JOBS_ROOT can import from other modules in JOBS_ROOT.
189
+ """
190
+ job_class, job_model = get_job_class_and_model("relative_import", "TestReallyPass")
191
+ self.assertIsNotNone(job_class)
192
+ self.assertIsNotNone(job_model)
193
+
194
+ def test_get_jobs_from_jobs_root(self):
195
+ """
196
+ Test that get_jobs() correctly loads jobs from JOBS_ROOT as its contents change.
197
+ """
198
+ try:
199
+ with tempfile.TemporaryDirectory() as temp_dir:
200
+ with override_settings(JOBS_ROOT=temp_dir):
201
+ # Create a new Job and make sure it's discovered correctly
202
+ with open(os.path.join(temp_dir, "my_jobs.py"), "w") as fd:
203
+ fd.write("""\
204
+ from nautobot.apps.jobs import Job, register_jobs
205
+ class MyJob(Job):
206
+ def run(self):
207
+ pass
208
+ register_jobs(MyJob)
209
+ """)
210
+ jobs_data = get_jobs(reload=True)
211
+ self.assertIn("my_jobs.MyJob", jobs_data.keys())
212
+ self.assertIsNotNone(get_job("my_jobs.MyJob"))
213
+ # Also make sure some representative previous JOBS_ROOT jobs aren't still around:
214
+ self.assertNotIn("dry_run.TestDryRun", jobs_data.keys())
215
+ self.assertNotIn("pass.TestPass", jobs_data.keys())
216
+
217
+ # Create a second Job in the same module
218
+ with open(os.path.join(temp_dir, "my_jobs.py"), "a") as fd:
219
+ fd.write("""
220
+ class MyOtherJob(MyJob):
221
+ pass
222
+ register_jobs(MyOtherJob)
223
+ """)
224
+ jobs_data = get_jobs(reload=True)
225
+ self.assertIn("my_jobs.MyJob", jobs_data.keys())
226
+ self.assertIsNotNone(get_job("my_jobs.MyJob"))
227
+ self.assertIn("my_jobs.MyOtherJob", jobs_data.keys())
228
+ self.assertIsNotNone(get_job("my_jobs.MyOtherJob"))
229
+
230
+ # Create a third Job in another module
231
+ with open(os.path.join(temp_dir, "their_jobs.py"), "w") as fd:
232
+ fd.write("""
233
+ from nautobot.apps.jobs import Job, register_jobs
234
+
235
+ class MyJob(Job):
236
+ def run(self):
237
+ pass
238
+ register_jobs(MyJob)
239
+ """)
240
+ jobs_data = get_jobs(reload=True)
241
+ self.assertIn("my_jobs.MyJob", jobs_data.keys())
242
+ self.assertIsNotNone(get_job("my_jobs.MyJob"))
243
+ self.assertIn("my_jobs.MyOtherJob", jobs_data.keys())
244
+ self.assertIsNotNone(get_job("my_jobs.MyOtherJob"))
245
+ self.assertIn("their_jobs.MyJob", jobs_data.keys())
246
+ self.assertIsNotNone(get_job("their_jobs.MyJob"))
247
+ self.assertNotEqual(get_job("my_jobs.MyJob"), get_job("their_jobs.MyJob"))
248
+
249
+ # Delete a module
250
+ os.remove(os.path.join(temp_dir, "their_jobs.py"))
251
+ jobs_data = get_jobs(reload=True)
252
+ self.assertIn("my_jobs.MyJob", jobs_data.keys())
253
+ self.assertIsNotNone(get_job("my_jobs.MyJob"))
254
+ self.assertIn("my_jobs.MyOtherJob", jobs_data.keys())
255
+ self.assertIsNotNone(get_job("my_jobs.MyOtherJob"))
256
+ self.assertNotIn("their_jobs", jobs_data.keys())
257
+ self.assertIsNone(get_job("their_jobs.MyJob"))
258
+
259
+ # Create a module with an inauspicious name
260
+ with open(os.path.join(temp_dir, "traceback.py"), "w") as fd:
261
+ fd.write("""
262
+ from nautobot.apps.jobs import Job, register_jobs
263
+
264
+ class BadJob(Job):
265
+ def run(self):
266
+ raise RuntimeError("You ran a bad job!")
267
+ register_jobs(BadJob)
268
+ """)
269
+ jobs_data = get_jobs(reload=True)
270
+ self.assertIn("my_jobs.MyJob", jobs_data.keys())
271
+ self.assertIsNotNone(get_job("my_jobs.MyJob"))
272
+ self.assertIn("my_jobs.MyOtherJob", jobs_data.keys())
273
+ self.assertIsNotNone(get_job("my_jobs.MyOtherJob"))
274
+ # Since `traceback` conflicts with a system module, it should not get loaded
275
+ self.assertNotIn("traceback.BadJob", jobs_data.keys())
276
+ self.assertIsNone(get_job("traceback.BadJob"))
277
+
278
+ # TODO: testing with subdirectories/submodules under JOBS_ROOT...
279
+ finally:
280
+ # Clean up back to normal behavior
281
+ get_jobs(reload=True)
282
+
177
283
 
178
284
  class JobTransactionTest(TransactionTestCase):
179
285
  """
@@ -216,6 +322,19 @@ class JobTransactionTest(TransactionTestCase):
216
322
  name = "TestPass"
217
323
  job_result = create_job_result_and_run_job(module, name)
218
324
  self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_SUCCESS)
325
+ self.assertEqual(job_result.result, True)
326
+ logs = job_result.job_log_entries
327
+ self.assertGreater(logs.count(), 0)
328
+ try:
329
+ logs.get(message="before_start() was called as expected")
330
+ logs.get(message="Success")
331
+ logs.get(message="on_success() was called as expected")
332
+ logs.get(message="after_return() was called as expected")
333
+ except models.JobLogEntry.DoesNotExist:
334
+ for log in logs.all():
335
+ print(log.message)
336
+ print(job_result.traceback)
337
+ raise
219
338
 
220
339
  def test_job_result_manager_censor_sensitive_variables(self):
221
340
  """
@@ -237,6 +356,18 @@ class JobTransactionTest(TransactionTestCase):
237
356
  name = "TestFail"
238
357
  job_result = create_job_result_and_run_job(module, name)
239
358
  self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_FAILURE)
359
+ logs = job_result.job_log_entries
360
+ self.assertGreater(logs.count(), 0)
361
+ try:
362
+ logs.get(message="before_start() was called as expected")
363
+ logs.get(message="I'm a test job that fails!")
364
+ logs.get(message="on_failure() was called as expected")
365
+ logs.get(message="after_return() was called as expected")
366
+ except models.JobLogEntry.DoesNotExist:
367
+ for log in logs.all():
368
+ print(log.message)
369
+ print(job_result.traceback)
370
+ raise
240
371
 
241
372
  def test_job_fail_with_sanitization(self):
242
373
  """
@@ -876,6 +1007,7 @@ class JobHookReceiverTransactionTest(TransactionTestCase):
876
1007
  test_location = Location.objects.get(name="test_jhr")
877
1008
  oc = get_changes_for_model(test_location).first()
878
1009
  self.assertEqual(oc.change_context, ObjectChangeEventContextChoices.CONTEXT_JOB_HOOK)
1010
+ self.assertIsNotNone(job_result.user)
879
1011
  self.assertEqual(oc.user_id, job_result.user.pk)
880
1012
 
881
1013
  def test_missing_receive_job_hook_method(self):
@@ -933,6 +1065,8 @@ class JobHookTransactionTest(TransactionTestCase): # TODO: BaseModelTestCase mi
933
1065
  module = "job_hook_receiver"
934
1066
  name = "TestJobHookReceiverLog"
935
1067
  self.job_class, self.job_model = get_job_class_and_model(module, name)
1068
+ self.assertIsNotNone(self.job_class)
1069
+ self.assertIsNotNone(self.job_model)
936
1070
  job_hook = models.JobHook(
937
1071
  name="JobHookTest",
938
1072
  type_create=True,
@@ -65,6 +65,7 @@ from nautobot.extras.models import (
65
65
  Webhook,
66
66
  )
67
67
  from nautobot.extras.models.statuses import StatusModel
68
+ from nautobot.extras.registry import registry
68
69
  from nautobot.extras.secrets.exceptions import SecretParametersError, SecretProviderError, SecretValueNotFoundError
69
70
  from nautobot.ipam.models import IPAddress
70
71
  from nautobot.tenancy.models import Tenant
@@ -1079,25 +1080,31 @@ class JobModelTest(ModelTestCases.BaseModelTestCase):
1079
1080
  def test_defaults(self):
1080
1081
  """Verify that defaults for discovered JobModel instances are as expected."""
1081
1082
  for job_model in JobModel.objects.all():
1082
- self.assertTrue(job_model.installed)
1083
- # System jobs should be enabled by default, all others are disabled by default
1084
- if job_model.module_name.startswith("nautobot."):
1085
- self.assertTrue(job_model.enabled)
1086
- else:
1087
- self.assertFalse(job_model.enabled)
1088
- for field_name in JOB_OVERRIDABLE_FIELDS:
1089
- if field_name == "name" and "duplicate_name" in job_model.job_class.__module__:
1090
- pass # name field for test_duplicate_name jobs tested in test_duplicate_job_name below
1091
- else:
1092
- self.assertFalse(
1093
- getattr(job_model, f"{field_name}_override"),
1094
- (field_name, getattr(job_model, field_name), getattr(job_model.job_class, field_name)),
1095
- )
1096
- self.assertEqual(
1097
- getattr(job_model, field_name),
1098
- getattr(job_model.job_class, field_name),
1099
- field_name,
1100
- )
1083
+ with self.subTest(class_path=job_model.class_path):
1084
+ try:
1085
+ self.assertTrue(job_model.installed)
1086
+ # System jobs should be enabled by default, all others are disabled by default
1087
+ if job_model.module_name.startswith("nautobot."):
1088
+ self.assertTrue(job_model.enabled)
1089
+ else:
1090
+ self.assertFalse(job_model.enabled)
1091
+ for field_name in JOB_OVERRIDABLE_FIELDS:
1092
+ if field_name == "name" and "duplicate_name" in job_model.job_class.__module__:
1093
+ pass # name field for test_duplicate_name jobs tested in test_duplicate_job_name below
1094
+ else:
1095
+ self.assertFalse(
1096
+ getattr(job_model, f"{field_name}_override"),
1097
+ (field_name, getattr(job_model, field_name), getattr(job_model.job_class, field_name)),
1098
+ )
1099
+ self.assertEqual(
1100
+ getattr(job_model, field_name),
1101
+ getattr(job_model.job_class, field_name),
1102
+ field_name,
1103
+ )
1104
+ except AssertionError:
1105
+ print(list(JobModel.objects.all()))
1106
+ print(registry["jobs"])
1107
+ raise
1101
1108
 
1102
1109
  def test_duplicate_job_name(self):
1103
1110
  self.assertTrue(JobModel.objects.filter(name="TestDuplicateNameNoMeta").exists())
@@ -9,7 +9,6 @@ from django.urls import NoReverseMatch, reverse
9
9
  import netaddr
10
10
 
11
11
  from nautobot.circuits.models import Circuit, CircuitType, Provider
12
- from nautobot.core.celery import app
13
12
  from nautobot.core.testing import APIViewTestCases, disable_warnings, extract_page_body, TestCase, ViewTestCases
14
13
  from nautobot.dcim.models import Device, DeviceType, Location, LocationType, Manufacturer
15
14
  from nautobot.dcim.tests.test_views import create_test_device
@@ -114,9 +113,8 @@ class AppTest(TestCase):
114
113
  """
115
114
  from example_app.jobs import ExampleJob
116
115
 
117
- self.assertIn(ExampleJob, registry.get("plugin_jobs", []))
116
+ self.assertIn(ExampleJob.class_path, registry.get("jobs", {}))
118
117
  self.assertEqual(ExampleJob, get_job("example_app.jobs.ExampleJob"))
119
- self.assertIn("example_app.jobs.ExampleJob", app.tasks)
120
118
 
121
119
  def test_git_datasource_contents_registration(self):
122
120
  """
@@ -1765,10 +1765,8 @@ class JobTestCase(
1765
1765
  model = Job
1766
1766
 
1767
1767
  def _get_queryset(self):
1768
- """Don't include hidden Jobs, non-installed Jobs, JobHookReceivers or JobButtonReceivers as they won't appear in the UI by default."""
1769
- return self.model.objects.filter(
1770
- installed=True, hidden=False, is_job_hook_receiver=False, is_job_button_receiver=False
1771
- )
1768
+ """Don't include hidden Jobs or non-installed Jobs, as they won't appear in the UI by default."""
1769
+ return self.model.objects.filter(installed=True, hidden=False)
1772
1770
 
1773
1771
  @classmethod
1774
1772
  def setUpTestData(cls):