diff --git a/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py index ef7a4be605..1e0cdbe4a1 100644 --- a/netbox/dcim/api/serializers.py +++ b/netbox/dcim/api/serializers.py @@ -130,14 +130,14 @@ class Meta(ManufacturerSerializer.Meta): # Device types # -class DeviceTypeSerializer(serializers.ModelSerializer): +class DeviceTypeSerializer(CustomFieldSerializer, serializers.ModelSerializer): manufacturer = ManufacturerNestedSerializer() subdevice_role = serializers.SerializerMethodField() class Meta: model = DeviceType fields = ['id', 'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', - 'is_console_server', 'is_pdu', 'is_network_device', 'subdevice_role'] + 'is_console_server', 'is_pdu', 'is_network_device', 'subdevice_role', 'custom_fields'] def get_subdevice_role(self, obj): return { @@ -197,8 +197,9 @@ class DeviceTypeDetailSerializer(DeviceTypeSerializer): class Meta(DeviceTypeSerializer.Meta): fields = ['id', 'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', - 'is_console_server', 'is_pdu', 'is_network_device', 'console_port_templates', 'cs_port_templates', - 'power_port_templates', 'power_outlet_templates', 'interface_templates'] + 'is_console_server', 'is_pdu', 'is_network_device', 'subdevice_role', 'custom_fields', + 'console_port_templates', 'cs_port_templates', 'power_port_templates', 'power_outlet_templates', + 'interface_templates'] # diff --git a/netbox/dcim/api/views.py b/netbox/dcim/api/views.py index 0322208eec..621f937fba 100644 --- a/netbox/dcim/api/views.py +++ b/netbox/dcim/api/views.py @@ -152,20 +152,20 @@ class ManufacturerDetailView(generics.RetrieveAPIView): # Device Types # -class DeviceTypeListView(generics.ListAPIView): +class DeviceTypeListView(CustomFieldModelAPIView, generics.ListAPIView): """ List device types (filterable) """ - queryset = DeviceType.objects.select_related('manufacturer') + queryset = DeviceType.objects.select_related('manufacturer').prefetch_related('custom_field_values__field') serializer_class = serializers.DeviceTypeSerializer filter_class = filters.DeviceTypeFilter -class DeviceTypeDetailView(generics.RetrieveAPIView): +class DeviceTypeDetailView(CustomFieldModelAPIView, generics.RetrieveAPIView): """ Retrieve a single device type """ - queryset = DeviceType.objects.select_related('manufacturer') + queryset = DeviceType.objects.select_related('manufacturer').prefetch_related('custom_field_values__field') serializer_class = serializers.DeviceTypeDetailSerializer diff --git a/netbox/dcim/filters.py b/netbox/dcim/filters.py index 4cf53c303f..8cd2735498 100644 --- a/netbox/dcim/filters.py +++ b/netbox/dcim/filters.py @@ -123,7 +123,7 @@ def search(self, queryset, value): ) -class DeviceTypeFilter(django_filters.FilterSet): +class DeviceTypeFilter(CustomFieldFilterSet, django_filters.FilterSet): manufacturer_id = django_filters.ModelMultipleChoiceFilter( name='manufacturer', queryset=Manufacturer.objects.all(), diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index f5cec6474e..b06d5297f4 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -254,7 +254,7 @@ class Meta: # Device types # -class DeviceTypeForm(forms.ModelForm, BootstrapMixin): +class DeviceTypeForm(BootstrapMixin, CustomFieldForm): slug = SlugField(slug_source='model') class Meta: @@ -263,7 +263,7 @@ class Meta: 'is_pdu', 'is_network_device', 'subdevice_role'] -class DeviceTypeBulkEditForm(BulkEditForm, BootstrapMixin): +class DeviceTypeBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm): pk = forms.ModelMultipleChoiceField(queryset=DeviceType.objects.all(), widget=forms.MultipleHiddenInput) manufacturer = forms.ModelChoiceField(queryset=Manufacturer.objects.all(), required=False) u_height = forms.IntegerField(min_value=1, required=False) @@ -272,7 +272,8 @@ class Meta: nullable_fields = [] -class DeviceTypeFilterForm(forms.Form, BootstrapMixin): +class DeviceTypeFilterForm(BootstrapMixin, CustomFieldFilterForm): + model = DeviceType manufacturer = FilterChoiceField(queryset=Manufacturer.objects.annotate(filter_count=Count('device_types')), to_field_name='slug') diff --git a/netbox/dcim/models.py b/netbox/dcim/models.py index 784f4ef02f..17bea9979d 100644 --- a/netbox/dcim/models.py +++ b/netbox/dcim/models.py @@ -521,7 +521,7 @@ def get_absolute_url(self): return "{}?manufacturer={}".format(reverse('dcim:devicetype_list'), self.slug) -class DeviceType(models.Model): +class DeviceType(models.Model, CustomFieldModel): """ A DeviceType represents a particular make (Manufacturer) and model of device. It specifies rack height and depth, as well as high-level functional role(s). @@ -553,6 +553,7 @@ class DeviceType(models.Model): choices=SUBDEVICE_ROLE_CHOICES, help_text="Parent devices house child devices in device bays. Select " "\"None\" if this device type is neither a parent nor a child.") + custom_field_values = GenericRelation(CustomFieldValue, content_type_field='obj_type', object_id_field='obj_id') class Meta: ordering = ['manufacturer', 'model'] diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index b586604a4a..6b955e8da7 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -331,6 +331,7 @@ class DeviceTypeEditView(PermissionRequiredMixin, ObjectEditView): permission_required = 'dcim.change_devicetype' model = DeviceType form_class = forms.DeviceTypeForm + template_name = 'dcim/devicetype_edit.html' obj_list_url = 'dcim:devicetype_list' diff --git a/netbox/extras/forms.py b/netbox/extras/forms.py index 9d738219da..497d204a69 100644 --- a/netbox/extras/forms.py +++ b/netbox/extras/forms.py @@ -44,7 +44,7 @@ def get_custom_fields_for_model(content_type, filterable_only=False, bulk_edit=F # Date elif cf.type == CF_TYPE_DATE: - field = forms.DateField(required=cf.required, initial=cf.default) + field = forms.DateField(required=cf.required, initial=cf.default, help_text="Date format: YYYY-MM-DD") # Select elif cf.type == CF_TYPE_SELECT: @@ -63,7 +63,8 @@ def get_custom_fields_for_model(content_type, filterable_only=False, bulk_edit=F field.model = cf field.label = cf.label if cf.label else cf.name.replace('_', ' ').capitalize() - field.help_text = cf.description + if cf.description: + field.help_text = cf.description field_dict[field_name] = field diff --git a/netbox/extras/models.py b/netbox/extras/models.py index a65e90834c..d45e4846f9 100644 --- a/netbox/extras/models.py +++ b/netbox/extras/models.py @@ -12,7 +12,7 @@ CUSTOMFIELD_MODELS = ( - 'site', 'rack', 'device', # DCIM + 'site', 'rack', 'devicetype', 'device', # DCIM 'aggregate', 'prefix', 'ipaddress', 'vlan', 'vrf', # IPAM 'provider', 'circuit', # Circuits 'tenant', # Tenants diff --git a/netbox/templates/dcim/devicetype.html b/netbox/templates/dcim/devicetype.html index 7f9cc39dc5..0b324d8287 100644 --- a/netbox/templates/dcim/devicetype.html +++ b/netbox/templates/dcim/devicetype.html @@ -145,6 +145,9 @@

{{ devicetype.manufacturer }} {{ devicetype.model }}

+ {% with devicetype.get_custom_fields as custom_fields %} + {% include 'inc/custom_fields_panel.html' %} + {% endwith %} {% include 'dcim/inc/devicetype_component_table.html' with table=consoleport_table title='Console Ports' add_url='dcim:devicetype_add_consoleport' delete_url='dcim:devicetype_delete_consoleport' %} {% include 'dcim/inc/devicetype_component_table.html' with table=powerport_table title='Power Ports' add_url='dcim:devicetype_add_powerport' delete_url='dcim:devicetype_delete_powerport' %} {% include 'dcim/inc/devicetype_component_table.html' with table=mgmt_interface_table title='Management Interfaces' add_url='dcim:devicetype_add_interface' add_url_extra='?mgmt_only=1' edit_url='dcim:devicetype_bulkedit_interface' delete_url='dcim:devicetype_delete_interface' %} diff --git a/netbox/templates/dcim/devicetype_edit.html b/netbox/templates/dcim/devicetype_edit.html new file mode 100644 index 0000000000..d25e951e35 --- /dev/null +++ b/netbox/templates/dcim/devicetype_edit.html @@ -0,0 +1,28 @@ +{% extends 'utilities/obj_edit.html' %} +{% load form_helpers %} + +{% block form %} +
+
Device Type
+
+ {% render_field form.manufacturer %} + {% render_field form.model %} + {% render_field form.slug %} + {% render_field form.part_number %} + {% render_field form.u_height %} + {% render_field form.is_full_depth %} + {% render_field form.is_console_server %} + {% render_field form.is_pdu %} + {% render_field form.is_network_device %} + {% render_field form.subdevice_role %} +
+
+ {% if form.custom_fields %} +
+
Custom Fields
+
+ {% render_custom_fields form %} +
+
+ {% endif %} +{% endblock %}