diff --git a/docs/models/dcim/device.md b/docs/models/dcim/device.md index 8f97b920b..2216e351c 100644 --- a/docs/models/dcim/device.md +++ b/docs/models/dcim/device.md @@ -61,6 +61,10 @@ If installed in a rack, this field indicates the base rack unit in which the dev !!! tip Devices with a height of more than one rack unit should be set to the lowest-numbered rack unit that they occupy. +### Latitude & Longitude + +GPS coordinates of the device for geolocation. + ### Status The device's operational status. diff --git a/docs/plugins/development/forms.md b/docs/plugins/development/forms.md index 51f6c70de..31751855e 100644 --- a/docs/plugins/development/forms.md +++ b/docs/plugins/development/forms.md @@ -165,19 +165,6 @@ In addition to the [form fields provided by Django](https://docs.djangoproject.c options: members: false -## Choice Fields - -!!! warning "Obsolete Fields" - NetBox's custom `ChoiceField` and `MultipleChoiceField` classes are no longer necessary thanks to improvements made to the user interface. Django's native form fields can be used instead. These custom field classes will be removed in NetBox v3.6. - -::: utilities.forms.fields.ChoiceField - options: - members: false - -::: utilities.forms.fields.MultipleChoiceField - options: - members: false - ## Dynamic Object Fields ::: utilities.forms.fields.DynamicModelChoiceField diff --git a/docs/release-notes/version-3.6.md b/docs/release-notes/version-3.6.md new file mode 100644 index 000000000..c4e6847d3 --- /dev/null +++ b/docs/release-notes/version-3.6.md @@ -0,0 +1,12 @@ +# NetBox v3.6 + +## v3.6.0 (FUTURE) + +### Breaking Changes + +* The `napalm_driver` and `napalm_args` fields (which were deprecated in v3.5) have been removed from the platform model. + +### Other Changes + +* [#11766](https://github.com/netbox-community/netbox/issues/11766) - Remove obsolete custom `ChoiceField` and `MultipleChoiceField` classes +* [#12320](https://github.com/netbox-community/netbox/issues/12320) - Remove obsolete fields `napalm_driver` and `napalm_args` from Platform diff --git a/mkdocs.yml b/mkdocs.yml index f7da976c3..6be33d592 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -273,6 +273,7 @@ nav: - git Cheat Sheet: 'development/git-cheat-sheet.md' - Release Notes: - Summary: 'release-notes/index.md' + - Version 3.6: 'release-notes/version-3.6.md' - Version 3.5: 'release-notes/version-3.5.md' - Version 3.4: 'release-notes/version-3.4.md' - Version 3.3: 'release-notes/version-3.3.md' diff --git a/netbox/core/models/data.py b/netbox/core/models/data.py index c58ef45f9..efde7158d 100644 --- a/netbox/core/models/data.py +++ b/netbox/core/models/data.py @@ -200,6 +200,7 @@ class DataSource(JobsMixin, PrimaryModel): # Emit the post_sync signal post_sync.send(sender=self.__class__, instance=self) + sync.alters_data = True def _walk(self, root): """ diff --git a/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py index 3f6d55da7..3a3065acc 100644 --- a/netbox/dcim/api/serializers.py +++ b/netbox/dcim/api/serializers.py @@ -635,8 +635,8 @@ class PlatformSerializer(NetBoxModelSerializer): class Meta: model = Platform fields = [ - 'id', 'url', 'display', 'name', 'slug', 'manufacturer', 'config_template', 'napalm_driver', 'napalm_args', - 'description', 'tags', 'custom_fields', 'created', 'last_updated', 'device_count', 'virtualmachine_count', + 'id', 'url', 'display', 'name', 'slug', 'manufacturer', 'config_template', 'description', 'tags', + 'custom_fields', 'created', 'last_updated', 'device_count', 'virtualmachine_count', ] @@ -673,9 +673,10 @@ class DeviceSerializer(NetBoxModelSerializer): model = Device fields = [ 'id', 'url', 'display', 'name', 'device_type', 'device_role', 'tenant', 'platform', 'serial', 'asset_tag', - 'site', 'location', 'rack', 'position', 'face', 'parent_device', 'status', 'airflow', 'primary_ip', - 'primary_ip4', 'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'description', - 'comments', 'config_template', 'local_context_data', 'tags', 'custom_fields', 'created', 'last_updated', + 'site', 'location', 'rack', 'position', 'face', 'latitude', 'longitude', 'parent_device', 'status', 'airflow', + 'primary_ip', 'primary_ip4', 'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', + 'description', 'comments', 'config_template', 'local_context_data', 'tags', 'custom_fields', 'created', + 'last_updated', ] @extend_schema_field(NestedDeviceSerializer) diff --git a/netbox/dcim/filtersets.py b/netbox/dcim/filtersets.py index e784be8e8..e87a37847 100644 --- a/netbox/dcim/filtersets.py +++ b/netbox/dcim/filtersets.py @@ -811,7 +811,7 @@ class PlatformFilterSet(OrganizationalModelFilterSet): class Meta: model = Platform - fields = ['id', 'name', 'slug', 'napalm_driver', 'description'] + fields = ['id', 'name', 'slug', 'description'] class DeviceFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilterSet, LocalConfigContextFilterSet): @@ -999,7 +999,7 @@ class DeviceFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilter class Meta: model = Device - fields = ['id', 'asset_tag', 'face', 'position', 'airflow', 'vc_position', 'vc_priority'] + fields = ['id', 'asset_tag', 'face', 'position', 'latitude', 'longitude', 'airflow', 'vc_position', 'vc_priority'] def search(self, queryset, name, value): if not value.strip(): diff --git a/netbox/dcim/forms/bulk_edit.py b/netbox/dcim/forms/bulk_edit.py index bc9693afb..11cfd685d 100644 --- a/netbox/dcim/forms/bulk_edit.py +++ b/netbox/dcim/forms/bulk_edit.py @@ -472,10 +472,6 @@ class PlatformBulkEditForm(NetBoxModelBulkEditForm): queryset=Manufacturer.objects.all(), required=False ) - napalm_driver = forms.CharField( - max_length=50, - required=False - ) config_template = DynamicModelChoiceField( queryset=ConfigTemplate.objects.all(), required=False @@ -487,9 +483,9 @@ class PlatformBulkEditForm(NetBoxModelBulkEditForm): model = Platform fieldsets = ( - (None, ('manufacturer', 'config_template', 'napalm_driver', 'description')), + (None, ('manufacturer', 'config_template', 'description')), ) - nullable_fields = ('manufacturer', 'config_template', 'napalm_driver', 'description') + nullable_fields = ('manufacturer', 'config_template', 'description') class DeviceBulkEditForm(NetBoxModelBulkEditForm): diff --git a/netbox/dcim/forms/bulk_import.py b/netbox/dcim/forms/bulk_import.py index de7575acb..e3e97ab73 100644 --- a/netbox/dcim/forms/bulk_import.py +++ b/netbox/dcim/forms/bulk_import.py @@ -365,7 +365,7 @@ class PlatformImportForm(NetBoxModelImportForm): class Meta: model = Platform fields = ( - 'name', 'slug', 'manufacturer', 'config_template', 'napalm_driver', 'napalm_args', 'description', 'tags', + 'name', 'slug', 'manufacturer', 'config_template', 'description', 'tags', ) @@ -478,8 +478,9 @@ class DeviceImportForm(BaseDeviceImportForm): class Meta(BaseDeviceImportForm.Meta): fields = [ 'name', 'device_role', 'tenant', 'manufacturer', 'device_type', 'platform', 'serial', 'asset_tag', 'status', - 'site', 'location', 'rack', 'position', 'face', 'parent', 'device_bay', 'airflow', 'virtual_chassis', - 'vc_position', 'vc_priority', 'cluster', 'description', 'config_template', 'comments', 'tags', + 'site', 'location', 'rack', 'position', 'face', 'latitude', 'longitude', 'parent', 'device_bay', 'airflow', + 'virtual_chassis', 'vc_position', 'vc_priority', 'cluster', 'description', 'config_template', 'comments', + 'tags', ] def __init__(self, data=None, *args, **kwargs): diff --git a/netbox/dcim/forms/model_forms.py b/netbox/dcim/forms/model_forms.py index 219216045..56542d70c 100644 --- a/netbox/dcim/forms/model_forms.py +++ b/netbox/dcim/forms/model_forms.py @@ -360,19 +360,14 @@ class PlatformForm(NetBoxModelForm): ) fieldsets = ( - ('Platform', ( - 'name', 'slug', 'manufacturer', 'config_template', 'napalm_driver', 'napalm_args', 'description', 'tags', - )), + ('Platform', ('name', 'slug', 'manufacturer', 'config_template', 'description', 'tags')), ) class Meta: model = Platform fields = [ - 'name', 'slug', 'manufacturer', 'config_template', 'napalm_driver', 'napalm_args', 'description', 'tags', + 'name', 'slug', 'manufacturer', 'config_template', 'description', 'tags', ] - widgets = { - 'napalm_args': forms.Textarea(), - } class DeviceForm(TenancyForm, NetBoxModelForm): @@ -454,9 +449,9 @@ class DeviceForm(TenancyForm, NetBoxModelForm): model = Device fields = [ 'name', 'device_role', 'device_type', 'serial', 'asset_tag', 'site', 'rack', 'location', 'position', 'face', - 'status', 'airflow', 'platform', 'primary_ip4', 'primary_ip6', 'cluster', 'tenant_group', 'tenant', - 'virtual_chassis', 'vc_position', 'vc_priority', 'description', 'config_template', 'comments', 'tags', - 'local_context_data' + 'latitude', 'longitude', 'status', 'airflow', 'platform', 'primary_ip4', 'primary_ip6', 'cluster', + 'tenant_group', 'tenant', 'virtual_chassis', 'vc_position', 'vc_priority', 'description', 'config_template', + 'comments', 'tags', 'local_context_data' ] def __init__(self, *args, **kwargs): diff --git a/netbox/dcim/migrations/0173_remove_napalm_fields.py b/netbox/dcim/migrations/0173_remove_napalm_fields.py new file mode 100644 index 000000000..61c7c5695 --- /dev/null +++ b/netbox/dcim/migrations/0173_remove_napalm_fields.py @@ -0,0 +1,19 @@ +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('dcim', '0172_larger_power_draw_values'), + ] + + operations = [ + migrations.RemoveField( + model_name='platform', + name='napalm_args', + ), + migrations.RemoveField( + model_name='platform', + name='napalm_driver', + ), + ] diff --git a/netbox/dcim/migrations/0174_device_latitude_device_longitude.py b/netbox/dcim/migrations/0174_device_latitude_device_longitude.py new file mode 100644 index 000000000..f9f72f9f8 --- /dev/null +++ b/netbox/dcim/migrations/0174_device_latitude_device_longitude.py @@ -0,0 +1,22 @@ +# Generated by Django 4.1.9 on 2023-05-31 22:13 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ('dcim', '0173_remove_napalm_fields'), + ] + + operations = [ + migrations.AddField( + model_name='device', + name='latitude', + field=models.DecimalField(blank=True, decimal_places=6, max_digits=8, null=True), + ), + migrations.AddField( + model_name='device', + name='longitude', + field=models.DecimalField(blank=True, decimal_places=6, max_digits=9, null=True), + ), + ] diff --git a/netbox/dcim/models/cables.py b/netbox/dcim/models/cables.py index af69c440e..b2786719c 100644 --- a/netbox/dcim/models/cables.py +++ b/netbox/dcim/models/cables.py @@ -359,6 +359,7 @@ class CableTermination(ChangeLoggedModel): # Circuit terminations elif getattr(self.termination, 'site', None): self._site = self.termination.site + cache_related_objects.alters_data = True def to_objectchange(self, action): objectchange = super().to_objectchange(action) @@ -637,6 +638,7 @@ class CablePath(models.Model): self.save() else: self.delete() + retrace.alters_data = True def _get_path(self): """ diff --git a/netbox/dcim/models/device_component_templates.py b/netbox/dcim/models/device_component_templates.py index 6a89655b2..0355d7028 100644 --- a/netbox/dcim/models/device_component_templates.py +++ b/netbox/dcim/models/device_component_templates.py @@ -213,6 +213,7 @@ class ConsoleServerPortTemplate(ModularComponentTemplateModel): type=self.type, **kwargs ) + instantiate.do_not_call_in_templates = True def to_yaml(self): return { @@ -256,6 +257,7 @@ class PowerPortTemplate(ModularComponentTemplateModel): allocated_draw=self.allocated_draw, **kwargs ) + instantiate.do_not_call_in_templates = True def clean(self): super().clean() @@ -330,6 +332,7 @@ class PowerOutletTemplate(ModularComponentTemplateModel): feed_leg=self.feed_leg, **kwargs ) + instantiate.do_not_call_in_templates = True def to_yaml(self): return { @@ -413,6 +416,7 @@ class InterfaceTemplate(ModularComponentTemplateModel): poe_type=self.poe_type, **kwargs ) + instantiate.do_not_call_in_templates = True def to_yaml(self): return { @@ -507,6 +511,7 @@ class FrontPortTemplate(ModularComponentTemplateModel): rear_port_position=self.rear_port_position, **kwargs ) + instantiate.do_not_call_in_templates = True def to_yaml(self): return { @@ -550,6 +555,7 @@ class RearPortTemplate(ModularComponentTemplateModel): positions=self.positions, **kwargs ) + instantiate.do_not_call_in_templates = True def to_yaml(self): return { @@ -581,6 +587,7 @@ class ModuleBayTemplate(ComponentTemplateModel): label=self.label, position=self.position ) + instantiate.do_not_call_in_templates = True def to_yaml(self): return { @@ -603,6 +610,7 @@ class DeviceBayTemplate(ComponentTemplateModel): name=self.name, label=self.label ) + instantiate.do_not_call_in_templates = True def clean(self): if self.device_type and self.device_type.subdevice_role != SubdeviceRoleChoices.ROLE_PARENT: @@ -696,3 +704,4 @@ class InventoryItemTemplate(MPTTModel, ComponentTemplateModel): part_id=self.part_id, **kwargs ) + instantiate.do_not_call_in_templates = True diff --git a/netbox/dcim/models/devices.py b/netbox/dcim/models/devices.py index 85a5d6870..30fafef94 100644 --- a/netbox/dcim/models/devices.py +++ b/netbox/dcim/models/devices.py @@ -432,9 +432,8 @@ class DeviceRole(OrganizationalModel): class Platform(OrganizationalModel): """ - Platform refers to the software or firmware running on a Device. For example, "Cisco IOS-XR" or "Juniper Junos". - NetBox uses Platforms to determine how to interact with devices when pulling inventory data or other information by - specifying a NAPALM driver. + Platform refers to the software or firmware running on a Device. For example, "Cisco IOS-XR" or "Juniper Junos". A + Platform may optionally be associated with a particular Manufacturer. """ manufacturer = models.ForeignKey( to='dcim.Manufacturer', @@ -451,18 +450,6 @@ class Platform(OrganizationalModel): blank=True, null=True ) - napalm_driver = models.CharField( - max_length=50, - blank=True, - verbose_name='NAPALM driver', - help_text=_('The name of the NAPALM driver to use when interacting with devices') - ) - napalm_args = models.JSONField( - blank=True, - null=True, - verbose_name='NAPALM arguments', - help_text=_('Additional arguments to pass when initiating the NAPALM driver (JSON format)') - ) def get_absolute_url(self): return reverse('dcim:platform', args=[self.pk]) @@ -637,6 +624,20 @@ class Device(PrimaryModel, ConfigContextModel): blank=True, null=True ) + latitude = models.DecimalField( + max_digits=8, + decimal_places=6, + blank=True, + null=True, + help_text=_("GPS coordinate in decimal format (xx.yyyyyy)") + ) + longitude = models.DecimalField( + max_digits=9, + decimal_places=6, + blank=True, + null=True, + help_text=_("GPS coordinate in decimal format (xx.yyyyyy)") + ) # Generic relations contacts = GenericRelation( diff --git a/netbox/dcim/search.py b/netbox/dcim/search.py index bae4f030f..f70c729f4 100644 --- a/netbox/dcim/search.py +++ b/netbox/dcim/search.py @@ -172,7 +172,6 @@ class PlatformIndex(SearchIndex): fields = ( ('name', 100), ('slug', 110), - ('napalm_driver', 300), ('description', 500), ) diff --git a/netbox/dcim/tables/devices.py b/netbox/dcim/tables/devices.py index db2655d27..a5862da68 100644 --- a/netbox/dcim/tables/devices.py +++ b/netbox/dcim/tables/devices.py @@ -137,11 +137,11 @@ class PlatformTable(NetBoxTable): class Meta(NetBoxTable.Meta): model = models.Platform fields = ( - 'pk', 'id', 'name', 'manufacturer', 'device_count', 'vm_count', 'slug', 'config_template', 'napalm_driver', - 'napalm_args', 'description', 'tags', 'actions', 'created', 'last_updated', + 'pk', 'id', 'name', 'manufacturer', 'device_count', 'vm_count', 'slug', 'config_template', 'description', + 'tags', 'actions', 'created', 'last_updated', ) default_columns = ( - 'pk', 'name', 'manufacturer', 'device_count', 'vm_count', 'napalm_driver', 'description', + 'pk', 'name', 'manufacturer', 'device_count', 'vm_count', 'description', ) @@ -236,9 +236,9 @@ class DeviceTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable): fields = ( 'pk', 'id', 'name', 'status', 'tenant', 'tenant_group', 'device_role', 'manufacturer', 'device_type', 'platform', 'serial', 'asset_tag', 'region', 'site_group', 'site', 'location', 'rack', 'parent_device', - 'device_bay_position', 'position', 'face', 'airflow', 'primary_ip', 'primary_ip4', 'primary_ip6', 'cluster', - 'virtual_chassis', 'vc_position', 'vc_priority', 'description', 'config_template', 'comments', 'contacts', - 'tags', 'created', 'last_updated', + 'device_bay_position', 'position', 'face', 'latitude', 'longitude', 'airflow', 'primary_ip', 'primary_ip4', + 'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'description', 'config_template', + 'comments', 'contacts', 'tags', 'created', 'last_updated', ) default_columns = ( 'pk', 'name', 'status', 'tenant', 'site', 'location', 'rack', 'device_role', 'manufacturer', 'device_type', diff --git a/netbox/dcim/tests/test_filtersets.py b/netbox/dcim/tests/test_filtersets.py index 3f9712f2a..aa6860a16 100644 --- a/netbox/dcim/tests/test_filtersets.py +++ b/netbox/dcim/tests/test_filtersets.py @@ -1515,9 +1515,9 @@ class PlatformTestCase(TestCase, ChangeLoggedFilterSetTests): Manufacturer.objects.bulk_create(manufacturers) platforms = ( - Platform(name='Platform 1', slug='platform-1', manufacturer=manufacturers[0], napalm_driver='driver-1', description='A'), - Platform(name='Platform 2', slug='platform-2', manufacturer=manufacturers[1], napalm_driver='driver-2', description='B'), - Platform(name='Platform 3', slug='platform-3', manufacturer=manufacturers[2], napalm_driver='driver-3', description='C'), + Platform(name='Platform 1', slug='platform-1', manufacturer=manufacturers[0], description='A'), + Platform(name='Platform 2', slug='platform-2', manufacturer=manufacturers[1], description='B'), + Platform(name='Platform 3', slug='platform-3', manufacturer=manufacturers[2], description='C'), ) Platform.objects.bulk_create(platforms) @@ -1533,10 +1533,6 @@ class PlatformTestCase(TestCase, ChangeLoggedFilterSetTests): params = {'description': ['A', 'B']} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) - def test_napalm_driver(self): - params = {'napalm_driver': ['driver-1', 'driver-2']} - self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) - def test_manufacturer(self): manufacturers = Manufacturer.objects.all()[:2] params = {'manufacturer_id': [manufacturers[0].pk, manufacturers[1].pk]} @@ -1642,9 +1638,9 @@ class DeviceTestCase(TestCase, ChangeLoggedFilterSetTests): Tenant.objects.bulk_create(tenants) devices = ( - Device(name='Device 1', device_type=device_types[0], device_role=device_roles[0], platform=platforms[0], tenant=tenants[0], serial='ABC', asset_tag='1001', site=sites[0], location=locations[0], rack=racks[0], position=1, face=DeviceFaceChoices.FACE_FRONT, status=DeviceStatusChoices.STATUS_ACTIVE, cluster=clusters[0], local_context_data={"foo": 123}), - Device(name='Device 2', device_type=device_types[1], device_role=device_roles[1], platform=platforms[1], tenant=tenants[1], serial='DEF', asset_tag='1002', site=sites[1], location=locations[1], rack=racks[1], position=2, face=DeviceFaceChoices.FACE_FRONT, status=DeviceStatusChoices.STATUS_STAGED, airflow=DeviceAirflowChoices.AIRFLOW_FRONT_TO_REAR, cluster=clusters[1]), - Device(name='Device 3', device_type=device_types[2], device_role=device_roles[2], platform=platforms[2], tenant=tenants[2], serial='GHI', asset_tag='1003', site=sites[2], location=locations[2], rack=racks[2], position=3, face=DeviceFaceChoices.FACE_REAR, status=DeviceStatusChoices.STATUS_FAILED, airflow=DeviceAirflowChoices.AIRFLOW_REAR_TO_FRONT, cluster=clusters[2]), + Device(name='Device 1', device_type=device_types[0], device_role=device_roles[0], platform=platforms[0], tenant=tenants[0], serial='ABC', asset_tag='1001', site=sites[0], location=locations[0], rack=racks[0], position=1, face=DeviceFaceChoices.FACE_FRONT, latitude=10, longitude=10, status=DeviceStatusChoices.STATUS_ACTIVE, cluster=clusters[0], local_context_data={"foo": 123}), + Device(name='Device 2', device_type=device_types[1], device_role=device_roles[1], platform=platforms[1], tenant=tenants[1], serial='DEF', asset_tag='1002', site=sites[1], location=locations[1], rack=racks[1], position=2, face=DeviceFaceChoices.FACE_FRONT, latitude=20, longitude=20, status=DeviceStatusChoices.STATUS_STAGED, airflow=DeviceAirflowChoices.AIRFLOW_FRONT_TO_REAR, cluster=clusters[1]), + Device(name='Device 3', device_type=device_types[2], device_role=device_roles[2], platform=platforms[2], tenant=tenants[2], serial='GHI', asset_tag='1003', site=sites[2], location=locations[2], rack=racks[2], position=3, face=DeviceFaceChoices.FACE_REAR, latitude=30, longitude=30, status=DeviceStatusChoices.STATUS_FAILED, airflow=DeviceAirflowChoices.AIRFLOW_REAR_TO_FRONT, cluster=clusters[2]), ) Device.objects.bulk_create(devices) @@ -1725,6 +1721,14 @@ class DeviceTestCase(TestCase, ChangeLoggedFilterSetTests): params = {'position': [1, 2]} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + def test_latitude(self): + params = {'latitude': [10, 20]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + + def test_longitude(self): + params = {'longitude': [10, 20]} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + def test_vc_position(self): params = {'vc_position': [1, 2]} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) diff --git a/netbox/dcim/tests/test_views.py b/netbox/dcim/tests/test_views.py index c0cfca2e7..a327d6400 100644 --- a/netbox/dcim/tests/test_views.py +++ b/netbox/dcim/tests/test_views.py @@ -1609,8 +1609,6 @@ class PlatformTestCase(ViewTestCases.OrganizationalObjectViewTestCase): 'name': 'Platform X', 'slug': 'platform-x', 'manufacturer': manufacturer.pk, - 'napalm_driver': 'junos', - 'napalm_args': None, 'description': 'A new platform', 'tags': [t.pk for t in tags], } @@ -1630,7 +1628,6 @@ class PlatformTestCase(ViewTestCases.OrganizationalObjectViewTestCase): ) cls.bulk_edit_data = { - 'napalm_driver': 'ios', 'description': 'New description', } @@ -1699,6 +1696,8 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase): 'rack': racks[1].pk, 'position': 1, 'face': DeviceFaceChoices.FACE_FRONT, + 'latitude': Decimal('35.780000'), + 'longitude': Decimal('-78.642000'), 'status': DeviceStatusChoices.STATUS_PLANNED, 'primary_ip4': None, 'primary_ip6': None, diff --git a/netbox/extras/models/configs.py b/netbox/extras/models/configs.py index 632323af0..ee9f7cfda 100644 --- a/netbox/extras/models/configs.py +++ b/netbox/extras/models/configs.py @@ -146,6 +146,7 @@ class ConfigContext(SyncedDataMixin, CloningMixin, ChangeLoggedModel): Synchronize context data from the designated DataFile (if any). """ self.data = self.data_file.get_data() + sync_data.alters_data = True class ConfigContextModel(models.Model): @@ -236,6 +237,7 @@ class ConfigTemplate(SyncedDataMixin, ExportTemplatesMixin, TagsMixin, ChangeLog Synchronize template content from the designated DataFile (if any). """ self.template_code = self.data_file.data_as_string + sync_data.alters_data = True def render(self, context=None): """ diff --git a/netbox/extras/models/models.py b/netbox/extras/models/models.py index b7034645f..e95c0aff3 100644 --- a/netbox/extras/models/models.py +++ b/netbox/extras/models/models.py @@ -362,6 +362,7 @@ class ExportTemplate(SyncedDataMixin, CloningMixin, ExportTemplatesMixin, Change Synchronize template content from the designated DataFile (if any). """ self.template_code = self.data_file.data_as_string + sync_data.alters_data = True def render(self, queryset): """ @@ -625,6 +626,7 @@ class ConfigRevision(models.Model): """ cache.set('config', self.data, None) cache.set('config_version', self.pk, None) + activate.alters_data = True @admin.display(boolean=True) def is_active(self): diff --git a/netbox/extras/models/staging.py b/netbox/extras/models/staging.py index b46d6a7bc..3d1c149bc 100644 --- a/netbox/extras/models/staging.py +++ b/netbox/extras/models/staging.py @@ -112,3 +112,4 @@ class StagedChange(ChangeLoggedModel): instance = self.model.objects.get(pk=self.object_id) logger.info(f'Deleting {self.model._meta.verbose_name} {instance}') instance.delete() + apply.alters_data = True diff --git a/netbox/netbox/models/features.py b/netbox/netbox/models/features.py index 8bacba534..8d79dd6bc 100644 --- a/netbox/netbox/models/features.py +++ b/netbox/netbox/models/features.py @@ -71,6 +71,7 @@ class ChangeLoggingMixin(models.Model): `_prechange_snapshot` on the instance. """ self._prechange_snapshot = self.serialize_object() + snapshot.alters_data = True def to_objectchange(self, action): """ @@ -244,6 +245,7 @@ class CustomFieldsMixin(models.Model): """ for cf in self.custom_fields: self.custom_field_data[cf.name] = cf.default + populate_custom_field_defaults.alters_data = True def clean(self): super().clean() @@ -419,6 +421,7 @@ class SyncedDataMixin(models.Model): self.data_synced = None super().clean() + clean.alters_data = True def save(self, *args, **kwargs): from core.models import AutoSyncRecord @@ -466,6 +469,7 @@ class SyncedDataMixin(models.Model): self.data_synced = timezone.now() if save: self.save() + sync.alters_data = True def sync_data(self): """ diff --git a/netbox/templates/dcim/device.html b/netbox/templates/dcim/device.html index b0e67269c..68fa84a24 100644 --- a/netbox/templates/dcim/device.html +++ b/netbox/templates/dcim/device.html @@ -76,6 +76,23 @@ {% endif %} +
{{ object.napalm_args|json }}-