mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-29 03:46:25 -06:00
Fixes #3950: Cloned Device Form does not retain device type
This commit is contained in:
parent
8b641af3e0
commit
4d9fa0396f
@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
## Bug Fixes
|
## Bug Fixes
|
||||||
|
|
||||||
|
* [#3950](https://github.com/netbox-community/netbox/issues/3950) - Fix "Create and Add Another" not keep Manufacturer and Device Type after device creation
|
||||||
* [#3983](https://github.com/netbox-community/netbox/issues/3983) - Permit the creation of multiple unnamed devices
|
* [#3983](https://github.com/netbox-community/netbox/issues/3983) - Permit the creation of multiple unnamed devices
|
||||||
* [#3989](https://github.com/netbox-community/netbox/issues/3989) - Correct HTTP content type assignment for webhooks
|
* [#3989](https://github.com/netbox-community/netbox/issues/3989) - Correct HTTP content type assignment for webhooks
|
||||||
* [#3999](https://github.com/netbox-community/netbox/issues/3999) - Do not filter child results by null if non-required parent fields are blank
|
* [#3999](https://github.com/netbox-community/netbox/issues/3999) - Do not filter child results by null if non-required parent fields are blank
|
||||||
@ -26,7 +27,6 @@
|
|||||||
* [#3721](https://github.com/netbox-community/netbox/issues/3721) - Allow Unicode characters in tag slugs
|
* [#3721](https://github.com/netbox-community/netbox/issues/3721) - Allow Unicode characters in tag slugs
|
||||||
* [#3923](https://github.com/netbox-community/netbox/issues/3923) - Indicate validation failure when using SSH-style RSA keys
|
* [#3923](https://github.com/netbox-community/netbox/issues/3923) - Indicate validation failure when using SSH-style RSA keys
|
||||||
* [#3951](https://github.com/netbox-community/netbox/issues/3951) - Fix exception in webhook worker due to missing constant
|
* [#3951](https://github.com/netbox-community/netbox/issues/3951) - Fix exception in webhook worker due to missing constant
|
||||||
* [#3950](https://github.com/netbox-community/netbox/issues/3950) - Fix "Create and Add Another" not keep Manufacturer and Device Type after device creation
|
|
||||||
* [#3953](https://github.com/netbox-community/netbox/issues/3953) - Fix validation error when creating child devices
|
* [#3953](https://github.com/netbox-community/netbox/issues/3953) - Fix validation error when creating child devices
|
||||||
* [#3960](https://github.com/netbox-community/netbox/issues/3960) - Fix legacy device status choice
|
* [#3960](https://github.com/netbox-community/netbox/issues/3960) - Fix legacy device status choice
|
||||||
* [#3962](https://github.com/netbox-community/netbox/issues/3962) - Fix display of unnamed devices in rack elevations
|
* [#3962](https://github.com/netbox-community/netbox/issues/3962) - Fix display of unnamed devices in rack elevations
|
||||||
|
@ -66,6 +66,14 @@
|
|||||||
"slug": "servertech"
|
"slug": "servertech"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"model": "dcim.manufacturer",
|
||||||
|
"pk": 4,
|
||||||
|
"fields": {
|
||||||
|
"name": "Dell",
|
||||||
|
"slug": "dell"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"model": "dcim.devicetype",
|
"model": "dcim.devicetype",
|
||||||
"pk": 1,
|
"pk": 1,
|
||||||
@ -144,6 +152,19 @@
|
|||||||
"is_full_depth": false
|
"is_full_depth": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"model": "dcim.devicetype",
|
||||||
|
"pk": 7,
|
||||||
|
"fields": {
|
||||||
|
"created": "2016-06-23",
|
||||||
|
"last_updated": "2016-06-23T03:19:56.521Z",
|
||||||
|
"manufacturer": 4,
|
||||||
|
"model": "PowerEdge R640",
|
||||||
|
"slug": "poweredge-r640",
|
||||||
|
"u_height": 1,
|
||||||
|
"is_full_depth": false
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"model": "dcim.consoleporttemplate",
|
"model": "dcim.consoleporttemplate",
|
||||||
"pk": 1,
|
"pk": 1,
|
||||||
@ -1880,6 +1901,15 @@
|
|||||||
"color": "yellow"
|
"color": "yellow"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"model": "dcim.devicerole",
|
||||||
|
"pk": 7,
|
||||||
|
"fields": {
|
||||||
|
"name": "Server",
|
||||||
|
"slug": "server",
|
||||||
|
"color": "grey"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"model": "dcim.platform",
|
"model": "dcim.platform",
|
||||||
"pk": 1,
|
"pk": 1,
|
||||||
@ -2127,6 +2157,34 @@
|
|||||||
"comments": ""
|
"comments": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"model": "dcim.device",
|
||||||
|
"pk": 13,
|
||||||
|
"fields": {
|
||||||
|
"local_context_data": null,
|
||||||
|
"created": "2016-06-23",
|
||||||
|
"last_updated": "2016-06-23T03:19:56.521Z",
|
||||||
|
"device_type": 7,
|
||||||
|
"device_role": 6,
|
||||||
|
"tenant": null,
|
||||||
|
"platform": null,
|
||||||
|
"name": "test1-server1",
|
||||||
|
"serial": "",
|
||||||
|
"asset_tag": null,
|
||||||
|
"site": 1,
|
||||||
|
"rack": 2,
|
||||||
|
"position": null,
|
||||||
|
"face": "",
|
||||||
|
"status": true,
|
||||||
|
"primary_ip4": null,
|
||||||
|
"primary_ip6": null,
|
||||||
|
"cluster": 4,
|
||||||
|
"virtual_chassis": null,
|
||||||
|
"vc_position": null,
|
||||||
|
"vc_priority": null,
|
||||||
|
"comments": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"model": "dcim.consoleport",
|
"model": "dcim.consoleport",
|
||||||
"pk": 1,
|
"pk": 1,
|
||||||
|
@ -1636,13 +1636,22 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldForm):
|
|||||||
instance = kwargs.get('instance')
|
instance = kwargs.get('instance')
|
||||||
if 'initial' not in kwargs:
|
if 'initial' not in kwargs:
|
||||||
kwargs['initial'] = {}
|
kwargs['initial'] = {}
|
||||||
|
|
||||||
# Using hasattr() instead of "is not None" to avoid RelatedObjectDoesNotExist on required field
|
# Using hasattr() instead of "is not None" to avoid RelatedObjectDoesNotExist on required field
|
||||||
if instance and hasattr(instance, 'device_type'):
|
if instance and hasattr(instance, 'device_type'):
|
||||||
kwargs['initial']['manufacturer'] = instance.device_type.manufacturer
|
kwargs['initial']['manufacturer'] = instance.device_type.manufacturer
|
||||||
if instance and instance.cluster is not None:
|
if instance and instance.cluster is not None:
|
||||||
kwargs['initial']['cluster_group'] = instance.cluster.group
|
kwargs['initial']['cluster_group'] = instance.cluster.group
|
||||||
|
|
||||||
|
if 'device_type' in kwargs['initial'] and 'manufacturer' not in kwargs['initial']:
|
||||||
|
device_type_id = kwargs['initial']['device_type']
|
||||||
|
manufacturer_id = DeviceType.objects.filter(pk=device_type_id).values_list('manufacturer__pk', flat=True).first()
|
||||||
|
kwargs['initial']['manufacturer'] = manufacturer_id
|
||||||
|
|
||||||
|
if 'cluster' in kwargs['initial'] and 'cluster_group' not in kwargs['initial']:
|
||||||
|
cluster_id = kwargs['initial']['cluster']
|
||||||
|
cluster_group_id = Cluster.objects.filter(pk=cluster_id).values_list('group__pk', flat=True).first()
|
||||||
|
kwargs['initial']['cluster_group'] = cluster_group_id
|
||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
if self.instance.pk:
|
if self.instance.pk:
|
||||||
|
@ -1409,7 +1409,7 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel):
|
|||||||
'site', 'rack_group', 'rack_name', 'position', 'face', 'comments',
|
'site', 'rack_group', 'rack_name', 'position', 'face', 'comments',
|
||||||
]
|
]
|
||||||
clone_fields = [
|
clone_fields = [
|
||||||
'device_type', 'device_type__manufacturer', 'device_role', 'tenant', 'platform', 'site', 'rack', 'status', 'cluster', 'cluster__group',
|
'device_type', 'device_role', 'tenant', 'platform', 'site', 'rack', 'status', 'cluster',
|
||||||
]
|
]
|
||||||
|
|
||||||
STATUS_CLASS_MAP = {
|
STATUS_CLASS_MAP = {
|
||||||
|
@ -10,7 +10,7 @@ def get_id(model, slug):
|
|||||||
|
|
||||||
class DeviceTestCase(TestCase):
|
class DeviceTestCase(TestCase):
|
||||||
|
|
||||||
fixtures = ['dcim', 'ipam']
|
fixtures = ['dcim', 'ipam', 'virtualization']
|
||||||
|
|
||||||
def test_racked_device(self):
|
def test_racked_device(self):
|
||||||
test = DeviceForm(data={
|
test = DeviceForm(data={
|
||||||
@ -78,3 +78,15 @@ class DeviceTestCase(TestCase):
|
|||||||
})
|
})
|
||||||
self.assertTrue(test.is_valid())
|
self.assertTrue(test.is_valid())
|
||||||
self.assertTrue(test.save())
|
self.assertTrue(test.save())
|
||||||
|
|
||||||
|
def test_cloned_cluster_device_initial_data(self):
|
||||||
|
test = DeviceForm(initial={
|
||||||
|
'device_type': get_id(DeviceType, 'poweredge-r640'),
|
||||||
|
'device_role': get_id(DeviceRole, 'server'),
|
||||||
|
'status': DeviceStatusChoices.STATUS_ACTIVE,
|
||||||
|
'site': get_id(Site, 'test1'),
|
||||||
|
"cluster": Cluster.objects.get(id=4).id,
|
||||||
|
})
|
||||||
|
self.assertEqual(test.initial['manufacturer'], get_id(Manufacturer, 'dell'))
|
||||||
|
self.assertIn('cluster_group', test.initial)
|
||||||
|
self.assertEqual(test.initial['cluster_group'], get_id(ClusterGroup, 'vm-host'))
|
||||||
|
@ -181,31 +181,15 @@ def render_jinja2(template_code, context):
|
|||||||
return Environment().from_string(source=template_code).render(**context)
|
return Environment().from_string(source=template_code).render(**context)
|
||||||
|
|
||||||
|
|
||||||
def get_field_value(instance, field_name):
|
|
||||||
"""
|
|
||||||
Retrieve appropriate field name & value from object recursively.
|
|
||||||
"""
|
|
||||||
if '__' in field_name:
|
|
||||||
fn = field_name.split('__')
|
|
||||||
attr = getattr(instance, fn[0])
|
|
||||||
return get_field_value(attr, fn[1])
|
|
||||||
|
|
||||||
field = instance._meta.get_field(field_name) if instance else None
|
|
||||||
field_value = field.value_from_object(instance) if field else None
|
|
||||||
|
|
||||||
if field_name == 'group':
|
|
||||||
field_name = 'cluster_group'
|
|
||||||
return field_name, field_value
|
|
||||||
|
|
||||||
|
|
||||||
def prepare_cloned_fields(instance):
|
def prepare_cloned_fields(instance):
|
||||||
"""
|
"""
|
||||||
Compile an object's `clone_fields` list into a string of URL query parameters. Tags are automatically cloned where
|
Compile an object's `clone_fields` list into a string of URL query parameters. Tags are automatically cloned where
|
||||||
applicable.
|
applicable.
|
||||||
"""
|
"""
|
||||||
params = {}
|
params = {}
|
||||||
for field in getattr(instance, 'clone_fields', []):
|
for field_name in getattr(instance, 'clone_fields', []):
|
||||||
field_name, field_value = get_field_value(instance, field)
|
field = instance._meta.get_field(field_name)
|
||||||
|
field_value = field.value_from_object(instance)
|
||||||
|
|
||||||
# Swap out False with URL-friendly value
|
# Swap out False with URL-friendly value
|
||||||
if field_value is False:
|
if field_value is False:
|
||||||
|
170
netbox/virtualization/fixtures/virtualization.json
Normal file
170
netbox/virtualization/fixtures/virtualization.json
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"model": "virtualization.clustertype",
|
||||||
|
"pk": 1,
|
||||||
|
"fields": {
|
||||||
|
"created": "2016-08-01",
|
||||||
|
"last_updated": "2016-08-01T15:22:42.289Z",
|
||||||
|
"name": "Public Cloud",
|
||||||
|
"slug": "public-cloud"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "virtualization.clustertype",
|
||||||
|
"pk": 2,
|
||||||
|
"fields": {
|
||||||
|
"created": "2016-08-01",
|
||||||
|
"last_updated": "2016-08-01T15:22:42.289Z",
|
||||||
|
"name": "vSphere",
|
||||||
|
"slug": "vsphere"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "virtualization.clustertype",
|
||||||
|
"pk": 3,
|
||||||
|
"fields": {
|
||||||
|
"created": "2016-08-01",
|
||||||
|
"last_updated": "2016-08-01T15:22:42.289Z",
|
||||||
|
"name": "Hyper-V",
|
||||||
|
"slug": "hyper-v"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "virtualization.clustertype",
|
||||||
|
"pk": 4,
|
||||||
|
"fields": {
|
||||||
|
"created": "2016-08-01",
|
||||||
|
"last_updated": "2016-08-01T15:22:42.289Z",
|
||||||
|
"name": "libvirt",
|
||||||
|
"slug": "libvirt"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "virtualization.clustertype",
|
||||||
|
"pk": 5,
|
||||||
|
"fields": {
|
||||||
|
"created": "2016-08-01",
|
||||||
|
"last_updated": "2016-08-01T15:22:42.289Z",
|
||||||
|
"name": "LXD",
|
||||||
|
"slug": "lxd"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "virtualization.clustertype",
|
||||||
|
"pk": 6,
|
||||||
|
"fields": {
|
||||||
|
"created": "2016-08-01",
|
||||||
|
"last_updated": "2016-08-01T15:22:42.289Z",
|
||||||
|
"name": "Docker",
|
||||||
|
"slug": "docker"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "virtualization.clustergroup",
|
||||||
|
"pk": 1,
|
||||||
|
"fields": {
|
||||||
|
"created": "2016-08-01",
|
||||||
|
"last_updated": "2016-08-01T15:22:42.289Z",
|
||||||
|
"name": "VM Host",
|
||||||
|
"slug": "vm-host"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "virtualization.cluster",
|
||||||
|
"pk": 1,
|
||||||
|
"fields": {
|
||||||
|
"created": "2016-08-01",
|
||||||
|
"last_updated": "2016-08-01T15:22:42.289Z",
|
||||||
|
"name": "Digital Ocean",
|
||||||
|
"type": 1,
|
||||||
|
"group": 1,
|
||||||
|
"tenant": null,
|
||||||
|
"site": null,
|
||||||
|
"comments": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "virtualization.cluster",
|
||||||
|
"pk": 2,
|
||||||
|
"fields": {
|
||||||
|
"created": "2016-08-01",
|
||||||
|
"last_updated": "2016-08-01T15:22:42.289Z",
|
||||||
|
"name": "Amazon EC2",
|
||||||
|
"type": 1,
|
||||||
|
"group": 1,
|
||||||
|
"tenant": null,
|
||||||
|
"site": null,
|
||||||
|
"comments": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "virtualization.cluster",
|
||||||
|
"pk": 3,
|
||||||
|
"fields": {
|
||||||
|
"created": "2016-08-01",
|
||||||
|
"last_updated": "2016-08-01T15:22:42.289Z",
|
||||||
|
"name": "Microsoft Azure",
|
||||||
|
"type": 1,
|
||||||
|
"group": 1,
|
||||||
|
"tenant": null,
|
||||||
|
"site": null,
|
||||||
|
"comments": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "virtualization.cluster",
|
||||||
|
"pk": 4,
|
||||||
|
"fields": {
|
||||||
|
"created": "2016-08-01",
|
||||||
|
"last_updated": "2016-08-01T15:22:42.289Z",
|
||||||
|
"name": "vSphere Cluster",
|
||||||
|
"type": 2,
|
||||||
|
"group": 1,
|
||||||
|
"tenant": null,
|
||||||
|
"site": null,
|
||||||
|
"comments": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "virtualization.virtualmachine",
|
||||||
|
"pk": 1,
|
||||||
|
"fields": {
|
||||||
|
"local_context_data": null,
|
||||||
|
"created": "2019-12-19",
|
||||||
|
"last_updated": "2019-12-19T05:24:19.146Z",
|
||||||
|
"cluster": 2,
|
||||||
|
"tenant": null,
|
||||||
|
"platform": null,
|
||||||
|
"name": "vm1",
|
||||||
|
"status": "active",
|
||||||
|
"role": null,
|
||||||
|
"primary_ip4": null,
|
||||||
|
"primary_ip6": null,
|
||||||
|
"vcpus": null,
|
||||||
|
"memory": null,
|
||||||
|
"disk": null,
|
||||||
|
"comments": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "virtualization.virtualmachine",
|
||||||
|
"pk": 2,
|
||||||
|
"fields": {
|
||||||
|
"local_context_data": null,
|
||||||
|
"created": "2019-12-19",
|
||||||
|
"last_updated": "2019-12-19T05:24:41.478Z",
|
||||||
|
"cluster": 1,
|
||||||
|
"tenant": null,
|
||||||
|
"platform": null,
|
||||||
|
"name": "vm2",
|
||||||
|
"status": "active",
|
||||||
|
"role": null,
|
||||||
|
"primary_ip4": null,
|
||||||
|
"primary_ip6": null,
|
||||||
|
"vcpus": null,
|
||||||
|
"memory": null,
|
||||||
|
"disk": null,
|
||||||
|
"comments": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
Loading…
Reference in New Issue
Block a user