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 utilities.forms import (
|
||||
APISelect, add_blank_choice, ArrayFieldSelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect,
|
||||
ChainedFieldsMixin, ChainedModelChoiceField, CommentField, ExpandableNameField, FilterChoiceField,
|
||||
ChainedFieldsMixin, ChainedModelChoiceField, CommentField, CSVChoiceField, ExpandableNameField, FilterChoiceField,
|
||||
FlexibleModelChoiceField, Livesearch, SelectWithDisabled, SmallTextarea, SlugField,
|
||||
FilterTreeNodeMultipleChoiceField,
|
||||
)
|
||||
@ -24,8 +24,9 @@ from .models import (
|
||||
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceRole, DeviceType,
|
||||
Interface, IFACE_FF_CHOICES, IFACE_FF_LAG, IFACE_ORDERING_CHOICES, InterfaceConnection, InterfaceTemplate,
|
||||
Manufacturer, InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate,
|
||||
RACK_TYPE_CHOICES, RACK_WIDTH_CHOICES, Rack, RackGroup, RackReservation, RackRole, Region, Site, STATUS_CHOICES,
|
||||
SUBDEVICE_ROLE_CHILD, SUBDEVICE_ROLE_PARENT, VIRTUAL_IFACE_TYPES,
|
||||
RACK_FACE_CHOICES, RACK_TYPE_CHOICES, RACK_WIDTH_CHOICES, Rack, RackGroup, RackReservation, RackRole,
|
||||
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
|
||||
|
||||
|
||||
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):
|
||||
"""
|
||||
Allow inclusion of the parent device as context for limiting field choices.
|
||||
@ -256,7 +232,7 @@ class RackCSVForm(forms.ModelForm):
|
||||
queryset=RackGroup.objects.all(),
|
||||
to_field_name='name',
|
||||
required=False,
|
||||
help_text='Name of parent group',
|
||||
help_text='Name of parent rack group',
|
||||
error_messages={
|
||||
'invalid_choice': 'Rack group not found.',
|
||||
}
|
||||
@ -279,15 +255,24 @@ class RackCSVForm(forms.ModelForm):
|
||||
'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:
|
||||
model = Rack
|
||||
fields = [
|
||||
'site', 'group', 'name', 'facility_id', 'tenant', 'role', 'type', 'width', 'u_height', 'desc_units',
|
||||
]
|
||||
help_texts = {
|
||||
'type': 'Rack type',
|
||||
}
|
||||
|
||||
def clean_group(self):
|
||||
|
||||
@ -297,21 +282,6 @@ class RackCSVForm(forms.ModelForm):
|
||||
if group and group.site != site:
|
||||
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):
|
||||
pk = forms.ModelMultipleChoiceField(queryset=Rack.objects.all(), widget=forms.MultipleHiddenInput)
|
||||
@ -752,8 +722,9 @@ class BaseDeviceCSVForm(forms.ModelForm):
|
||||
'invalid_choice': 'Invalid platform.',
|
||||
}
|
||||
)
|
||||
status = forms.CharField(
|
||||
help_text='Status name'
|
||||
status = CSVChoiceField(
|
||||
choices=STATUS_CHOICES,
|
||||
help_text='Operational status of device'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
@ -772,13 +743,6 @@ class BaseDeviceCSVForm(forms.ModelForm):
|
||||
except DeviceType.DoesNotExist:
|
||||
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):
|
||||
site = forms.ModelChoiceField(
|
||||
@ -793,9 +757,10 @@ class DeviceCSVForm(BaseDeviceCSVForm):
|
||||
required=False,
|
||||
help_text='Name of parent rack'
|
||||
)
|
||||
face = forms.CharField(
|
||||
face = CSVChoiceField(
|
||||
choices=RACK_FACE_CHOICES,
|
||||
required=False,
|
||||
help_text='Mounted rack face (front or rear)'
|
||||
help_text='Mounted rack face'
|
||||
)
|
||||
|
||||
class Meta(BaseDeviceCSVForm.Meta):
|
||||
@ -991,7 +956,10 @@ class ConsoleConnectionCSVForm(forms.ModelForm):
|
||||
console_port = forms.CharField(
|
||||
help_text='Console port name'
|
||||
)
|
||||
connection_status = ConnectionStatusCSVField()
|
||||
connection_status = CSVChoiceField(
|
||||
choices=CONNECTION_STATUS_CHOICES,
|
||||
help_text='Connection status'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = ConsolePort
|
||||
@ -1245,7 +1213,10 @@ class PowerConnectionCSVForm(forms.ModelForm):
|
||||
power_port = forms.CharField(
|
||||
help_text='Power port name'
|
||||
)
|
||||
connection_status = ConnectionStatusCSVField()
|
||||
connection_status = CSVChoiceField(
|
||||
choices=CONNECTION_STATUS_CHOICES,
|
||||
help_text='Connection status'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = PowerPort
|
||||
@ -1645,7 +1616,10 @@ class InterfaceConnectionCSVForm(forms.ModelForm):
|
||||
interface_b = forms.CharField(
|
||||
help_text='Interface name'
|
||||
)
|
||||
connection_status = ConnectionStatusCSVField()
|
||||
connection_status = CSVChoiceField(
|
||||
choices=CONNECTION_STATUS_CHOICES,
|
||||
help_text='Connection status'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = InterfaceConnection
|
||||
|
@ -346,7 +346,7 @@ class RackGroup(models.Model):
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return '{} - {}'.format(self.site.name, self.name)
|
||||
return self.name
|
||||
|
||||
def get_absolute_url(self):
|
||||
return "{}?group_id={}".format(reverse('dcim:rack_list'), self.pk)
|
||||
|
@ -247,7 +247,7 @@ class RackImportTable(BaseTable):
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
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.models import Tenant
|
||||
from utilities.forms import (
|
||||
APISelect, BootstrapMixin, BulkEditNullBooleanSelect, ChainedModelChoiceField, ExpandableIPAddressField,
|
||||
FilterChoiceField, Livesearch, ReturnURLForm, SlugField, add_blank_choice,
|
||||
APISelect, BootstrapMixin, BulkEditNullBooleanSelect, ChainedModelChoiceField, CSVChoiceField,
|
||||
ExpandableIPAddressField, FilterChoiceField, Livesearch, ReturnURLForm, SlugField, add_blank_choice,
|
||||
)
|
||||
from .models import (
|
||||
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',
|
||||
required=False
|
||||
)
|
||||
status = forms.CharField(
|
||||
status = CSVChoiceField(
|
||||
choices=IPADDRESS_STATUS_CHOICES,
|
||||
help_text='Status name'
|
||||
)
|
||||
role = forms.ModelChoiceField(
|
||||
@ -289,13 +290,6 @@ class PrefixCSVForm(forms.ModelForm):
|
||||
except VLAN.MultipleObjectsReturned:
|
||||
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):
|
||||
pk = forms.ModelMultipleChoiceField(queryset=Prefix.objects.all(), widget=forms.MultipleHiddenInput)
|
||||
@ -567,7 +561,8 @@ class IPAddressCSVForm(forms.ModelForm):
|
||||
'invalid_choice': 'Tenant not found.',
|
||||
}
|
||||
)
|
||||
status = forms.CharField(
|
||||
status = CSVChoiceField(
|
||||
choices=PREFIX_STATUS_CHOICES,
|
||||
help_text='Status name'
|
||||
)
|
||||
device = forms.ModelChoiceField(
|
||||
@ -613,13 +608,6 @@ class IPAddressCSVForm(forms.ModelForm):
|
||||
if is_primary and not device:
|
||||
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):
|
||||
|
||||
# Set interface
|
||||
@ -756,7 +744,8 @@ class VLANCSVForm(forms.ModelForm):
|
||||
'invalid_choice': 'Tenant not found.',
|
||||
}
|
||||
)
|
||||
status = forms.CharField(
|
||||
status = CSVChoiceField(
|
||||
choices=VLAN_STATUS_CHOICES,
|
||||
help_text='Status name'
|
||||
)
|
||||
role = forms.ModelChoiceField(
|
||||
@ -783,13 +772,6 @@ class VLANCSVForm(forms.ModelForm):
|
||||
except VLANGroup.DoesNotExist:
|
||||
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):
|
||||
|
||||
vlan = super(VLANCSVForm, self).save(commit=False)
|
||||
|
@ -45,7 +45,7 @@
|
||||
<td>
|
||||
{{ field.help_text|default:field.label }}
|
||||
{% 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' %}
|
||||
<br /><small class="text-muted">Format: YYYY-MM-DD</small>
|
||||
{% elif field|widget_type == 'checkboxinput' %}
|
||||
|
@ -271,6 +271,25 @@ class CSVDataField(forms.CharField):
|
||||
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):
|
||||
"""
|
||||
A field which allows for numeric range expansion
|
||||
|
@ -75,7 +75,7 @@ def example_choices(value, arg=3):
|
||||
if not id:
|
||||
continue
|
||||
choices.append(label)
|
||||
return ', '.join(choices) or 'None found'
|
||||
return ', '.join(choices) or 'None'
|
||||
|
||||
|
||||
#
|
||||
|
Reference in New Issue
Block a user