mirror of
https://github.com/netbox-community/netbox.git
synced 2025-08-26 01:06:11 -06:00
Merge branch 'feature' into 12237-django-4.2
This commit is contained in:
commit
4d996dc833
@ -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.
|
||||
|
@ -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
|
||||
|
12
docs/release-notes/version-3.6.md
Normal file
12
docs/release-notes/version-3.6.md
Normal file
@ -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
|
@ -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'
|
||||
|
@ -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):
|
||||
"""
|
||||
|
@ -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)
|
||||
|
@ -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():
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
19
netbox/dcim/migrations/0173_remove_napalm_fields.py
Normal file
19
netbox/dcim/migrations/0173_remove_napalm_fields.py
Normal file
@ -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',
|
||||
),
|
||||
]
|
@ -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),
|
||||
),
|
||||
]
|
@ -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):
|
||||
"""
|
||||
|
@ -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
|
||||
|
@ -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(
|
||||
|
@ -172,7 +172,6 @@ class PlatformIndex(SearchIndex):
|
||||
fields = (
|
||||
('name', 100),
|
||||
('slug', 110),
|
||||
('napalm_driver', 300),
|
||||
('description', 500),
|
||||
)
|
||||
|
||||
|
@ -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',
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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):
|
||||
"""
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
"""
|
||||
|
@ -76,6 +76,23 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">GPS Coordinates</th>
|
||||
<td class="position-relative">
|
||||
{% if object.latitude and object.longitude %}
|
||||
{% if config.MAPS_URL %}
|
||||
<div class="position-absolute top-50 end-0 translate-middle-y noprint">
|
||||
<a href="{{ config.MAPS_URL }}{{ object.latitude }},{{ object.longitude }}" target="_blank" class="btn btn-primary btn-sm">
|
||||
<i class="mdi mdi-map-marker"></i> Map It
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
<span>{{ object.latitude }}, {{ object.longitude }}</span>
|
||||
{% else %}
|
||||
{{ ''|placeholder }}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Tenant</th>
|
||||
<td>
|
||||
|
@ -53,6 +53,8 @@
|
||||
{% else %}
|
||||
{% render_field form.face %}
|
||||
{% render_field form.position %}
|
||||
{% render_field form.latitude %}
|
||||
{% render_field form.longitude %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
@ -53,26 +53,11 @@
|
||||
title="This field has been deprecated, and will be removed in NetBox v3.6."
|
||||
></i>
|
||||
</th>
|
||||
<td>{{ object.napalm_driver|placeholder }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% include 'inc/panels/tags.html' %}
|
||||
<div class="card">
|
||||
<h5 class="card-header">
|
||||
NAPALM Arguments
|
||||
<i
|
||||
class="mdi mdi-alert-box text-warning"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="right"
|
||||
title="This field has been deprecated, and will be removed in NetBox v3.6."
|
||||
></i>
|
||||
</h5>
|
||||
<div class="card-body">
|
||||
<pre>{{ object.napalm_args|json }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-md-6">
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user