diff --git a/netbox/extras/constants.py b/netbox/extras/constants.py index 8a615c076..39daa572b 100644 --- a/netbox/extras/constants.py +++ b/netbox/extras/constants.py @@ -3,11 +3,11 @@ from __future__ import unicode_literals # Models which support custom fields CUSTOMFIELD_MODELS = ( - 'provider', 'circuit', # Circuits - 'site', 'rack', 'devicetype', 'device', # DCIM - 'aggregate', 'prefix', 'ipaddress', 'vlan', 'vrf', # IPAM - 'tenant', # Tenancy - 'cluster', 'virtualmachine', # Virtualization + 'provider', 'circuit', # Circuits + 'site', 'rack', 'devicetype', 'device', # DCIM + 'aggregate', 'prefix', 'ipaddress', 'vlan', 'vrf', 'service', # IPAM + 'tenant', # Tenancy + 'cluster', 'virtualmachine', # Virtualization ) # Custom field types diff --git a/netbox/ipam/api/serializers.py b/netbox/ipam/api/serializers.py index f7969fbc3..bfbc66be3 100644 --- a/netbox/ipam/api/serializers.py +++ b/netbox/ipam/api/serializers.py @@ -301,7 +301,7 @@ class AvailableIPSerializer(serializers.Serializer): # Services # -class ServiceSerializer(ValidatedModelSerializer): +class ServiceSerializer(CustomFieldModelSerializer): device = NestedDeviceSerializer(required=False, allow_null=True) virtual_machine = NestedVirtualMachineSerializer(required=False, allow_null=True) protocol = ChoiceFieldSerializer(choices=IP_PROTOCOL_CHOICES) @@ -315,6 +315,6 @@ class ServiceSerializer(ValidatedModelSerializer): class Meta: model = Service fields = [ - 'id', 'device', 'virtual_machine', 'name', 'port', 'protocol', 'ipaddresses', 'description', 'created', - 'last_updated', + 'id', 'device', 'virtual_machine', 'name', 'port', 'protocol', 'ipaddresses', 'description', + 'custom_fields', 'created', 'last_updated', ] diff --git a/netbox/ipam/forms.py b/netbox/ipam/forms.py index a521549f9..2b5f8ed7a 100644 --- a/netbox/ipam/forms.py +++ b/netbox/ipam/forms.py @@ -11,9 +11,9 @@ from extras.forms import CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFi from tenancy.forms import TenancyForm from tenancy.models import Tenant from utilities.forms import ( - AnnotatedMultipleChoiceField, APISelect, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect, - ChainedModelChoiceField, CSVChoiceField, ExpandableIPAddressField, FilterChoiceField, FlexibleModelChoiceField, - Livesearch, ReturnURLForm, SlugField, add_blank_choice, + AnnotatedMultipleChoiceField, APISelect, BootstrapMixin, BulkEditNullBooleanSelect, ChainedModelChoiceField, + CSVChoiceField, ExpandableIPAddressField, FilterChoiceField, FlexibleModelChoiceField, Livesearch, ReturnURLForm, + SlugField, add_blank_choice, ) from virtualization.models import VirtualMachine from .constants import ( @@ -917,7 +917,7 @@ class VLANFilterForm(BootstrapMixin, CustomFieldFilterForm): # Services # -class ServiceForm(BootstrapMixin, forms.ModelForm): +class ServiceForm(BootstrapMixin, CustomFieldForm): class Meta: model = Service @@ -947,7 +947,10 @@ class ServiceForm(BootstrapMixin, forms.ModelForm): class ServiceFilterForm(BootstrapMixin, CustomFieldFilterForm): model = Service - q = forms.CharField(required=False, label='Search') + q = forms.CharField( + required=False, + label='Search' + ) protocol = forms.ChoiceField( choices=add_blank_choice(IP_PROTOCOL_CHOICES), required=False @@ -957,7 +960,7 @@ class ServiceFilterForm(BootstrapMixin, CustomFieldFilterForm): ) -class ServiceBulkEditForm(BootstrapMixin, BulkEditForm): +class ServiceBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm): pk = forms.ModelMultipleChoiceField(queryset=Service.objects.all(), widget=forms.MultipleHiddenInput) protocol = forms.ChoiceField(choices=add_blank_choice(IP_PROTOCOL_CHOICES), required=False) port = forms.IntegerField(validators=[MinValueValidator(1), MaxValueValidator(65535)], required=False) diff --git a/netbox/ipam/models.py b/netbox/ipam/models.py index 736160fd8..70713cd88 100644 --- a/netbox/ipam/models.py +++ b/netbox/ipam/models.py @@ -835,7 +835,7 @@ class VLAN(CreatedUpdatedModel, CustomFieldModel): @python_2_unicode_compatible -class Service(CreatedUpdatedModel): +class Service(CreatedUpdatedModel, CustomFieldModel): """ A Service represents a layer-four service (e.g. HTTP or SSH) running on a Device or VirtualMachine. A Service may optionally be tied to one or more specific IPAddresses belonging to its parent. @@ -875,6 +875,11 @@ class Service(CreatedUpdatedModel): max_length=100, blank=True ) + custom_field_values = GenericRelation( + to='extras.CustomFieldValue', + content_type_field='obj_type', + object_id_field='obj_id' + ) serializer = 'ipam.api.serializers.ServiceSerializer' diff --git a/netbox/templates/ipam/service_edit.html b/netbox/templates/ipam/service_edit.html index e1db968dd..b3cf5571a 100644 --- a/netbox/templates/ipam/service_edit.html +++ b/netbox/templates/ipam/service_edit.html @@ -32,4 +32,12 @@ {% render_field form.description %} + {% if form.custom_fields %} +
+
Custom Fields
+
+ {% render_custom_fields form %} +
+
+ {% endif %} {% endblock %} diff --git a/netbox/templates/ipam/service_list.html b/netbox/templates/ipam/service_list.html index 840b071bd..c78b2bba2 100644 --- a/netbox/templates/ipam/service_list.html +++ b/netbox/templates/ipam/service_list.html @@ -1,6 +1,4 @@ {% extends '_base.html' %} -{% load buttons %} -{% load humanize %} {% block content %}

{% block title %}Services{% endblock %}