Просмотр исходного кода

Closes #1673: Added object/list views for services

Jeremy Stretch 7 лет назад
Родитель
Сommit
7819d9c112

+ 10 - 0
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)

+ 29 - 4
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']

+ 3 - 0
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

+ 17 - 1
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 = """
 <div class="progress">
@@ -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 - 0
netbox/ipam/urls.py

@@ -85,6 +85,10 @@ urlpatterns = [
     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'),
 

+ 38 - 3
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'

+ 5 - 0
netbox/templates/inc/nav_menu.html

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

+ 3 - 1
netbox/templates/ipam/inc/service.html

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

+ 88 - 0
netbox/templates/ipam/service.html

@@ -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 - 0
netbox/templates/ipam/service_list.html

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