Parcourir la source

Replaced all CSVForm ChoiceFields with CSVChoiceField

Jeremy Stretch il y a 7 ans
Parent
commit
e6c4ce51f7

+ 35 - 61
netbox/dcim/forms.py

@@ -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,
-    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
     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(
-        help_text='Status name'
+    status = CSVChoiceField(
+        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

+ 1 - 1
netbox/dcim/models.py

@@ -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)

+ 1 - 1
netbox/dcim/tables.py

@@ -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')
 
 
 
 
 #
 #

+ 8 - 26
netbox/ipam/forms.py

@@ -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,
-    FilterChoiceField, Livesearch, ReturnURLForm, SlugField, add_blank_choice,
+    APISelect, BootstrapMixin, BulkEditNullBooleanSelect, ChainedModelChoiceField, CSVChoiceField,
+    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)

+ 1 - 1
netbox/templates/utilities/obj_import.html

@@ -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' %}

+ 19 - 0
netbox/utilities/forms.py

@@ -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

+ 1 - 1
netbox/utilities/templatetags/helpers.py

@@ -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'
 
 
 
 
 #
 #