nautobot 2.4.20__py3-none-any.whl → 2.4.21__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/circuits/templates/circuits/circuit.html +1 -1
- nautobot/circuits/templates/circuits/circuittermination.html +1 -1
- nautobot/circuits/templates/circuits/circuittype.html +1 -1
- nautobot/circuits/templates/circuits/providernetwork.html +1 -1
- nautobot/core/cli/migrate_deprecated_templates.py +200 -0
- nautobot/core/jobs/__init__.py +2 -1
- nautobot/core/jobs/groups.py +31 -1
- nautobot/core/models/tree_queries.py +10 -5
- nautobot/core/signals.py +12 -1
- nautobot/core/templates/components/panel/panel.html +1 -1
- nautobot/core/templates/inc/image_attachments.html +2 -1
- nautobot/core/templatetags/helpers.py +22 -0
- nautobot/core/tests/runner.py +3 -0
- nautobot/core/tests/test_cli.py +40 -0
- nautobot/core/tests/test_forms.py +41 -0
- nautobot/core/tests/test_jobs.py +75 -1
- nautobot/core/tests/test_tree_queries.py +14 -1
- nautobot/core/ui/object_detail.py +41 -5
- nautobot/core/utils/filtering.py +11 -9
- nautobot/core/views/generic.py +3 -3
- nautobot/dcim/models/device_components.py +81 -68
- nautobot/dcim/templates/dcim/device/config.html +1 -1
- nautobot/dcim/templates/dcim/device/consoleports.html +1 -1
- nautobot/dcim/templates/dcim/device/consoleserverports.html +1 -1
- nautobot/dcim/templates/dcim/device/devicebays.html +1 -1
- nautobot/dcim/templates/dcim/device/frontports.html +1 -1
- nautobot/dcim/templates/dcim/device/interfaces.html +1 -1
- nautobot/dcim/templates/dcim/device/inventory.html +1 -1
- nautobot/dcim/templates/dcim/device/lldp_neighbors.html +1 -1
- nautobot/dcim/templates/dcim/device/modulebays.html +1 -1
- nautobot/dcim/templates/dcim/device/poweroutlets.html +1 -1
- nautobot/dcim/templates/dcim/device/powerports.html +1 -1
- nautobot/dcim/templates/dcim/device/rearports.html +1 -1
- nautobot/dcim/templates/dcim/device/status.html +1 -1
- nautobot/dcim/templates/dcim/device/wireless.html +1 -1
- nautobot/dcim/templates/dcim/device.html +1 -1
- nautobot/dcim/templates/dcim/device_interface_delete.html +1 -1
- nautobot/dcim/templates/dcim/devicetype.html +1 -1
- nautobot/dcim/templates/dcim/footer_convert_to_contact_or_team_record.html +14 -0
- nautobot/dcim/templates/dcim/interface_bulk_delete.html +1 -1
- nautobot/dcim/templates/dcim/inventoryitem_bulk_delete.html +1 -1
- nautobot/dcim/templates/dcim/location_retrieve.html +1 -242
- nautobot/dcim/templates/dcim/modulefamily_retrieve.html +1 -1
- nautobot/dcim/templates/dcim/powerfeed.html +1 -1
- nautobot/dcim/templates/dcim/powerpanel.html +1 -1
- nautobot/dcim/templates/dcim/virtualchassis.html +1 -1
- nautobot/dcim/tests/test_models.py +43 -3
- nautobot/dcim/tests/test_views.py +52 -21
- nautobot/dcim/views.py +203 -87
- nautobot/extras/api/views.py +9 -1
- nautobot/extras/filters/customfields.py +9 -3
- nautobot/extras/models/groups.py +42 -5
- nautobot/extras/signals.py +20 -19
- nautobot/extras/tables.py +31 -2
- nautobot/extras/templates/extras/computedfield.html +1 -1
- nautobot/extras/templates/extras/configcontext.html +1 -1
- nautobot/extras/templates/extras/configcontextschema_validation.html +1 -1
- nautobot/extras/templates/extras/customfield.html +1 -1
- nautobot/extras/templates/extras/dynamicgroup_retrieve.html +11 -5
- nautobot/extras/templates/extras/gitrepository_result.html +0 -2
- nautobot/extras/templates/extras/graphqlquery_retrieve.html +1 -96
- nautobot/extras/templates/extras/inc/graphqlquery_execute.html +71 -0
- nautobot/extras/templates/extras/object_dynamicgroups.html +2 -2
- nautobot/extras/templates/extras/secretsgroup.html +1 -1
- nautobot/extras/templates/extras/tag.html +1 -1
- nautobot/extras/tests/integration/test_dynamicgroups.py +5 -1
- nautobot/extras/tests/test_api.py +1 -0
- nautobot/extras/tests/test_changelog.py +28 -0
- nautobot/extras/tests/test_customfields.py +10 -2
- nautobot/extras/tests/test_dynamicgroups.py +37 -1
- nautobot/extras/views.py +49 -19
- nautobot/ipam/signals.py +71 -0
- nautobot/ipam/templates/ipam/prefix_delete.html +1 -1
- nautobot/ipam/templates/ipam/service.html +1 -1
- nautobot/ipam/templates/ipam/vlan.html +1 -1
- nautobot/ipam/templates/ipam/vlan_interfaces.html +1 -1
- nautobot/ipam/templates/ipam/vlan_vminterfaces.html +1 -1
- nautobot/ipam/tests/test_models.py +42 -0
- nautobot/users/templates/users/sessionkey_delete.html +1 -1
- nautobot/users/views.py +2 -2
- nautobot/virtualization/models.py +1 -68
- nautobot/virtualization/templates/virtualization/virtual_machine_vminterface_delete.html +1 -1
- nautobot/virtualization/templates/virtualization/virtualmachine.html +1 -1
- nautobot/virtualization/tests/test_models.py +42 -3
- {nautobot-2.4.20.dist-info → nautobot-2.4.21.dist-info}/METADATA +9 -9
- {nautobot-2.4.20.dist-info → nautobot-2.4.21.dist-info}/RECORD +90 -86
- nautobot-2.4.21.dist-info/entry_points.txt +4 -0
- nautobot-2.4.20.dist-info/entry_points.txt +0 -3
- {nautobot-2.4.20.dist-info → nautobot-2.4.21.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.4.20.dist-info → nautobot-2.4.21.dist-info}/NOTICE +0 -0
- {nautobot-2.4.20.dist-info → nautobot-2.4.21.dist-info}/WHEEL +0 -0
nautobot/users/views.py
CHANGED
|
@@ -339,7 +339,7 @@ class TokenDeleteView(GenericView):
|
|
|
339
339
|
|
|
340
340
|
return render(
|
|
341
341
|
request,
|
|
342
|
-
"generic/
|
|
342
|
+
"generic/object_destroy.html",
|
|
343
343
|
{
|
|
344
344
|
"obj": token,
|
|
345
345
|
"obj_type": token._meta.verbose_name,
|
|
@@ -358,7 +358,7 @@ class TokenDeleteView(GenericView):
|
|
|
358
358
|
|
|
359
359
|
return render(
|
|
360
360
|
request,
|
|
361
|
-
"generic/
|
|
361
|
+
"generic/object_destroy.html",
|
|
362
362
|
{
|
|
363
363
|
"obj": token,
|
|
364
364
|
"obj_type": token._meta.verbose_name,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from django.contrib.contenttypes.models import ContentType
|
|
2
2
|
from django.core.exceptions import ValidationError
|
|
3
|
-
from django.db import models
|
|
3
|
+
from django.db import models
|
|
4
4
|
|
|
5
5
|
from nautobot.core.constants import CHARFIELD_MAX_LENGTH
|
|
6
6
|
from nautobot.core.models import BaseManager
|
|
@@ -400,73 +400,6 @@ class VMInterface(PrimaryModel, BaseInterface):
|
|
|
400
400
|
|
|
401
401
|
return super().to_objectchange(action, related_object=virtual_machine, **kwargs)
|
|
402
402
|
|
|
403
|
-
def add_ip_addresses(
|
|
404
|
-
self,
|
|
405
|
-
ip_addresses,
|
|
406
|
-
is_source=False,
|
|
407
|
-
is_destination=False,
|
|
408
|
-
is_default=False,
|
|
409
|
-
is_preferred=False,
|
|
410
|
-
is_primary=False,
|
|
411
|
-
is_secondary=False,
|
|
412
|
-
is_standby=False,
|
|
413
|
-
):
|
|
414
|
-
"""Add one or more IPAddress instances to this interface's `ip_addresses` many-to-many relationship.
|
|
415
|
-
|
|
416
|
-
Args:
|
|
417
|
-
ip_addresses (:obj:`list` or `IPAddress`): Instance of `nautobot.ipam.models.IPAddress` or list of `IPAddress` instances.
|
|
418
|
-
is_source (bool, optional): Is source address. Defaults to False.
|
|
419
|
-
is_destination (bool, optional): Is destination address. Defaults to False.
|
|
420
|
-
is_default (bool, optional): Is default address. Defaults to False.
|
|
421
|
-
is_preferred (bool, optional): Is preferred address. Defaults to False.
|
|
422
|
-
is_primary (bool, optional): Is primary address. Defaults to False.
|
|
423
|
-
is_secondary (bool, optional): Is secondary address. Defaults to False.
|
|
424
|
-
is_standby (bool, optional): Is standby address. Defaults to False.
|
|
425
|
-
|
|
426
|
-
Returns:
|
|
427
|
-
Number of instances added.
|
|
428
|
-
"""
|
|
429
|
-
if not isinstance(ip_addresses, (tuple, list)):
|
|
430
|
-
ip_addresses = [ip_addresses]
|
|
431
|
-
with transaction.atomic():
|
|
432
|
-
for ip in ip_addresses:
|
|
433
|
-
instance = self.ip_addresses.through(
|
|
434
|
-
ip_address=ip,
|
|
435
|
-
vm_interface=self,
|
|
436
|
-
is_source=is_source,
|
|
437
|
-
is_destination=is_destination,
|
|
438
|
-
is_default=is_default,
|
|
439
|
-
is_preferred=is_preferred,
|
|
440
|
-
is_primary=is_primary,
|
|
441
|
-
is_secondary=is_secondary,
|
|
442
|
-
is_standby=is_standby,
|
|
443
|
-
)
|
|
444
|
-
instance.validated_save()
|
|
445
|
-
return len(ip_addresses)
|
|
446
|
-
|
|
447
|
-
add_ip_addresses.alters_data = True
|
|
448
|
-
|
|
449
|
-
def remove_ip_addresses(self, ip_addresses):
|
|
450
|
-
"""Remove one or more IPAddress instances from this interface's `ip_addresses` many-to-many relationship.
|
|
451
|
-
|
|
452
|
-
Args:
|
|
453
|
-
ip_addresses (:obj:`list` or `IPAddress`): Instance of `nautobot.ipam.models.IPAddress` or list of `IPAddress` instances.
|
|
454
|
-
|
|
455
|
-
Returns:
|
|
456
|
-
Number of instances removed.
|
|
457
|
-
"""
|
|
458
|
-
count = 0
|
|
459
|
-
if not isinstance(ip_addresses, (tuple, list)):
|
|
460
|
-
ip_addresses = [ip_addresses]
|
|
461
|
-
with transaction.atomic():
|
|
462
|
-
for ip in ip_addresses:
|
|
463
|
-
qs = self.ip_addresses.through.objects.filter(ip_address=ip, vm_interface=self)
|
|
464
|
-
deleted_count, _ = qs.delete()
|
|
465
|
-
count += deleted_count
|
|
466
|
-
return count
|
|
467
|
-
|
|
468
|
-
remove_ip_addresses.alters_data = True
|
|
469
|
-
|
|
470
403
|
@property
|
|
471
404
|
def parent(self):
|
|
472
405
|
return self.virtual_machine
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
{% extends '
|
|
1
|
+
{% extends 'generic/object_retrieve.html' %}
|
|
2
2
|
{% comment %}3.0 TODO: remove this template, which only exists for backward compatibility with 2.4 and earlier{% endcomment %}
|
|
@@ -140,6 +140,11 @@ class VMInterfaceTestCase(TestCase): # TODO: change to BaseModelTestCase
|
|
|
140
140
|
self.assertEqual(count, 1)
|
|
141
141
|
self.assertEqual(IPAddressToInterface.objects.filter(ip_address=ips[-1], vm_interface=vm_interface).count(), 1)
|
|
142
142
|
|
|
143
|
+
# add a single instance which is already there
|
|
144
|
+
count = vm_interface.add_ip_addresses(ips[-1])
|
|
145
|
+
self.assertEqual(count, 0)
|
|
146
|
+
self.assertEqual(IPAddressToInterface.objects.filter(ip_address=ips[-1], vm_interface=vm_interface).count(), 1)
|
|
147
|
+
|
|
143
148
|
# add multiple instances
|
|
144
149
|
count = vm_interface.add_ip_addresses(ips[:5])
|
|
145
150
|
self.assertEqual(count, 5)
|
|
@@ -147,6 +152,20 @@ class VMInterfaceTestCase(TestCase): # TODO: change to BaseModelTestCase
|
|
|
147
152
|
for ip in ips[:5]:
|
|
148
153
|
self.assertEqual(IPAddressToInterface.objects.filter(ip_address=ip, vm_interface=vm_interface).count(), 1)
|
|
149
154
|
|
|
155
|
+
# add multiple instances all of which are already there
|
|
156
|
+
count = vm_interface.add_ip_addresses(ips[:5])
|
|
157
|
+
self.assertEqual(count, 0)
|
|
158
|
+
self.assertEqual(IPAddressToInterface.objects.filter(vm_interface=vm_interface).count(), 6)
|
|
159
|
+
for ip in ips[:5]:
|
|
160
|
+
self.assertEqual(IPAddressToInterface.objects.filter(ip_address=ip, vm_interface=vm_interface).count(), 1)
|
|
161
|
+
|
|
162
|
+
# add multiple IPs some of which are there
|
|
163
|
+
count = vm_interface.add_ip_addresses(ips[3:7])
|
|
164
|
+
self.assertEqual(count, 2)
|
|
165
|
+
self.assertEqual(IPAddressToInterface.objects.filter(vm_interface=vm_interface).count(), 8)
|
|
166
|
+
for ip in ips[3:7]:
|
|
167
|
+
self.assertEqual(IPAddressToInterface.objects.filter(ip_address=ip, vm_interface=vm_interface).count(), 1)
|
|
168
|
+
|
|
150
169
|
def test_remove_ip_addresses(self):
|
|
151
170
|
"""Test the `remove_ip_addresses` helper method on `VMInterface`"""
|
|
152
171
|
vm_interface = VMInterface.objects.create(
|
|
@@ -165,13 +184,28 @@ class VMInterfaceTestCase(TestCase): # TODO: change to BaseModelTestCase
|
|
|
165
184
|
self.assertEqual(count, 1)
|
|
166
185
|
self.assertEqual(IPAddressToInterface.objects.filter(vm_interface=vm_interface).count(), 9)
|
|
167
186
|
|
|
187
|
+
# remove a single instance which has already been removed
|
|
188
|
+
count = vm_interface.remove_ip_addresses(ips[-1])
|
|
189
|
+
self.assertEqual(count, 0)
|
|
190
|
+
self.assertEqual(IPAddressToInterface.objects.filter(vm_interface=vm_interface).count(), 9)
|
|
191
|
+
|
|
168
192
|
# remove multiple instances
|
|
169
193
|
count = vm_interface.remove_ip_addresses(ips[:5])
|
|
170
194
|
self.assertEqual(count, 5)
|
|
171
195
|
self.assertEqual(IPAddressToInterface.objects.filter(vm_interface=vm_interface).count(), 4)
|
|
172
196
|
|
|
197
|
+
# remove multiple instances all which have already been removed
|
|
198
|
+
count = vm_interface.remove_ip_addresses(ips[:5])
|
|
199
|
+
self.assertEqual(count, 0)
|
|
200
|
+
self.assertEqual(IPAddressToInterface.objects.filter(vm_interface=vm_interface).count(), 4)
|
|
201
|
+
|
|
202
|
+
# remove multiple instances some of which have already been removed
|
|
203
|
+
count = vm_interface.remove_ip_addresses(ips[3:7])
|
|
204
|
+
self.assertEqual(count, 2)
|
|
205
|
+
self.assertEqual(IPAddressToInterface.objects.filter(vm_interface=vm_interface).count(), 2)
|
|
206
|
+
|
|
173
207
|
count = vm_interface.remove_ip_addresses(ips)
|
|
174
|
-
self.assertEqual(count,
|
|
208
|
+
self.assertEqual(count, 2)
|
|
175
209
|
self.assertEqual(IPAddressToInterface.objects.filter(vm_interface=vm_interface).count(), 0)
|
|
176
210
|
|
|
177
211
|
# Test the pre_delete signal for IPAddressToInterface instances
|
|
@@ -180,9 +214,14 @@ class VMInterfaceTestCase(TestCase): # TODO: change to BaseModelTestCase
|
|
|
180
214
|
self.virtualmachine.primary_ip6 = vm_interface.ip_addresses.all().filter(ip_version=6).first()
|
|
181
215
|
self.virtualmachine.save()
|
|
182
216
|
|
|
183
|
-
vm_interface.remove_ip_addresses(self.virtualmachine.primary_ip4)
|
|
217
|
+
count = vm_interface.remove_ip_addresses(self.virtualmachine.primary_ip4)
|
|
218
|
+
self.assertEqual(count, 1)
|
|
184
219
|
self.virtualmachine.refresh_from_db()
|
|
185
220
|
self.assertEqual(self.virtualmachine.primary_ip4, None)
|
|
186
|
-
|
|
221
|
+
# NOTE: This effectively tests what happens when you pass remove_ip_addresses None; it
|
|
222
|
+
# NOTE: does not remove a v6 address, because there are no v6 IPs created in this test
|
|
223
|
+
# NOTE: class.
|
|
224
|
+
count = vm_interface.remove_ip_addresses(self.virtualmachine.primary_ip6)
|
|
225
|
+
self.assertEqual(count, 0)
|
|
187
226
|
self.virtualmachine.refresh_from_db()
|
|
188
227
|
self.assertEqual(self.virtualmachine.primary_ip6, None)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: nautobot
|
|
3
|
-
Version: 2.4.
|
|
3
|
+
Version: 2.4.21
|
|
4
4
|
Summary: Source of truth and network automation platform.
|
|
5
5
|
License: Apache-2.0
|
|
6
6
|
Keywords: Nautobot
|
|
@@ -24,16 +24,16 @@ Requires-Dist: Django (>=4.2.25,<4.3.0)
|
|
|
24
24
|
Requires-Dist: GitPython (>=3.1.45,<3.2.0)
|
|
25
25
|
Requires-Dist: Jinja2 (>=3.1.6,<3.2.0)
|
|
26
26
|
Requires-Dist: Markdown (>=3.8.2,<3.9.0)
|
|
27
|
-
Requires-Dist: Pillow (>=
|
|
27
|
+
Requires-Dist: Pillow (>=12.0.0,<12.1.0)
|
|
28
28
|
Requires-Dist: PyYAML (>=6.0.3,<6.1.0)
|
|
29
29
|
Requires-Dist: celery (>=5.5.3,<5.6.0)
|
|
30
|
-
Requires-Dist: cryptography (>=
|
|
30
|
+
Requires-Dist: cryptography (>=46.0.3,<46.1.0)
|
|
31
31
|
Requires-Dist: django-ajax-tables (>=1.1.1,<1.2.0)
|
|
32
32
|
Requires-Dist: django-auth-ldap (>=5.2.0,<5.3.0) ; extra == "all" or extra == "ldap"
|
|
33
33
|
Requires-Dist: django-celery-beat (>=2.7.0,<2.8.0)
|
|
34
34
|
Requires-Dist: django-celery-results (>=2.6.0,<2.7.0)
|
|
35
35
|
Requires-Dist: django-constance (>=4.3.2,<4.4.0)
|
|
36
|
-
Requires-Dist: django-cors-headers (>=4.
|
|
36
|
+
Requires-Dist: django-cors-headers (>=4.9.0,<4.10.0)
|
|
37
37
|
Requires-Dist: django-db-file-storage (>=0.5.6.1,<0.6.0.0)
|
|
38
38
|
Requires-Dist: django-extensions (>=4.1,<4.2)
|
|
39
39
|
Requires-Dist: django-filter (>=25.1,<25.2)
|
|
@@ -43,15 +43,15 @@ Requires-Dist: django-prometheus (>=2.4.1,<2.5.0)
|
|
|
43
43
|
Requires-Dist: django-redis (>=6.0.0,<6.1.0)
|
|
44
44
|
Requires-Dist: django-silk (>=5.4.3,<5.5.0)
|
|
45
45
|
Requires-Dist: django-storages (>=1.14.6,<1.15.0) ; extra == "all" or extra == "remote-storage"
|
|
46
|
-
Requires-Dist: django-structlog[celery] (>=
|
|
46
|
+
Requires-Dist: django-structlog[celery] (>=10.0.0,<10.1.0)
|
|
47
47
|
Requires-Dist: django-tables2 (>=2.7.5,<2.8.0)
|
|
48
48
|
Requires-Dist: django-taggit (>=6.1.0,<6.2.0)
|
|
49
49
|
Requires-Dist: django-timezone-field (>=7.1,<7.2)
|
|
50
|
-
Requires-Dist: django-tree-queries (>=0.
|
|
50
|
+
Requires-Dist: django-tree-queries (>=0.21.2,<0.22.0)
|
|
51
51
|
Requires-Dist: django-webserver (>=1.2.0,<1.3.0)
|
|
52
52
|
Requires-Dist: djangorestframework (>=3.16.1,<3.17.0)
|
|
53
53
|
Requires-Dist: drf-spectacular[sidecar] (>=0.28.0,<0.29.0)
|
|
54
|
-
Requires-Dist: emoji (>=2.
|
|
54
|
+
Requires-Dist: emoji (>=2.15.0,<2.16.0)
|
|
55
55
|
Requires-Dist: graphene-django (>=2.16.0,<2.17.0)
|
|
56
56
|
Requires-Dist: graphene-django-optimizer (>=0.8.0,<0.9.0)
|
|
57
57
|
Requires-Dist: jsonschema (>=4.7.0,<5.0.0)
|
|
@@ -62,12 +62,12 @@ Requires-Dist: netaddr (>=1.3.0,<1.4.0)
|
|
|
62
62
|
Requires-Dist: netutils (>=1.14.0,<2.0.0)
|
|
63
63
|
Requires-Dist: nh3 (>=0.3.1,<0.4.0)
|
|
64
64
|
Requires-Dist: packaging (>=23.1)
|
|
65
|
-
Requires-Dist: prometheus-client (>=0.
|
|
65
|
+
Requires-Dist: prometheus-client (>=0.23.1,<0.24.0)
|
|
66
66
|
Requires-Dist: psycopg2-binary (>=2.9.11,<2.10.0)
|
|
67
67
|
Requires-Dist: python-slugify (>=8.0.4,<8.1.0)
|
|
68
68
|
Requires-Dist: pyuwsgi (>=2.0.30,<2.1.0)
|
|
69
69
|
Requires-Dist: social-auth-app-django (>=5.4.3,<5.5.0)
|
|
70
|
-
Requires-Dist: social-auth-core[saml] (>=4.
|
|
70
|
+
Requires-Dist: social-auth-core[saml] (>=4.8.1,<4.9.0) ; extra == "all" or extra == "sso"
|
|
71
71
|
Requires-Dist: svgwrite (>=1.4.3,<1.5.0)
|
|
72
72
|
Project-URL: Documentation, https://docs.nautobot.com
|
|
73
73
|
Project-URL: Homepage, https://nautobot.com
|