Skip to content

Commit

Permalink
Closes #1673: Added object/list views for services
Browse files Browse the repository at this point in the history
  • Loading branch information
jeremystretch committed Jun 21, 2018
1 parent 258373f commit 7819d9c
Show file tree
Hide file tree
Showing 10 changed files with 212 additions and 9 deletions.
10 changes: 10 additions & 0 deletions netbox/ipam/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,10 @@ def search(self, queryset, name, value):


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)',
Expand All @@ -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)
33 changes: 29 additions & 4 deletions netbox/ipam/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

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

Expand All @@ -10,12 +11,14 @@
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 = [
Expand Down Expand Up @@ -940,3 +943,25 @@ def __init__(self, *args, **kwargs):
)
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']
3 changes: 3 additions & 0 deletions netbox/ipam/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -884,6 +884,9 @@ class Meta:
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
Expand Down
18 changes: 17 additions & 1 deletion netbox/ipam/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
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 = """
<div class="progress">
Expand Down Expand Up @@ -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')
4 changes: 4 additions & 0 deletions netbox/ipam/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@
url(r'^vlans/(?P<pk>\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<pk>\d+)/$', views.ServiceView.as_view(), name='service'),
url(r'^services/(?P<pk>\d+)/edit/$', views.ServiceEditView.as_view(), name='service_edit'),
url(r'^services/(?P<pk>\d+)/delete/$', views.ServiceDeleteView.as_view(), name='service_delete'),

Expand Down
41 changes: 38 additions & 3 deletions netbox/ipam/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -944,9 +963,6 @@ def alter_obj(self, obj, request, url_args, url_kwargs):
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'
Expand All @@ -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'
5 changes: 5 additions & 0 deletions netbox/templates/inc/nav_menu.html
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,11 @@
{% endif %}
<a href="{% url 'ipam:vlangroup_list' %}">VLAN Groups</a>
</li>
<li class="divider"></li>
<li class="dropdown-header">Services</li>
<li>
<a href="{% url 'ipam:service_list' %}">Services</a>
</li>
</ul>
</li>
<li class="dropdown{% if request.path|contains:'/virtualization/' %} active{% endif %}">
Expand Down
4 changes: 3 additions & 1 deletion netbox/templates/ipam/inc/service.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<tr>
<td>{{ service.name }}</td>
<td>
<a href="{{ service.get_absolute_url }}">{{ service.name }}</a>
</td>
<td>
{{ service.get_protocol_display }}/{{ service.port }}
</td>
Expand Down
88 changes: 88 additions & 0 deletions netbox/templates/ipam/service.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
{% extends '_base.html' %}
{% load helpers %}

{% block content %}
<div class="row">
<div class="col-sm-8 col-md-9">
<ol class="breadcrumb">
<li><a href="{% url 'ipam:service_list' %}">Services</a></li>
<li><a href="{{ service.parent.get_absolute_url }}">{{ service.parent }}</a></li>
<li>{{ service }}</li>
</ol>
</div>
<div class="col-sm-4 col-md-3">
<form action="{% url 'ipam:service_list' %}" method="get">
<div class="input-group">
<input type="text" name="q" class="form-control" placeholder="Search Services" />
<span class="input-group-btn">
<button type="submit" class="btn btn-primary">
<span class="fa fa-search" aria-hidden="true"></span>
</button>
</span>
</div>
</form>
</div>
</div>
{% if perms.dcim.change_service %}
<div class="pull-right">
<a href="{% url 'ipam:service_edit' pk=service.pk %}" class="btn btn-warning">
<span class="fa fa-pencil" aria-hidden="true"></span>
Edit this service
</a>
</div>
{% endif %}
<h1>{% block title %}{{ service }}{% endblock %}</h1>
{% include 'inc/created_updated.html' with obj=service %}
<div class="row">
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Service</strong>
</div>
<table class="table table-hover panel-body attr-table">
<tr>
<td>Name</td>
<td>{{ service.name }}</td>
</tr>
<tr>
<td>Parent</td>
<td>
<a href="{{ service.parent.get_absolute_url }}">{{ service.parent }}</a>
</td>
</tr>
<tr>
<td>Protocol</td>
<td>{{ service.get_protocol_display }}</td>
</tr>
<tr>
<td>Port</td>
<td>{{ service.port }}</td>
</tr>
<tr>
<td>IP Addresses</td>
<td>
{% for ipaddress in service.ipaddresses.all %}
<a href="{{ ipaddress.get_absolute_url }}">{{ ipaddress }}</a><br />
{% empty %}
<span class="text-muted">None</span>
{% endfor %}
</td>
</tr>
<tr>
<td>Description</td>
<td>
{% if service.description %}
{{ service.description }}
{% else %}
<span class="text-muted">N/A</span>
{% endif %}
</td>
</tr>
</table>
</div>
{% with service.get_custom_fields as custom_fields %}
{% include 'inc/custom_fields_panel.html' %}
{% endwith %}
</div>
</div>
{% endblock %}
15 changes: 15 additions & 0 deletions netbox/templates/ipam/service_list.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{% extends '_base.html' %}
{% load buttons %}
{% load humanize %}

{% block content %}
<h1>{% block title %}Services{% endblock %}</h1>
<div class="row">
<div class="col-md-9">
{% include 'utilities/obj_table.html' with bulk_edit_url='ipam:service_bulk_edit' bulk_delete_url='ipam:service_bulk_delete' %}
</div>
<div class="col-md-3">
{% include 'inc/search_panel.html' %}
</div>
</div>
{% endblock %}

0 comments on commit 7819d9c

Please sign in to comment.