mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
Replaced all CSVForm ChoiceFields with CSVChoiceField
This commit is contained in:
@ -14,7 +14,7 @@ from tenancy.forms import TenancyForm
|
|||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
from utilities.forms import (
|
from utilities.forms import (
|
||||||
APISelect, add_blank_choice, ArrayFieldSelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect,
|
APISelect, add_blank_choice, ArrayFieldSelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect,
|
||||||
ChainedFieldsMixin, ChainedModelChoiceField, CommentField, ExpandableNameField, FilterChoiceField,
|
ChainedFieldsMixin, ChainedModelChoiceField, CommentField, CSVChoiceField, ExpandableNameField, FilterChoiceField,
|
||||||
FlexibleModelChoiceField, Livesearch, SelectWithDisabled, SmallTextarea, SlugField,
|
FlexibleModelChoiceField, Livesearch, SelectWithDisabled, SmallTextarea, SlugField,
|
||||||
FilterTreeNodeMultipleChoiceField,
|
FilterTreeNodeMultipleChoiceField,
|
||||||
)
|
)
|
||||||
@ -24,8 +24,9 @@ from .models import (
|
|||||||
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceRole, DeviceType,
|
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceRole, DeviceType,
|
||||||
Interface, IFACE_FF_CHOICES, IFACE_FF_LAG, IFACE_ORDERING_CHOICES, InterfaceConnection, InterfaceTemplate,
|
Interface, IFACE_FF_CHOICES, IFACE_FF_LAG, IFACE_ORDERING_CHOICES, InterfaceConnection, InterfaceTemplate,
|
||||||
Manufacturer, InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate,
|
Manufacturer, InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate,
|
||||||
RACK_TYPE_CHOICES, RACK_WIDTH_CHOICES, Rack, RackGroup, RackReservation, RackRole, Region, Site, STATUS_CHOICES,
|
RACK_FACE_CHOICES, RACK_TYPE_CHOICES, RACK_WIDTH_CHOICES, Rack, RackGroup, RackReservation, RackRole,
|
||||||
SUBDEVICE_ROLE_CHILD, SUBDEVICE_ROLE_PARENT, VIRTUAL_IFACE_TYPES,
|
RACK_WIDTH_19IN, RACK_WIDTH_23IN, Region, Site, STATUS_CHOICES, SUBDEVICE_ROLE_CHILD, SUBDEVICE_ROLE_PARENT,
|
||||||
|
VIRTUAL_IFACE_TYPES,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -50,31 +51,6 @@ def get_device_by_name_or_pk(name):
|
|||||||
return device
|
return device
|
||||||
|
|
||||||
|
|
||||||
class ConnectionStatusCSVField(forms.ChoiceField):
|
|
||||||
"""
|
|
||||||
This field accepts either "planned" or "connected" as a connection status for CSV imports.
|
|
||||||
"""
|
|
||||||
default_error_messages = {
|
|
||||||
'invalid_choice': '%(value)s is not a valid connection status. It must be either "planned" or "connected."',
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
kwargs['choices'] = (
|
|
||||||
('planned', 'planned'),
|
|
||||||
('connected', 'connected'),
|
|
||||||
)
|
|
||||||
super(ConnectionStatusCSVField, self).__init__(*args, **kwargs)
|
|
||||||
if not self.help_text:
|
|
||||||
self.help_text = 'Connection status'
|
|
||||||
|
|
||||||
def clean(self, value):
|
|
||||||
value = super(ConnectionStatusCSVField, self).clean(value)
|
|
||||||
return {
|
|
||||||
'planned': CONNECTION_STATUS_PLANNED,
|
|
||||||
'connected': CONNECTION_STATUS_CONNECTED,
|
|
||||||
}[value.lower()]
|
|
||||||
|
|
||||||
|
|
||||||
class DeviceComponentForm(BootstrapMixin, forms.Form):
|
class DeviceComponentForm(BootstrapMixin, forms.Form):
|
||||||
"""
|
"""
|
||||||
Allow inclusion of the parent device as context for limiting field choices.
|
Allow inclusion of the parent device as context for limiting field choices.
|
||||||
@ -256,7 +232,7 @@ class RackCSVForm(forms.ModelForm):
|
|||||||
queryset=RackGroup.objects.all(),
|
queryset=RackGroup.objects.all(),
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
required=False,
|
required=False,
|
||||||
help_text='Name of parent group',
|
help_text='Name of parent rack group',
|
||||||
error_messages={
|
error_messages={
|
||||||
'invalid_choice': 'Rack group not found.',
|
'invalid_choice': 'Rack group not found.',
|
||||||
}
|
}
|
||||||
@ -279,15 +255,24 @@ class RackCSVForm(forms.ModelForm):
|
|||||||
'invalid_choice': 'Role not found.',
|
'invalid_choice': 'Role not found.',
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
type = CSVChoiceField(
|
||||||
|
choices=RACK_TYPE_CHOICES,
|
||||||
|
required=False,
|
||||||
|
help_text='Rack type'
|
||||||
|
)
|
||||||
|
width = forms.ChoiceField(
|
||||||
|
choices = (
|
||||||
|
(RACK_WIDTH_19IN, '19'),
|
||||||
|
(RACK_WIDTH_23IN, '23'),
|
||||||
|
),
|
||||||
|
help_text='Rail-to-rail width (in inches)'
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Rack
|
model = Rack
|
||||||
fields = [
|
fields = [
|
||||||
'site', 'group', 'name', 'facility_id', 'tenant', 'role', 'type', 'width', 'u_height', 'desc_units',
|
'site', 'group', 'name', 'facility_id', 'tenant', 'role', 'type', 'width', 'u_height', 'desc_units',
|
||||||
]
|
]
|
||||||
help_texts = {
|
|
||||||
'type': 'Rack type',
|
|
||||||
}
|
|
||||||
|
|
||||||
def clean_group(self):
|
def clean_group(self):
|
||||||
|
|
||||||
@ -297,21 +282,6 @@ class RackCSVForm(forms.ModelForm):
|
|||||||
if group and group.site != site:
|
if group and group.site != site:
|
||||||
raise ValidationError("Invalid group for site {}: {}".format(site, group))
|
raise ValidationError("Invalid group for site {}: {}".format(site, group))
|
||||||
|
|
||||||
def clean_type(self):
|
|
||||||
|
|
||||||
rack_type = self.cleaned_data['type']
|
|
||||||
|
|
||||||
if not rack_type:
|
|
||||||
return None
|
|
||||||
try:
|
|
||||||
choices = {v.lower(): k for k, v in RACK_TYPE_CHOICES}
|
|
||||||
return choices[rack_type.lower()]
|
|
||||||
except KeyError:
|
|
||||||
raise forms.ValidationError('Invalid rack type ({}). Valid choices are: {}.'.format(
|
|
||||||
rack_type,
|
|
||||||
', '.join({v: k for k, v in RACK_TYPE_CHOICES}),
|
|
||||||
))
|
|
||||||
|
|
||||||
|
|
||||||
class RackBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
|
class RackBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
|
||||||
pk = forms.ModelMultipleChoiceField(queryset=Rack.objects.all(), widget=forms.MultipleHiddenInput)
|
pk = forms.ModelMultipleChoiceField(queryset=Rack.objects.all(), widget=forms.MultipleHiddenInput)
|
||||||
@ -752,8 +722,9 @@ class BaseDeviceCSVForm(forms.ModelForm):
|
|||||||
'invalid_choice': 'Invalid platform.',
|
'invalid_choice': 'Invalid platform.',
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
status = forms.CharField(
|
status = CSVChoiceField(
|
||||||
help_text='Status name'
|
choices=STATUS_CHOICES,
|
||||||
|
help_text='Operational status of device'
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -772,13 +743,6 @@ class BaseDeviceCSVForm(forms.ModelForm):
|
|||||||
except DeviceType.DoesNotExist:
|
except DeviceType.DoesNotExist:
|
||||||
self.add_error('model_name', "Invalid device type ({} {})".format(manufacturer, model_name))
|
self.add_error('model_name', "Invalid device type ({} {})".format(manufacturer, model_name))
|
||||||
|
|
||||||
def clean_status(self):
|
|
||||||
status_choices = {s[1].lower(): s[0] for s in STATUS_CHOICES}
|
|
||||||
try:
|
|
||||||
return status_choices[self.cleaned_data['status'].lower()]
|
|
||||||
except KeyError:
|
|
||||||
raise ValidationError("Invalid status: {}".format(self.cleaned_data['status']))
|
|
||||||
|
|
||||||
|
|
||||||
class DeviceCSVForm(BaseDeviceCSVForm):
|
class DeviceCSVForm(BaseDeviceCSVForm):
|
||||||
site = forms.ModelChoiceField(
|
site = forms.ModelChoiceField(
|
||||||
@ -793,9 +757,10 @@ class DeviceCSVForm(BaseDeviceCSVForm):
|
|||||||
required=False,
|
required=False,
|
||||||
help_text='Name of parent rack'
|
help_text='Name of parent rack'
|
||||||
)
|
)
|
||||||
face = forms.CharField(
|
face = CSVChoiceField(
|
||||||
|
choices=RACK_FACE_CHOICES,
|
||||||
required=False,
|
required=False,
|
||||||
help_text='Mounted rack face (front or rear)'
|
help_text='Mounted rack face'
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta(BaseDeviceCSVForm.Meta):
|
class Meta(BaseDeviceCSVForm.Meta):
|
||||||
@ -991,7 +956,10 @@ class ConsoleConnectionCSVForm(forms.ModelForm):
|
|||||||
console_port = forms.CharField(
|
console_port = forms.CharField(
|
||||||
help_text='Console port name'
|
help_text='Console port name'
|
||||||
)
|
)
|
||||||
connection_status = ConnectionStatusCSVField()
|
connection_status = CSVChoiceField(
|
||||||
|
choices=CONNECTION_STATUS_CHOICES,
|
||||||
|
help_text='Connection status'
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ConsolePort
|
model = ConsolePort
|
||||||
@ -1245,7 +1213,10 @@ class PowerConnectionCSVForm(forms.ModelForm):
|
|||||||
power_port = forms.CharField(
|
power_port = forms.CharField(
|
||||||
help_text='Power port name'
|
help_text='Power port name'
|
||||||
)
|
)
|
||||||
connection_status = ConnectionStatusCSVField()
|
connection_status = CSVChoiceField(
|
||||||
|
choices=CONNECTION_STATUS_CHOICES,
|
||||||
|
help_text='Connection status'
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = PowerPort
|
model = PowerPort
|
||||||
@ -1645,7 +1616,10 @@ class InterfaceConnectionCSVForm(forms.ModelForm):
|
|||||||
interface_b = forms.CharField(
|
interface_b = forms.CharField(
|
||||||
help_text='Interface name'
|
help_text='Interface name'
|
||||||
)
|
)
|
||||||
connection_status = ConnectionStatusCSVField()
|
connection_status = CSVChoiceField(
|
||||||
|
choices=CONNECTION_STATUS_CHOICES,
|
||||||
|
help_text='Connection status'
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = InterfaceConnection
|
model = InterfaceConnection
|
||||||
|
@ -346,7 +346,7 @@ class RackGroup(models.Model):
|
|||||||
]
|
]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '{} - {}'.format(self.site.name, self.name)
|
return self.name
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return "{}?group_id={}".format(reverse('dcim:rack_list'), self.pk)
|
return "{}?group_id={}".format(reverse('dcim:rack_list'), self.pk)
|
||||||
|
@ -247,7 +247,7 @@ class RackImportTable(BaseTable):
|
|||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = Rack
|
model = Rack
|
||||||
fields = ('site', 'group', 'name', 'facility_id', 'tenant', 'u_height')
|
fields = ('name', 'site', 'group', 'facility_id', 'tenant', 'u_height')
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -9,8 +9,8 @@ from extras.forms import CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFi
|
|||||||
from tenancy.forms import TenancyForm
|
from tenancy.forms import TenancyForm
|
||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
from utilities.forms import (
|
from utilities.forms import (
|
||||||
APISelect, BootstrapMixin, BulkEditNullBooleanSelect, ChainedModelChoiceField, ExpandableIPAddressField,
|
APISelect, BootstrapMixin, BulkEditNullBooleanSelect, ChainedModelChoiceField, CSVChoiceField,
|
||||||
FilterChoiceField, Livesearch, ReturnURLForm, SlugField, add_blank_choice,
|
ExpandableIPAddressField, FilterChoiceField, Livesearch, ReturnURLForm, SlugField, add_blank_choice,
|
||||||
)
|
)
|
||||||
from .models import (
|
from .models import (
|
||||||
Aggregate, IPAddress, IPADDRESS_STATUS_CHOICES, Prefix, PREFIX_STATUS_CHOICES, RIR, Role, Service, VLAN,
|
Aggregate, IPAddress, IPADDRESS_STATUS_CHOICES, Prefix, PREFIX_STATUS_CHOICES, RIR, Role, Service, VLAN,
|
||||||
@ -238,7 +238,8 @@ class PrefixCSVForm(forms.ModelForm):
|
|||||||
help_text='Numeric ID of assigned VLAN',
|
help_text='Numeric ID of assigned VLAN',
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
status = forms.CharField(
|
status = CSVChoiceField(
|
||||||
|
choices=IPADDRESS_STATUS_CHOICES,
|
||||||
help_text='Status name'
|
help_text='Status name'
|
||||||
)
|
)
|
||||||
role = forms.ModelChoiceField(
|
role = forms.ModelChoiceField(
|
||||||
@ -289,13 +290,6 @@ class PrefixCSVForm(forms.ModelForm):
|
|||||||
except VLAN.MultipleObjectsReturned:
|
except VLAN.MultipleObjectsReturned:
|
||||||
self.add_error('vlan_vid', "Multiple VLANs found ({} - VID {})".format(site, vlan_vid))
|
self.add_error('vlan_vid', "Multiple VLANs found ({} - VID {})".format(site, vlan_vid))
|
||||||
|
|
||||||
def clean_status(self):
|
|
||||||
status_choices = {s[1].lower(): s[0] for s in PREFIX_STATUS_CHOICES}
|
|
||||||
try:
|
|
||||||
return status_choices[self.cleaned_data['status'].lower()]
|
|
||||||
except KeyError:
|
|
||||||
raise ValidationError("Invalid status: {}".format(self.cleaned_data['status']))
|
|
||||||
|
|
||||||
|
|
||||||
class PrefixBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
|
class PrefixBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
|
||||||
pk = forms.ModelMultipleChoiceField(queryset=Prefix.objects.all(), widget=forms.MultipleHiddenInput)
|
pk = forms.ModelMultipleChoiceField(queryset=Prefix.objects.all(), widget=forms.MultipleHiddenInput)
|
||||||
@ -567,7 +561,8 @@ class IPAddressCSVForm(forms.ModelForm):
|
|||||||
'invalid_choice': 'Tenant not found.',
|
'invalid_choice': 'Tenant not found.',
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
status = forms.CharField(
|
status = CSVChoiceField(
|
||||||
|
choices=PREFIX_STATUS_CHOICES,
|
||||||
help_text='Status name'
|
help_text='Status name'
|
||||||
)
|
)
|
||||||
device = forms.ModelChoiceField(
|
device = forms.ModelChoiceField(
|
||||||
@ -613,13 +608,6 @@ class IPAddressCSVForm(forms.ModelForm):
|
|||||||
if is_primary and not device:
|
if is_primary and not device:
|
||||||
self.add_error('is_primary', "No device specified; cannot set as primary IP")
|
self.add_error('is_primary', "No device specified; cannot set as primary IP")
|
||||||
|
|
||||||
def clean_status(self):
|
|
||||||
status_choices = {s[1].lower(): s[0] for s in IPADDRESS_STATUS_CHOICES}
|
|
||||||
try:
|
|
||||||
return status_choices[self.cleaned_data['status'].lower()]
|
|
||||||
except KeyError:
|
|
||||||
raise ValidationError("Invalid status: {}".format(self.cleaned_data['status']))
|
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
|
|
||||||
# Set interface
|
# Set interface
|
||||||
@ -756,7 +744,8 @@ class VLANCSVForm(forms.ModelForm):
|
|||||||
'invalid_choice': 'Tenant not found.',
|
'invalid_choice': 'Tenant not found.',
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
status = forms.CharField(
|
status = CSVChoiceField(
|
||||||
|
choices=VLAN_STATUS_CHOICES,
|
||||||
help_text='Status name'
|
help_text='Status name'
|
||||||
)
|
)
|
||||||
role = forms.ModelChoiceField(
|
role = forms.ModelChoiceField(
|
||||||
@ -783,13 +772,6 @@ class VLANCSVForm(forms.ModelForm):
|
|||||||
except VLANGroup.DoesNotExist:
|
except VLANGroup.DoesNotExist:
|
||||||
self.add_error('group_name', "Invalid VLAN group {}.".format(group_name))
|
self.add_error('group_name', "Invalid VLAN group {}.".format(group_name))
|
||||||
|
|
||||||
def clean_status(self):
|
|
||||||
status_choices = {s[1].lower(): s[0] for s in VLAN_STATUS_CHOICES}
|
|
||||||
try:
|
|
||||||
return status_choices[self.cleaned_data['status'].lower()]
|
|
||||||
except KeyError:
|
|
||||||
raise ValidationError("Invalid status: {}".format(self.cleaned_data['status']))
|
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
|
|
||||||
vlan = super(VLANCSVForm, self).save(commit=False)
|
vlan = super(VLANCSVForm, self).save(commit=False)
|
||||||
|
@ -45,7 +45,7 @@
|
|||||||
<td>
|
<td>
|
||||||
{{ field.help_text|default:field.label }}
|
{{ field.help_text|default:field.label }}
|
||||||
{% if field.choices %}
|
{% if field.choices %}
|
||||||
<br /><small class="text-muted">Examples: {{ field.choices|example_choices }}</small>
|
<br /><small class="text-muted">Choices: {{ field.choices|example_choices }}</small>
|
||||||
{% elif field|widget_type == 'dateinput' %}
|
{% elif field|widget_type == 'dateinput' %}
|
||||||
<br /><small class="text-muted">Format: YYYY-MM-DD</small>
|
<br /><small class="text-muted">Format: YYYY-MM-DD</small>
|
||||||
{% elif field|widget_type == 'checkboxinput' %}
|
{% elif field|widget_type == 'checkboxinput' %}
|
||||||
|
@ -271,6 +271,25 @@ class CSVDataField(forms.CharField):
|
|||||||
return records
|
return records
|
||||||
|
|
||||||
|
|
||||||
|
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(CSVChoiceField, self).__init__(choices, *args, **kwargs)
|
||||||
|
self.choices = [(label, label) for value, label in choices]
|
||||||
|
self.choice_values = {label: value for value, label in choices}
|
||||||
|
|
||||||
|
def clean(self, value):
|
||||||
|
value = super(CSVChoiceField, self).clean(value)
|
||||||
|
if not value:
|
||||||
|
return None
|
||||||
|
if value not in self.choice_values:
|
||||||
|
raise forms.ValidationError("Invalid choice: {}".format(value))
|
||||||
|
return self.choice_values[value]
|
||||||
|
|
||||||
|
|
||||||
class ExpandableNameField(forms.CharField):
|
class ExpandableNameField(forms.CharField):
|
||||||
"""
|
"""
|
||||||
A field which allows for numeric range expansion
|
A field which allows for numeric range expansion
|
||||||
|
@ -75,7 +75,7 @@ def example_choices(value, arg=3):
|
|||||||
if not id:
|
if not id:
|
||||||
continue
|
continue
|
||||||
choices.append(label)
|
choices.append(label)
|
||||||
return ', '.join(choices) or 'None found'
|
return ', '.join(choices) or 'None'
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
Reference in New Issue
Block a user