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.
Files changed (91) hide show
  1. nautobot/circuits/templates/circuits/circuit.html +1 -1
  2. nautobot/circuits/templates/circuits/circuittermination.html +1 -1
  3. nautobot/circuits/templates/circuits/circuittype.html +1 -1
  4. nautobot/circuits/templates/circuits/providernetwork.html +1 -1
  5. nautobot/core/cli/migrate_deprecated_templates.py +200 -0
  6. nautobot/core/jobs/__init__.py +2 -1
  7. nautobot/core/jobs/groups.py +31 -1
  8. nautobot/core/models/tree_queries.py +10 -5
  9. nautobot/core/signals.py +12 -1
  10. nautobot/core/templates/components/panel/panel.html +1 -1
  11. nautobot/core/templates/inc/image_attachments.html +2 -1
  12. nautobot/core/templatetags/helpers.py +22 -0
  13. nautobot/core/tests/runner.py +3 -0
  14. nautobot/core/tests/test_cli.py +40 -0
  15. nautobot/core/tests/test_forms.py +41 -0
  16. nautobot/core/tests/test_jobs.py +75 -1
  17. nautobot/core/tests/test_tree_queries.py +14 -1
  18. nautobot/core/ui/object_detail.py +41 -5
  19. nautobot/core/utils/filtering.py +11 -9
  20. nautobot/core/views/generic.py +3 -3
  21. nautobot/dcim/models/device_components.py +81 -68
  22. nautobot/dcim/templates/dcim/device/config.html +1 -1
  23. nautobot/dcim/templates/dcim/device/consoleports.html +1 -1
  24. nautobot/dcim/templates/dcim/device/consoleserverports.html +1 -1
  25. nautobot/dcim/templates/dcim/device/devicebays.html +1 -1
  26. nautobot/dcim/templates/dcim/device/frontports.html +1 -1
  27. nautobot/dcim/templates/dcim/device/interfaces.html +1 -1
  28. nautobot/dcim/templates/dcim/device/inventory.html +1 -1
  29. nautobot/dcim/templates/dcim/device/lldp_neighbors.html +1 -1
  30. nautobot/dcim/templates/dcim/device/modulebays.html +1 -1
  31. nautobot/dcim/templates/dcim/device/poweroutlets.html +1 -1
  32. nautobot/dcim/templates/dcim/device/powerports.html +1 -1
  33. nautobot/dcim/templates/dcim/device/rearports.html +1 -1
  34. nautobot/dcim/templates/dcim/device/status.html +1 -1
  35. nautobot/dcim/templates/dcim/device/wireless.html +1 -1
  36. nautobot/dcim/templates/dcim/device.html +1 -1
  37. nautobot/dcim/templates/dcim/device_interface_delete.html +1 -1
  38. nautobot/dcim/templates/dcim/devicetype.html +1 -1
  39. nautobot/dcim/templates/dcim/footer_convert_to_contact_or_team_record.html +14 -0
  40. nautobot/dcim/templates/dcim/interface_bulk_delete.html +1 -1
  41. nautobot/dcim/templates/dcim/inventoryitem_bulk_delete.html +1 -1
  42. nautobot/dcim/templates/dcim/location_retrieve.html +1 -242
  43. nautobot/dcim/templates/dcim/modulefamily_retrieve.html +1 -1
  44. nautobot/dcim/templates/dcim/powerfeed.html +1 -1
  45. nautobot/dcim/templates/dcim/powerpanel.html +1 -1
  46. nautobot/dcim/templates/dcim/virtualchassis.html +1 -1
  47. nautobot/dcim/tests/test_models.py +43 -3
  48. nautobot/dcim/tests/test_views.py +52 -21
  49. nautobot/dcim/views.py +203 -87
  50. nautobot/extras/api/views.py +9 -1
  51. nautobot/extras/filters/customfields.py +9 -3
  52. nautobot/extras/models/groups.py +42 -5
  53. nautobot/extras/signals.py +20 -19
  54. nautobot/extras/tables.py +31 -2
  55. nautobot/extras/templates/extras/computedfield.html +1 -1
  56. nautobot/extras/templates/extras/configcontext.html +1 -1
  57. nautobot/extras/templates/extras/configcontextschema_validation.html +1 -1
  58. nautobot/extras/templates/extras/customfield.html +1 -1
  59. nautobot/extras/templates/extras/dynamicgroup_retrieve.html +11 -5
  60. nautobot/extras/templates/extras/gitrepository_result.html +0 -2
  61. nautobot/extras/templates/extras/graphqlquery_retrieve.html +1 -96
  62. nautobot/extras/templates/extras/inc/graphqlquery_execute.html +71 -0
  63. nautobot/extras/templates/extras/object_dynamicgroups.html +2 -2
  64. nautobot/extras/templates/extras/secretsgroup.html +1 -1
  65. nautobot/extras/templates/extras/tag.html +1 -1
  66. nautobot/extras/tests/integration/test_dynamicgroups.py +5 -1
  67. nautobot/extras/tests/test_api.py +1 -0
  68. nautobot/extras/tests/test_changelog.py +28 -0
  69. nautobot/extras/tests/test_customfields.py +10 -2
  70. nautobot/extras/tests/test_dynamicgroups.py +37 -1
  71. nautobot/extras/views.py +49 -19
  72. nautobot/ipam/signals.py +71 -0
  73. nautobot/ipam/templates/ipam/prefix_delete.html +1 -1
  74. nautobot/ipam/templates/ipam/service.html +1 -1
  75. nautobot/ipam/templates/ipam/vlan.html +1 -1
  76. nautobot/ipam/templates/ipam/vlan_interfaces.html +1 -1
  77. nautobot/ipam/templates/ipam/vlan_vminterfaces.html +1 -1
  78. nautobot/ipam/tests/test_models.py +42 -0
  79. nautobot/users/templates/users/sessionkey_delete.html +1 -1
  80. nautobot/users/views.py +2 -2
  81. nautobot/virtualization/models.py +1 -68
  82. nautobot/virtualization/templates/virtualization/virtual_machine_vminterface_delete.html +1 -1
  83. nautobot/virtualization/templates/virtualization/virtualmachine.html +1 -1
  84. nautobot/virtualization/tests/test_models.py +42 -3
  85. {nautobot-2.4.20.dist-info → nautobot-2.4.21.dist-info}/METADATA +9 -9
  86. {nautobot-2.4.20.dist-info → nautobot-2.4.21.dist-info}/RECORD +90 -86
  87. nautobot-2.4.21.dist-info/entry_points.txt +4 -0
  88. nautobot-2.4.20.dist-info/entry_points.txt +0 -3
  89. {nautobot-2.4.20.dist-info → nautobot-2.4.21.dist-info}/LICENSE.txt +0 -0
  90. {nautobot-2.4.20.dist-info → nautobot-2.4.21.dist-info}/NOTICE +0 -0
  91. {nautobot-2.4.20.dist-info → nautobot-2.4.21.dist-info}/WHEEL +0 -0
@@ -1,4 +1,4 @@
1
- {% extends 'generic/object_delete.html' %}
1
+ {% extends 'generic/object_destroy.html' %}
2
2
 
3
3
  {% block message %}
4
4
  <p>Are you sure you want to delete your session key?</p>
nautobot/users/views.py CHANGED
@@ -339,7 +339,7 @@ class TokenDeleteView(GenericView):
339
339
 
340
340
  return render(
341
341
  request,
342
- "generic/object_delete.html",
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/object_delete.html",
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, transaction
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,3 +1,3 @@
1
- {% extends 'generic/object_delete.html' %}
1
+ {% extends 'generic/object_destroy.html' %}
2
2
 
3
3
  {% block message_extra %}This would also delete any/all child VMInterfaces of this VMInterface.{% endblock %}
@@ -1,2 +1,2 @@
1
- {% extends 'virtualization/virtualmachine_retrieve.html' %}
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, 4)
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
- vm_interface.remove_ip_addresses(self.virtualmachine.primary_ip6)
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.20
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 (>=11.0,<12.0)
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 (>=45.0.7,<45.1.0)
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.7.0,<4.8.0)
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] (>=9.1.1,<10.0.0)
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.20.0,<0.21.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.14.1,<2.15.0)
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.22.1,<0.23.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.7.0,<4.8.0) ; extra == "all" or extra == "sso"
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