nautobot 2.2.1__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.
- nautobot/apps/jobs.py +2 -0
- nautobot/core/api/utils.py +12 -9
- nautobot/core/apps/__init__.py +2 -2
- nautobot/core/celery/__init__.py +79 -68
- nautobot/core/celery/backends.py +9 -1
- nautobot/core/celery/control.py +4 -7
- nautobot/core/celery/schedulers.py +4 -2
- nautobot/core/celery/task.py +78 -5
- nautobot/core/graphql/schema.py +2 -1
- nautobot/core/jobs/__init__.py +2 -1
- nautobot/core/templates/generic/object_list.html +3 -3
- nautobot/core/templatetags/helpers.py +66 -9
- nautobot/core/testing/__init__.py +6 -1
- nautobot/core/testing/api.py +12 -13
- nautobot/core/testing/mixins.py +2 -2
- nautobot/core/testing/views.py +50 -51
- nautobot/core/tests/test_api.py +23 -2
- nautobot/core/tests/test_templatetags_helpers.py +32 -0
- nautobot/core/tests/test_views.py +21 -1
- nautobot/core/tests/test_views_utils.py +22 -1
- nautobot/core/utils/module_loading.py +89 -0
- nautobot/core/views/generic.py +4 -4
- nautobot/core/views/mixins.py +4 -3
- nautobot/core/views/utils.py +3 -2
- nautobot/core/wsgi.py +9 -2
- nautobot/dcim/choices.py +14 -0
- nautobot/dcim/forms.py +59 -4
- nautobot/dcim/models/device_components.py +9 -5
- nautobot/dcim/templates/dcim/device/lldp_neighbors.html +2 -2
- nautobot/dcim/templates/dcim/devicefamily_retrieve.html +1 -1
- nautobot/dcim/templates/dcim/location.html +32 -13
- nautobot/dcim/templates/dcim/location_migrate_data_to_contact.html +102 -0
- nautobot/dcim/tests/test_forms.py +49 -2
- nautobot/dcim/tests/test_views.py +137 -0
- nautobot/dcim/urls.py +5 -0
- nautobot/dcim/views.py +149 -1
- nautobot/extras/api/views.py +21 -10
- nautobot/extras/constants.py +3 -3
- nautobot/extras/context_managers.py +56 -0
- nautobot/extras/datasources/git.py +47 -58
- nautobot/extras/forms/forms.py +3 -1
- nautobot/extras/jobs.py +79 -146
- nautobot/extras/models/datasources.py +0 -2
- nautobot/extras/models/jobs.py +36 -18
- nautobot/extras/plugins/__init__.py +1 -20
- nautobot/extras/signals.py +88 -57
- nautobot/extras/test_jobs/__init__.py +8 -0
- nautobot/extras/test_jobs/dry_run.py +3 -2
- nautobot/extras/test_jobs/fail.py +43 -0
- nautobot/extras/test_jobs/ipaddress_vars.py +40 -1
- nautobot/extras/test_jobs/jobs_module/__init__.py +5 -0
- nautobot/extras/test_jobs/jobs_module/jobs_submodule/__init__.py +1 -0
- nautobot/extras/test_jobs/jobs_module/jobs_submodule/jobs.py +6 -0
- nautobot/extras/test_jobs/pass.py +40 -0
- nautobot/extras/test_jobs/relative_import.py +11 -0
- nautobot/extras/tests/test_api.py +3 -0
- nautobot/extras/tests/test_context_managers.py +98 -1
- nautobot/extras/tests/test_datasources.py +125 -118
- nautobot/extras/tests/test_job_variables.py +57 -15
- nautobot/extras/tests/test_jobs.py +135 -1
- nautobot/extras/tests/test_models.py +26 -19
- nautobot/extras/tests/test_plugins.py +1 -3
- nautobot/extras/tests/test_views.py +2 -4
- nautobot/extras/utils.py +37 -0
- nautobot/extras/views.py +47 -95
- nautobot/ipam/api/views.py +8 -1
- nautobot/ipam/graphql/types.py +11 -0
- nautobot/ipam/mixins.py +32 -0
- nautobot/ipam/models.py +2 -1
- nautobot/ipam/querysets.py +6 -1
- nautobot/ipam/tables.py +1 -1
- nautobot/ipam/tests/test_models.py +82 -0
- nautobot/project-static/docs/assets/extra.css +4 -0
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +180 -211
- nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +1 -1
- nautobot/project-static/docs/development/core/application-registry.html +126 -84
- nautobot/project-static/docs/development/core/model-checklist.html +49 -1
- nautobot/project-static/docs/development/core/model-features.html +1 -1
- nautobot/project-static/docs/development/jobs/index.html +334 -58
- nautobot/project-static/docs/development/jobs/migration/from-v1.html +1 -1
- nautobot/project-static/docs/objects.inv +0 -0
- nautobot/project-static/docs/release-notes/version-1.6.html +504 -201
- nautobot/project-static/docs/release-notes/version-2.2.html +392 -43
- nautobot/project-static/docs/search/search_index.json +1 -1
- nautobot/project-static/docs/sitemap.xml +254 -254
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +7 -4
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +111 -0
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +15 -28
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +4 -4
- nautobot/project-static/js/forms.js +18 -11
- {nautobot-2.2.1.dist-info → nautobot-2.2.3.dist-info}/METADATA +3 -3
- {nautobot-2.2.1.dist-info → nautobot-2.2.3.dist-info}/RECORD +98 -92
- nautobot/extras/test_jobs/job_variables.py +0 -93
- {nautobot-2.2.1.dist-info → nautobot-2.2.3.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.2.1.dist-info → nautobot-2.2.3.dist-info}/NOTICE +0 -0
- {nautobot-2.2.1.dist-info → nautobot-2.2.3.dist-info}/WHEEL +0 -0
- {nautobot-2.2.1.dist-info → nautobot-2.2.3.dist-info}/entry_points.txt +0 -0
nautobot/ipam/models.py
CHANGED
|
@@ -22,7 +22,7 @@ from nautobot.ipam import choices, constants
|
|
|
22
22
|
from nautobot.virtualization.models import VMInterface
|
|
23
23
|
|
|
24
24
|
from .fields import VarbinaryIPField
|
|
25
|
-
from .querysets import IPAddressQuerySet, PrefixQuerySet, RIRQuerySet
|
|
25
|
+
from .querysets import IPAddressQuerySet, PrefixQuerySet, RIRQuerySet, VLANQuerySet
|
|
26
26
|
from .validators import DNSValidator
|
|
27
27
|
|
|
28
28
|
__all__ = (
|
|
@@ -1380,6 +1380,7 @@ class VLAN(PrimaryModel):
|
|
|
1380
1380
|
]
|
|
1381
1381
|
|
|
1382
1382
|
natural_key_field_names = ["pk"]
|
|
1383
|
+
objects = BaseManager.from_queryset(VLANQuerySet)()
|
|
1383
1384
|
|
|
1384
1385
|
class Meta:
|
|
1385
1386
|
ordering = (
|
nautobot/ipam/querysets.py
CHANGED
|
@@ -6,6 +6,7 @@ import netaddr
|
|
|
6
6
|
|
|
7
7
|
from nautobot.core.models.querysets import RestrictedQuerySet
|
|
8
8
|
from nautobot.core.utils.data import merge_dicts_without_collision
|
|
9
|
+
from nautobot.ipam.mixins import LocationToLocationsQuerySetMixin
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
class RIRQuerySet(RestrictedQuerySet):
|
|
@@ -194,7 +195,7 @@ class BaseNetworkQuerySet(RestrictedQuerySet):
|
|
|
194
195
|
return ip, last_ip
|
|
195
196
|
|
|
196
197
|
|
|
197
|
-
class PrefixQuerySet(BaseNetworkQuerySet):
|
|
198
|
+
class PrefixQuerySet(LocationToLocationsQuerySetMixin, BaseNetworkQuerySet):
|
|
198
199
|
"""Queryset for `Prefix` objects."""
|
|
199
200
|
|
|
200
201
|
def net_equals(self, *prefixes):
|
|
@@ -474,3 +475,7 @@ class IPAddressQuerySet(BaseNetworkQuerySet):
|
|
|
474
475
|
q |= Q(pk__in=pk_values)
|
|
475
476
|
|
|
476
477
|
return super().filter(q)
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
class VLANQuerySet(LocationToLocationsQuerySetMixin, RestrictedQuerySet):
|
|
481
|
+
"""Queryset for `VLAN` objects."""
|
nautobot/ipam/tables.py
CHANGED
|
@@ -771,7 +771,7 @@ class InterfaceVLANTable(StatusTableMixin, BaseTable):
|
|
|
771
771
|
tenant = TenantColumn()
|
|
772
772
|
role = tables.TemplateColumn(template_code=VLAN_ROLE_LINK)
|
|
773
773
|
location_count = LinkedCountColumn(
|
|
774
|
-
viewname="dcim:
|
|
774
|
+
viewname="dcim:location_list",
|
|
775
775
|
url_params={"vlans": "pk"},
|
|
776
776
|
verbose_name="Locations",
|
|
777
777
|
)
|
|
@@ -295,6 +295,50 @@ class TestPrefix(ModelTestCases.BaseModelTestCase):
|
|
|
295
295
|
self.child1 = Prefix.objects.create(prefix="101.102.0.0/26", status=self.status, namespace=self.namespace)
|
|
296
296
|
self.child2 = Prefix.objects.create(prefix="101.102.0.64/26", status=self.status, namespace=self.namespace)
|
|
297
297
|
|
|
298
|
+
def test_location_queries(self):
|
|
299
|
+
locations = Location.objects.all()[:4]
|
|
300
|
+
for location in locations:
|
|
301
|
+
location.location_type.content_types.add(ContentType.objects.get_for_model(Prefix))
|
|
302
|
+
for i in range(10):
|
|
303
|
+
pfx = Prefix.objects.create(prefix=f"1.1.1.{4*i}/30", status=self.status, namespace=self.namespace)
|
|
304
|
+
if i > 4:
|
|
305
|
+
pfx.locations.set(locations)
|
|
306
|
+
|
|
307
|
+
with self.subTest("Assert filtering and excluding `location`"):
|
|
308
|
+
self.assertQuerysetEqualAndNotEmpty(
|
|
309
|
+
Prefix.objects.filter(location=locations[0]),
|
|
310
|
+
Prefix.objects.filter(locations__in=[locations[0]]),
|
|
311
|
+
)
|
|
312
|
+
self.assertQuerysetEqualAndNotEmpty(
|
|
313
|
+
Prefix.objects.exclude(location=locations[0]),
|
|
314
|
+
Prefix.objects.exclude(locations__in=[locations[0]]),
|
|
315
|
+
)
|
|
316
|
+
self.assertQuerysetEqualAndNotEmpty(
|
|
317
|
+
Prefix.objects.filter(location__in=[locations[0]]),
|
|
318
|
+
Prefix.objects.filter(locations__in=[locations[0]]),
|
|
319
|
+
)
|
|
320
|
+
self.assertQuerysetEqualAndNotEmpty(
|
|
321
|
+
Prefix.objects.exclude(location__in=[locations[0]]),
|
|
322
|
+
Prefix.objects.exclude(locations__in=[locations[0]]),
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
# We use `assertQuerysetEqualAndNotEmpty` for test validation. Including a nullable field could lead
|
|
326
|
+
# to flaky tests where querysets might return None, causing tests to fail. Therefore, we select
|
|
327
|
+
# fields that consistently contain values to ensure reliable filtering.
|
|
328
|
+
query_params = ["name", "location_type", "status"]
|
|
329
|
+
|
|
330
|
+
for field_name in query_params:
|
|
331
|
+
with self.subTest(f"Assert location__{field_name} query."):
|
|
332
|
+
value = getattr(locations[0], field_name)
|
|
333
|
+
self.assertQuerysetEqualAndNotEmpty(
|
|
334
|
+
Prefix.objects.filter(**{f"location__{field_name}": value}),
|
|
335
|
+
Prefix.objects.filter(**{f"locations__{field_name}": value}),
|
|
336
|
+
)
|
|
337
|
+
self.assertQuerysetEqualAndNotEmpty(
|
|
338
|
+
Prefix.objects.exclude(**{f"location__{field_name}": value}),
|
|
339
|
+
Prefix.objects.exclude(**{f"locations__{field_name}": value}),
|
|
340
|
+
)
|
|
341
|
+
|
|
298
342
|
def test_prefix_validation(self):
|
|
299
343
|
location_type = LocationType.objects.get(name="Room")
|
|
300
344
|
location = Location.objects.filter(location_type=location_type).first()
|
|
@@ -1201,6 +1245,44 @@ class TestVLAN(ModelTestCases.BaseModelTestCase):
|
|
|
1201
1245
|
location.vlans.add(vlan)
|
|
1202
1246
|
self.assertIn(f"{location} is a Floor and may not have VLANs associated to it.", str(cm.exception))
|
|
1203
1247
|
|
|
1248
|
+
def test_location_queries(self):
|
|
1249
|
+
location = VLAN.objects.filter(locations__isnull=False).first().locations.first()
|
|
1250
|
+
|
|
1251
|
+
with self.subTest("Assert filtering and excluding `location`"):
|
|
1252
|
+
self.assertQuerysetEqualAndNotEmpty(
|
|
1253
|
+
VLAN.objects.filter(location=location),
|
|
1254
|
+
VLAN.objects.filter(locations__in=[location]),
|
|
1255
|
+
)
|
|
1256
|
+
self.assertQuerysetEqualAndNotEmpty(
|
|
1257
|
+
VLAN.objects.exclude(location=location),
|
|
1258
|
+
VLAN.objects.exclude(locations__in=[location]),
|
|
1259
|
+
)
|
|
1260
|
+
self.assertQuerysetEqualAndNotEmpty(
|
|
1261
|
+
VLAN.objects.filter(location__in=[location]),
|
|
1262
|
+
VLAN.objects.filter(locations__in=[location]),
|
|
1263
|
+
)
|
|
1264
|
+
self.assertQuerysetEqualAndNotEmpty(
|
|
1265
|
+
VLAN.objects.exclude(location__in=[location]),
|
|
1266
|
+
VLAN.objects.exclude(locations__in=[location]),
|
|
1267
|
+
)
|
|
1268
|
+
|
|
1269
|
+
# We use `assertQuerysetEqualAndNotEmpty` for test validation. Including a nullable field could lead
|
|
1270
|
+
# to flaky tests where querysets might return None, causing tests to fail. Therefore, we select
|
|
1271
|
+
# fields that consistently contain values to ensure reliable filtering.
|
|
1272
|
+
query_params = ["name", "location_type", "status"]
|
|
1273
|
+
|
|
1274
|
+
for field_name in query_params:
|
|
1275
|
+
with self.subTest(f"Assert location__{field_name} query."):
|
|
1276
|
+
value = getattr(location, field_name)
|
|
1277
|
+
self.assertQuerysetEqualAndNotEmpty(
|
|
1278
|
+
VLAN.objects.filter(**{f"location__{field_name}": value}),
|
|
1279
|
+
VLAN.objects.filter(**{f"locations__{field_name}": value}),
|
|
1280
|
+
)
|
|
1281
|
+
self.assertQuerysetEqualAndNotEmpty(
|
|
1282
|
+
VLAN.objects.exclude(**{f"location__{field_name}": value}),
|
|
1283
|
+
VLAN.objects.exclude(**{f"locations__{field_name}": value}),
|
|
1284
|
+
)
|
|
1285
|
+
|
|
1204
1286
|
|
|
1205
1287
|
class TestVRF(ModelTestCases.BaseModelTestCase):
|
|
1206
1288
|
model = VRF
|
|
@@ -11918,7 +11918,7 @@ data any serializer fields that do not correspond to a specific model field</p>
|
|
|
11918
11918
|
|
|
11919
11919
|
|
|
11920
11920
|
<h2 id="nautobot.apps.api.get_view_name" class="doc doc-heading">
|
|
11921
|
-
<code class="highlight language-python"><span class="n">nautobot</span><span class="o">.</span><span class="n">apps</span><span class="o">.</span><span class="n">api</span><span class="o">.</span><span class="n">get_view_name</span><span class="p">(</span><span class="n">view</span><span class="p"
|
|
11921
|
+
<code class="highlight language-python"><span class="n">nautobot</span><span class="o">.</span><span class="n">apps</span><span class="o">.</span><span class="n">api</span><span class="o">.</span><span class="n">get_view_name</span><span class="p">(</span><span class="n">view</span><span class="p">)</span></code>
|
|
11922
11922
|
|
|
11923
11923
|
<a href="#nautobot.apps.api.get_view_name" class="headerlink" title="Permanent link">¶</a></h2>
|
|
11924
11924
|
|