Closes #5003: CSV import now accepts slug values for choice fields

This commit is contained in:
Jeremy Stretch 2020-09-18 13:03:38 -04:00
parent 70ec5b9f37
commit 0cc2a6b2cf
5 changed files with 36 additions and 39 deletions

View File

@ -8,6 +8,7 @@
* [#1692](https://github.com/netbox-community/netbox/issues/1692) - Allow assigment of inventory items to parent items in web UI * [#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 * [#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 * [#5146](https://github.com/netbox-community/netbox/issues/5146) - Add custom fields support for cables, power panels, rack reservations, and virtual chassis
### Other Changes ### Other Changes

View File

@ -983,9 +983,9 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
cls.csv_data = ( cls.csv_data = (
"device_role,manufacturer,device_type,status,name,site,rack_group,rack,position,face", "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 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 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 6,Site 1,Rack Group 1,Rack 1,30,front",
) )
cls.bulk_edit_data = { cls.bulk_edit_data = {
@ -1267,9 +1267,9 @@ class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
cls.csv_data = ( cls.csv_data = (
"device,name,type", "device,name,type",
"Device 1,Interface 4,1000BASE-T (1GE)", "Device 1,Interface 4,1000base-t",
"Device 1,Interface 5,1000BASE-T (1GE)", "Device 1,Interface 5,1000base-t",
"Device 1,Interface 6,1000BASE-T (1GE)", "Device 1,Interface 6,1000base-t",
) )
@ -1326,9 +1326,9 @@ class FrontPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
cls.csv_data = ( cls.csv_data = (
"device,name,type,rear_port,rear_port_position", "device,name,type,rear_port,rear_port_position",
"Device 1,Front Port 4,8P8C,Rear Port 4,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 5,8p8c,Rear Port 5,1",
"Device 1,Front Port 6,8P8C,Rear Port 6,1", "Device 1,Front Port 6,8p8c,Rear Port 6,1",
) )
@ -1372,9 +1372,9 @@ class RearPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
cls.csv_data = ( cls.csv_data = (
"device,name,type,positions", "device,name,type,positions",
"Device 1,Rear Port 4,8P8C,1", "Device 1,Rear Port 4,8p8c,1",
"Device 1,Rear Port 5,8P8C,1", "Device 1,Rear Port 5,8p8c,1",
"Device 1,Rear Port 6,8P8C,1", "Device 1,Rear Port 6,8p8c,1",
) )

View File

@ -194,9 +194,9 @@ class PrefixTestCase(ViewTestCases.PrimaryObjectViewTestCase):
cls.csv_data = ( cls.csv_data = (
"vrf,prefix,status", "vrf,prefix,status",
"VRF 1,10.4.0.0/16,Active", "VRF 1,10.4.0.0/16,active",
"VRF 1,10.5.0.0/16,Active", "VRF 1,10.5.0.0/16,active",
"VRF 1,10.6.0.0/16,Active", "VRF 1,10.6.0.0/16,active",
) )
cls.bulk_edit_data = { cls.bulk_edit_data = {
@ -244,9 +244,9 @@ class IPAddressTestCase(ViewTestCases.PrimaryObjectViewTestCase):
cls.csv_data = ( cls.csv_data = (
"vrf,address,status", "vrf,address,status",
"VRF 1,192.0.2.4/24,Active", "VRF 1,192.0.2.4/24,active",
"VRF 1,192.0.2.5/24,Active", "VRF 1,192.0.2.5/24,active",
"VRF 1,192.0.2.6/24,Active", "VRF 1,192.0.2.6/24,active",
) )
cls.bulk_edit_data = { cls.bulk_edit_data = {
@ -334,9 +334,9 @@ class VLANTestCase(ViewTestCases.PrimaryObjectViewTestCase):
cls.csv_data = ( cls.csv_data = (
"vid,name,status", "vid,name,status",
"104,VLAN104,Active", "104,VLAN104,active",
"105,VLAN105,Active", "105,VLAN105,active",
"106,VLAN106,Active", "106,VLAN106,active",
) )
cls.bulk_edit_data = { cls.bulk_edit_data = {
@ -393,9 +393,9 @@ class ServiceTestCase(
cls.csv_data = ( cls.csv_data = (
"device,name,protocol,port,description", "device,name,protocol,port,description",
"Device 1,Service 1,TCP,1,First service", "Device 1,Service 1,tcp,1,First service",
"Device 1,Service 2,TCP,2,Second service", "Device 1,Service 2,tcp,2,Second service",
"Device 1,Service 3,UDP,3,Third service", "Device 1,Service 3,udp,3,Third service",
) )
cls.bulk_edit_data = { cls.bulk_edit_data = {

View File

@ -66,7 +66,7 @@
{% endif %} {% endif %}
</td> </td>
<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"> <button type="button" class="btn btn-primary btn-xs pull-right" data-toggle="modal" data-target="#{{ name }}_choices">
<i class="fa fa-question"></i> <i class="fa fa-question"></i>
</button> </button>
@ -77,9 +77,12 @@
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button> <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title"><code>{{ name }}</code> Choices</h4> <h4 class="modal-title"><code>{{ name }}</code> Choices</h4>
</div> </div>
<div class="modal-body"> <table class="table table-striped modal-body">
<ul>{% for value, label in field.choices %}{% if value %}<li>{{ value }}</li>{% endif %}{% endfor %}</ul> <tr><th>Import Value</th><th>Label</th></tr>
</div> {% for value, label in field.choices %}
{% if value %}<tr><td><samp>{{ value }}</samp></td><td>{{ label }}</td></tr>{% endif %}
{% endfor %}
</table>
</div> </div>
</div> </div>
</div> </div>

View File

@ -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. 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): STATIC_CHOICES = True
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)}
def clean(self, value): def __init__(self, *, choices=(), **kwargs):
value = super().clean(value) super().__init__(choices=choices, **kwargs)
if not value: self.choices = unpack_grouped_choices(choices)
return ''
if value not in self.choice_values:
raise forms.ValidationError("Invalid choice: {}".format(value))
return self.choice_values[value]
class CSVModelChoiceField(forms.ModelChoiceField): class CSVModelChoiceField(forms.ModelChoiceField):