From 02db0bcc2e6b00e965a5efa2c346d9f863290957 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Fri, 12 May 2023 16:21:22 -0400 Subject: [PATCH 1/4] Closes #11766: Remove obsolete custom ChoiceField and MultipleChoiceField classes --- docs/plugins/development/forms.md | 13 ------------- docs/release-notes/version-3.6.md | 7 +++++++ mkdocs.yml | 1 + netbox/utilities/forms/fields/fields.py | 23 ----------------------- 4 files changed, 8 insertions(+), 36 deletions(-) create mode 100644 docs/release-notes/version-3.6.md 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..45a2acb73 --- /dev/null +++ b/docs/release-notes/version-3.6.md @@ -0,0 +1,7 @@ +# NetBox v3.6 + +## v3.6.0 (FUTURE) + +### Other Changes + +* [#11766](https://github.com/netbox-community/netbox/issues/11766) - Remove obsolete custom `ChoiceField` and `MultipleChoiceField` classes 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/utilities/forms/fields/fields.py b/netbox/utilities/forms/fields/fields.py index cb8c14d6d..c1e1e481c 100644 --- a/netbox/utilities/forms/fields/fields.py +++ b/netbox/utilities/forms/fields/fields.py @@ -11,13 +11,11 @@ from utilities.forms import widgets from utilities.validators import EnhancedURLValidator __all__ = ( - 'ChoiceField', 'ColorField', 'CommentField', 'JSONField', 'LaxURLField', 'MACAddressField', - 'MultipleChoiceField', 'SlugField', 'TagFilterField', ) @@ -128,24 +126,3 @@ class MACAddressField(forms.Field): raise forms.ValidationError(self.error_messages['invalid'], code='invalid') return value - - -# -# Choice fields -# - -class ChoiceField(forms.ChoiceField): - """ - Previously used to override Django's built-in `ChoiceField` to use NetBox's now-obsolete `StaticSelect` widget. - """ - # TODO: Remove in v3.6 - pass - - -class MultipleChoiceField(forms.MultipleChoiceField): - """ - Previously used to override Django's built-in `MultipleChoiceField` to use NetBox's now-obsolete - `StaticSelectMultiple` widget. - """ - # TODO: Remove in v3.6 - pass From 4208b79514a022137144b6088750777999d55e17 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Tue, 16 May 2023 09:35:27 -0400 Subject: [PATCH 2/4] Closes #12320: Remove obsolete fields napalm_driver and napalm_args from Platform --- docs/release-notes/version-3.6.md | 5 +++++ netbox/dcim/api/serializers.py | 4 ++-- netbox/dcim/filtersets.py | 2 +- netbox/dcim/forms/bulk_edit.py | 8 ++------ netbox/dcim/forms/bulk_import.py | 2 +- netbox/dcim/forms/model_forms.py | 9 ++------- .../migrations/0173_remove_napalm_fields.py | 19 +++++++++++++++++++ netbox/dcim/models/devices.py | 17 ++--------------- netbox/dcim/search.py | 1 - netbox/dcim/tables/devices.py | 6 +++--- netbox/dcim/tests/test_filtersets.py | 10 +++------- netbox/dcim/tests/test_views.py | 3 --- netbox/templates/dcim/platform.html | 15 --------------- 13 files changed, 40 insertions(+), 61 deletions(-) create mode 100644 netbox/dcim/migrations/0173_remove_napalm_fields.py diff --git a/docs/release-notes/version-3.6.md b/docs/release-notes/version-3.6.md index 45a2acb73..c4e6847d3 100644 --- a/docs/release-notes/version-3.6.md +++ b/docs/release-notes/version-3.6.md @@ -2,6 +2,11 @@ ## 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/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py index 3f6d55da7..894a3f4f9 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', ] diff --git a/netbox/dcim/filtersets.py b/netbox/dcim/filtersets.py index fccaa72f0..1f142d97f 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): diff --git a/netbox/dcim/forms/bulk_edit.py b/netbox/dcim/forms/bulk_edit.py index 6ed483c79..f64a9768a 100644 --- a/netbox/dcim/forms/bulk_edit.py +++ b/netbox/dcim/forms/bulk_edit.py @@ -471,10 +471,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 @@ -486,9 +482,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..c8f13e213 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', ) diff --git a/netbox/dcim/forms/model_forms.py b/netbox/dcim/forms/model_forms.py index 219216045..8379fd085 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): 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/models/devices.py b/netbox/dcim/models/devices.py index 85a5d6870..a908a6ab6 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]) 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..a0238a1fb 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', ) diff --git a/netbox/dcim/tests/test_filtersets.py b/netbox/dcim/tests/test_filtersets.py index 346b35005..4b82e87bd 100644 --- a/netbox/dcim/tests/test_filtersets.py +++ b/netbox/dcim/tests/test_filtersets.py @@ -1498,9 +1498,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) @@ -1516,10 +1516,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]} diff --git a/netbox/dcim/tests/test_views.py b/netbox/dcim/tests/test_views.py index c0cfca2e7..4bcb8df53 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', } diff --git a/netbox/templates/dcim/platform.html b/netbox/templates/dcim/platform.html index 80fdbd945..56c59d21c 100644 --- a/netbox/templates/dcim/platform.html +++ b/netbox/templates/dcim/platform.html @@ -53,26 +53,11 @@ title="This field has been deprecated, and will be removed in NetBox v3.6." > - {{ object.napalm_driver|placeholder }} {% include 'inc/panels/tags.html' %} -
-
- NAPALM Arguments - -
-
-
{{ object.napalm_args|json }}
-
-
{% plugin_left_page object %}
From 4f76dcd2ea856fa9411dbf287696501f59d6a6c1 Mon Sep 17 00:00:00 2001 From: Arthur Hanson Date: Wed, 14 Jun 2023 11:18:50 -0700 Subject: [PATCH 3/4] 11305 Add GPS coordinates to device (#12782) * 11305 add lat/long to devices * 11305 update docs * 11305 update tests --- docs/models/dcim/device.md | 4 ++++ netbox/dcim/api/serializers.py | 7 +++--- netbox/dcim/filtersets.py | 2 +- netbox/dcim/forms/bulk_import.py | 5 +++-- netbox/dcim/forms/model_forms.py | 6 ++--- .../0174_device_latitude_device_longitude.py | 22 +++++++++++++++++++ netbox/dcim/models/devices.py | 14 ++++++++++++ netbox/dcim/tables/devices.py | 6 ++--- netbox/dcim/tests/test_filtersets.py | 14 +++++++++--- netbox/dcim/tests/test_views.py | 2 ++ netbox/templates/dcim/device.html | 17 ++++++++++++++ netbox/templates/dcim/device_edit.html | 2 ++ 12 files changed, 86 insertions(+), 15 deletions(-) create mode 100644 netbox/dcim/migrations/0174_device_latitude_device_longitude.py 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/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py index 894a3f4f9..3a3065acc 100644 --- a/netbox/dcim/api/serializers.py +++ b/netbox/dcim/api/serializers.py @@ -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 d159e9b73..e87a37847 100644 --- a/netbox/dcim/filtersets.py +++ b/netbox/dcim/filtersets.py @@ -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_import.py b/netbox/dcim/forms/bulk_import.py index c8f13e213..e3e97ab73 100644 --- a/netbox/dcim/forms/bulk_import.py +++ b/netbox/dcim/forms/bulk_import.py @@ -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 8379fd085..56542d70c 100644 --- a/netbox/dcim/forms/model_forms.py +++ b/netbox/dcim/forms/model_forms.py @@ -449,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/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/devices.py b/netbox/dcim/models/devices.py index a908a6ab6..30fafef94 100644 --- a/netbox/dcim/models/devices.py +++ b/netbox/dcim/models/devices.py @@ -624,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/tables/devices.py b/netbox/dcim/tables/devices.py index a0238a1fb..a5862da68 100644 --- a/netbox/dcim/tables/devices.py +++ b/netbox/dcim/tables/devices.py @@ -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 bd0931d5a..aa6860a16 100644 --- a/netbox/dcim/tests/test_filtersets.py +++ b/netbox/dcim/tests/test_filtersets.py @@ -1638,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) @@ -1721,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 4bcb8df53..a327d6400 100644 --- a/netbox/dcim/tests/test_views.py +++ b/netbox/dcim/tests/test_views.py @@ -1696,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/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 %} + + GPS Coordinates + + {% if object.latitude and object.longitude %} + {% if config.MAPS_URL %} + + {% endif %} + {{ object.latitude }}, {{ object.longitude }} + {% else %} + {{ ''|placeholder }} + {% endif %} + + Tenant diff --git a/netbox/templates/dcim/device_edit.html b/netbox/templates/dcim/device_edit.html index 17780b513..2dbe1e3c5 100644 --- a/netbox/templates/dcim/device_edit.html +++ b/netbox/templates/dcim/device_edit.html @@ -53,6 +53,8 @@ {% else %} {% render_field form.face %} {% render_field form.position %} + {% render_field form.latitude %} + {% render_field form.longitude %} {% endif %}
From b4a315604610e9016fd0a5046782e4e81c1d0ba5 Mon Sep 17 00:00:00 2001 From: Arthur Date: Wed, 31 May 2023 13:47:22 -0700 Subject: [PATCH 4/4] 9077 audit alters_data=True --- netbox/core/models/data.py | 1 + netbox/dcim/models/cables.py | 2 ++ netbox/dcim/models/device_component_templates.py | 9 +++++++++ netbox/extras/models/configs.py | 2 ++ netbox/extras/models/models.py | 2 ++ netbox/extras/models/staging.py | 1 + netbox/netbox/models/features.py | 4 ++++ 7 files changed, 21 insertions(+) diff --git a/netbox/core/models/data.py b/netbox/core/models/data.py index a8ac4e8f1..2af697f60 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/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/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 9433ab6b0..969fd22e0 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): """