From 7819d9c112ec539d674c46ff7b2778825244e50a Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 21 Jun 2018 15:55:27 -0400 Subject: [PATCH] Closes #1673: Added object/list views for services --- netbox/ipam/filters.py | 10 +++ netbox/ipam/forms.py | 33 ++++++++-- netbox/ipam/models.py | 3 + netbox/ipam/tables.py | 18 ++++- netbox/ipam/urls.py | 4 ++ netbox/ipam/views.py | 41 +++++++++++- netbox/templates/inc/nav_menu.html | 5 ++ netbox/templates/ipam/inc/service.html | 4 +- netbox/templates/ipam/service.html | 88 +++++++++++++++++++++++++ netbox/templates/ipam/service_list.html | 15 +++++ 10 files changed, 212 insertions(+), 9 deletions(-) create mode 100644 netbox/templates/ipam/service.html create mode 100644 netbox/templates/ipam/service_list.html diff --git a/netbox/ipam/filters.py b/netbox/ipam/filters.py index db2806b77..487970b49 100644 --- a/netbox/ipam/filters.py +++ b/netbox/ipam/filters.py @@ -426,6 +426,10 @@ class VLANFilter(CustomFieldFilterSet, django_filters.FilterSet): class ServiceFilter(django_filters.FilterSet): + q = django_filters.CharFilter( + method='search', + label='Search', + ) device_id = django_filters.ModelMultipleChoiceFilter( queryset=Device.objects.all(), label='Device (ID)', @@ -450,3 +454,9 @@ class ServiceFilter(django_filters.FilterSet): class Meta: model = Service fields = ['name', 'protocol', 'port'] + + def search(self, queryset, name, value): + if not value.strip(): + return queryset + qs_filter = Q(name__icontains=value) | Q(description__icontains=value) + return queryset.filter(qs_filter) diff --git a/netbox/ipam/forms.py b/netbox/ipam/forms.py index 82ebfe724..a521549f9 100644 --- a/netbox/ipam/forms.py +++ b/netbox/ipam/forms.py @@ -2,6 +2,7 @@ from __future__ import unicode_literals from django import forms from django.core.exceptions import MultipleObjectsReturned +from django.core.validators import MaxValueValidator, MinValueValidator from django.db.models import Count from taggit.forms import TagField @@ -10,12 +11,14 @@ from extras.forms import CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFi from tenancy.forms import TenancyForm from tenancy.models import Tenant from utilities.forms import ( - AnnotatedMultipleChoiceField, APISelect, BootstrapMixin, BulkEditNullBooleanSelect, ChainedModelChoiceField, - CSVChoiceField, ExpandableIPAddressField, FilterChoiceField, FlexibleModelChoiceField, Livesearch, ReturnURLForm, - SlugField, add_blank_choice, + AnnotatedMultipleChoiceField, APISelect, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect, + ChainedModelChoiceField, CSVChoiceField, ExpandableIPAddressField, FilterChoiceField, FlexibleModelChoiceField, + Livesearch, ReturnURLForm, SlugField, add_blank_choice, ) from virtualization.models import VirtualMachine -from .constants import IPADDRESS_ROLE_CHOICES, IPADDRESS_STATUS_CHOICES, PREFIX_STATUS_CHOICES, VLAN_STATUS_CHOICES +from .constants import ( + IP_PROTOCOL_CHOICES, IPADDRESS_ROLE_CHOICES, IPADDRESS_STATUS_CHOICES, PREFIX_STATUS_CHOICES, VLAN_STATUS_CHOICES, +) from .models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF IP_FAMILY_CHOICES = [ @@ -940,3 +943,25 @@ class ServiceForm(BootstrapMixin, forms.ModelForm): ) else: self.fields['ipaddresses'].choices = [] + + +class ServiceFilterForm(BootstrapMixin, CustomFieldFilterForm): + model = Service + q = forms.CharField(required=False, label='Search') + protocol = forms.ChoiceField( + choices=add_blank_choice(IP_PROTOCOL_CHOICES), + required=False + ) + port = forms.IntegerField( + required=False + ) + + +class ServiceBulkEditForm(BootstrapMixin, BulkEditForm): + 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) + description = forms.CharField(max_length=100, required=False) + + class Meta: + nullable_fields = ['site', 'group', 'tenant', 'role', 'description'] diff --git a/netbox/ipam/models.py b/netbox/ipam/models.py index a3d8736c1..736160fd8 100644 --- a/netbox/ipam/models.py +++ b/netbox/ipam/models.py @@ -884,6 +884,9 @@ class Service(CreatedUpdatedModel): def __str__(self): return '{} ({}/{})'.format(self.name, self.port, self.get_protocol_display()) + def get_absolute_url(self): + return reverse('ipam:service', args=[self.pk]) + @property def parent(self): return self.device or self.virtual_machine diff --git a/netbox/ipam/tables.py b/netbox/ipam/tables.py index 08035d549..7f8b8918d 100644 --- a/netbox/ipam/tables.py +++ b/netbox/ipam/tables.py @@ -6,7 +6,7 @@ from django_tables2.utils import Accessor from dcim.models import Interface from tenancy.tables import COL_TENANT from utilities.tables import BaseTable, ToggleColumn -from .models import Aggregate, IPAddress, Prefix, RIR, Role, VLAN, VLANGroup, VRF +from .models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF RIR_UTILIZATION = """
@@ -392,3 +392,19 @@ class VLANMemberTable(BaseTable): class Meta(BaseTable.Meta): model = Interface fields = ('parent', 'name', 'untagged', 'actions') + + +# +# Services +# + +class ServiceTable(BaseTable): + pk = ToggleColumn() + name = tables.LinkColumn( + viewname='ipam:service', + args=[Accessor('pk')] + ) + + class Meta(BaseTable.Meta): + model = Service + fields = ('pk', 'name', 'parent', 'protocol', 'port', 'description') diff --git a/netbox/ipam/urls.py b/netbox/ipam/urls.py index aa7c17a5c..d22c32561 100644 --- a/netbox/ipam/urls.py +++ b/netbox/ipam/urls.py @@ -85,6 +85,10 @@ urlpatterns = [ url(r'^vlans/(?P\d+)/delete/$', views.VLANDeleteView.as_view(), name='vlan_delete'), # Services + url(r'^services/$', views.ServiceListView.as_view(), name='service_list'), + url(r'^services/edit/$', views.ServiceBulkEditView.as_view(), name='service_bulk_edit'), + url(r'^services/delete/$', views.ServiceBulkDeleteView.as_view(), name='service_bulk_delete'), + url(r'^services/(?P\d+)/$', views.ServiceView.as_view(), name='service'), url(r'^services/(?P\d+)/edit/$', views.ServiceEditView.as_view(), name='service_edit'), url(r'^services/(?P\d+)/delete/$', views.ServiceDeleteView.as_view(), name='service_delete'), diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index 1d4575e34..e60d1bd79 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -931,6 +931,25 @@ class VLANBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): # Services # +class ServiceListView(ObjectListView): + queryset = Service.objects.select_related('device', 'virtual_machine') + filter = filters.ServiceFilter + filter_form = forms.ServiceFilterForm + table = tables.ServiceTable + template_name = 'ipam/service_list.html' + + +class ServiceView(View): + + def get(self, request, pk): + + service = get_object_or_404(Service, pk=pk) + + return render(request, 'ipam/service.html', { + 'service': service, + }) + + class ServiceCreateView(PermissionRequiredMixin, ObjectEditView): permission_required = 'ipam.add_service' model = Service @@ -944,9 +963,6 @@ class ServiceCreateView(PermissionRequiredMixin, ObjectEditView): obj.virtual_machine = get_object_or_404(VirtualMachine, pk=url_kwargs['virtualmachine']) return obj - def get_return_url(self, request, obj): - return obj.parent.get_absolute_url() - class ServiceEditView(ServiceCreateView): permission_required = 'ipam.change_service' @@ -955,3 +971,22 @@ class ServiceEditView(ServiceCreateView): class ServiceDeleteView(PermissionRequiredMixin, ObjectDeleteView): permission_required = 'ipam.delete_service' model = Service + + +class ServiceBulkEditView(PermissionRequiredMixin, BulkEditView): + permission_required = 'ipam.change_service' + cls = Service + queryset = Service.objects.all() + filter = filters.ServiceFilter + table = tables.ServiceTable + form = forms.ServiceBulkEditForm + default_return_url = 'ipam:service_list' + + +class ServiceBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): + permission_required = 'ipam.delete_service' + cls = Service + queryset = Service.objects.all() + filter = filters.ServiceFilter + table = tables.ServiceTable + default_return_url = 'ipam:service_list' diff --git a/netbox/templates/inc/nav_menu.html b/netbox/templates/inc/nav_menu.html index 2c47ad85b..7b9e6ac3c 100644 --- a/netbox/templates/inc/nav_menu.html +++ b/netbox/templates/inc/nav_menu.html @@ -283,6 +283,11 @@ {% endif %} VLAN Groups +
  • + +
  • + Services +