mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-22 20:12:00 -06:00
Closes #5003: CSV import now accepts slug values for choice fields
This commit is contained in:
parent
70ec5b9f37
commit
0cc2a6b2cf
@ -8,6 +8,7 @@
|
||||
|
||||
* [#1692](https://github.com/netbox-community/netbox/issues/1692) - Allow assigment of inventory items to parent items in web UI
|
||||
* [#4956](https://github.com/netbox-community/netbox/issues/4956) - Include inventory items on primary device view
|
||||
* [#5003](https://github.com/netbox-community/netbox/issues/5003) - CSV import now accepts slug values for choice fields
|
||||
* [#5146](https://github.com/netbox-community/netbox/issues/5146) - Add custom fields support for cables, power panels, rack reservations, and virtual chassis
|
||||
|
||||
### Other Changes
|
||||
|
@ -983,9 +983,9 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||
|
||||
cls.csv_data = (
|
||||
"device_role,manufacturer,device_type,status,name,site,rack_group,rack,position,face",
|
||||
"Device Role 1,Manufacturer 1,Device Type 1,Active,Device 4,Site 1,Rack Group 1,Rack 1,10,Front",
|
||||
"Device Role 1,Manufacturer 1,Device Type 1,Active,Device 5,Site 1,Rack Group 1,Rack 1,20,Front",
|
||||
"Device Role 1,Manufacturer 1,Device Type 1,Active,Device 6,Site 1,Rack Group 1,Rack 1,30,Front",
|
||||
"Device Role 1,Manufacturer 1,Device Type 1,active,Device 4,Site 1,Rack Group 1,Rack 1,10,front",
|
||||
"Device Role 1,Manufacturer 1,Device Type 1,active,Device 5,Site 1,Rack Group 1,Rack 1,20,front",
|
||||
"Device Role 1,Manufacturer 1,Device Type 1,active,Device 6,Site 1,Rack Group 1,Rack 1,30,front",
|
||||
)
|
||||
|
||||
cls.bulk_edit_data = {
|
||||
@ -1267,9 +1267,9 @@ class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
||||
|
||||
cls.csv_data = (
|
||||
"device,name,type",
|
||||
"Device 1,Interface 4,1000BASE-T (1GE)",
|
||||
"Device 1,Interface 5,1000BASE-T (1GE)",
|
||||
"Device 1,Interface 6,1000BASE-T (1GE)",
|
||||
"Device 1,Interface 4,1000base-t",
|
||||
"Device 1,Interface 5,1000base-t",
|
||||
"Device 1,Interface 6,1000base-t",
|
||||
)
|
||||
|
||||
|
||||
@ -1326,9 +1326,9 @@ class FrontPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
||||
|
||||
cls.csv_data = (
|
||||
"device,name,type,rear_port,rear_port_position",
|
||||
"Device 1,Front Port 4,8P8C,Rear Port 4,1",
|
||||
"Device 1,Front Port 5,8P8C,Rear Port 5,1",
|
||||
"Device 1,Front Port 6,8P8C,Rear Port 6,1",
|
||||
"Device 1,Front Port 4,8p8c,Rear Port 4,1",
|
||||
"Device 1,Front Port 5,8p8c,Rear Port 5,1",
|
||||
"Device 1,Front Port 6,8p8c,Rear Port 6,1",
|
||||
)
|
||||
|
||||
|
||||
@ -1372,9 +1372,9 @@ class RearPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
||||
|
||||
cls.csv_data = (
|
||||
"device,name,type,positions",
|
||||
"Device 1,Rear Port 4,8P8C,1",
|
||||
"Device 1,Rear Port 5,8P8C,1",
|
||||
"Device 1,Rear Port 6,8P8C,1",
|
||||
"Device 1,Rear Port 4,8p8c,1",
|
||||
"Device 1,Rear Port 5,8p8c,1",
|
||||
"Device 1,Rear Port 6,8p8c,1",
|
||||
)
|
||||
|
||||
|
||||
|
@ -194,9 +194,9 @@ class PrefixTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||
|
||||
cls.csv_data = (
|
||||
"vrf,prefix,status",
|
||||
"VRF 1,10.4.0.0/16,Active",
|
||||
"VRF 1,10.5.0.0/16,Active",
|
||||
"VRF 1,10.6.0.0/16,Active",
|
||||
"VRF 1,10.4.0.0/16,active",
|
||||
"VRF 1,10.5.0.0/16,active",
|
||||
"VRF 1,10.6.0.0/16,active",
|
||||
)
|
||||
|
||||
cls.bulk_edit_data = {
|
||||
@ -244,9 +244,9 @@ class IPAddressTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||
|
||||
cls.csv_data = (
|
||||
"vrf,address,status",
|
||||
"VRF 1,192.0.2.4/24,Active",
|
||||
"VRF 1,192.0.2.5/24,Active",
|
||||
"VRF 1,192.0.2.6/24,Active",
|
||||
"VRF 1,192.0.2.4/24,active",
|
||||
"VRF 1,192.0.2.5/24,active",
|
||||
"VRF 1,192.0.2.6/24,active",
|
||||
)
|
||||
|
||||
cls.bulk_edit_data = {
|
||||
@ -334,9 +334,9 @@ class VLANTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||
|
||||
cls.csv_data = (
|
||||
"vid,name,status",
|
||||
"104,VLAN104,Active",
|
||||
"105,VLAN105,Active",
|
||||
"106,VLAN106,Active",
|
||||
"104,VLAN104,active",
|
||||
"105,VLAN105,active",
|
||||
"106,VLAN106,active",
|
||||
)
|
||||
|
||||
cls.bulk_edit_data = {
|
||||
@ -393,9 +393,9 @@ class ServiceTestCase(
|
||||
|
||||
cls.csv_data = (
|
||||
"device,name,protocol,port,description",
|
||||
"Device 1,Service 1,TCP,1,First service",
|
||||
"Device 1,Service 2,TCP,2,Second service",
|
||||
"Device 1,Service 3,UDP,3,Third service",
|
||||
"Device 1,Service 1,tcp,1,First service",
|
||||
"Device 1,Service 2,tcp,2,Second service",
|
||||
"Device 1,Service 3,udp,3,Third service",
|
||||
)
|
||||
|
||||
cls.bulk_edit_data = {
|
||||
|
@ -66,7 +66,7 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if field.choice_values %}
|
||||
{% if field.STATIC_CHOICES %}
|
||||
<button type="button" class="btn btn-primary btn-xs pull-right" data-toggle="modal" data-target="#{{ name }}_choices">
|
||||
<i class="fa fa-question"></i>
|
||||
</button>
|
||||
@ -77,9 +77,12 @@
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title"><code>{{ name }}</code> Choices</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<ul>{% for value, label in field.choices %}{% if value %}<li>{{ value }}</li>{% endif %}{% endfor %}</ul>
|
||||
</div>
|
||||
<table class="table table-striped modal-body">
|
||||
<tr><th>Import Value</th><th>Label</th></tr>
|
||||
{% for value, label in field.choices %}
|
||||
{% if value %}<tr><td><samp>{{ value }}</samp></td><td>{{ label }}</td></tr>{% endif %}
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -117,18 +117,11 @@ class CSVChoiceField(forms.ChoiceField):
|
||||
"""
|
||||
Invert the provided set of choices to take the human-friendly label as input, and return the database value.
|
||||
"""
|
||||
def __init__(self, choices, *args, **kwargs):
|
||||
super().__init__(choices=choices, *args, **kwargs)
|
||||
self.choices = [(label, label) for value, label in unpack_grouped_choices(choices)]
|
||||
self.choice_values = {label: value for value, label in unpack_grouped_choices(choices)}
|
||||
STATIC_CHOICES = True
|
||||
|
||||
def clean(self, value):
|
||||
value = super().clean(value)
|
||||
if not value:
|
||||
return ''
|
||||
if value not in self.choice_values:
|
||||
raise forms.ValidationError("Invalid choice: {}".format(value))
|
||||
return self.choice_values[value]
|
||||
def __init__(self, *, choices=(), **kwargs):
|
||||
super().__init__(choices=choices, **kwargs)
|
||||
self.choices = unpack_grouped_choices(choices)
|
||||
|
||||
|
||||
class CSVModelChoiceField(forms.ModelChoiceField):
|
||||
|
Loading…
Reference in New Issue
Block a user