Browse Source

Introduced CBVs for IPAM Roles

Jeremy Stretch 9 years ago
parent
commit
ff46970ba9

+ 16 - 1
netbox/ipam/forms.py

@@ -5,7 +5,7 @@ from django.db.models import Count
 
 from dcim.models import Site, Device, Interface
 from utilities.forms import BootstrapMixin, ConfirmationForm, APISelect, Livesearch, CSVDataField, BulkImportForm
-from .models import VRF, RIR, Aggregate, Prefix, IPAddress, VLAN, Status, Role
+from .models import VRF, RIR, Aggregate, Role, Status, Prefix, IPAddress, VLAN
 
 
 #
@@ -111,6 +111,21 @@ class AggregateFilterForm(forms.Form, BootstrapMixin):
 
 
 #
+# Roles
+#
+
+class RoleForm(forms.ModelForm, BootstrapMixin):
+
+    class Meta:
+        model = Role
+        fields = ['name', 'slug']
+
+
+class RoleBulkDeleteForm(ConfirmationForm):
+    pk = forms.ModelMultipleChoiceField(queryset=Role.objects.all(), widget=forms.MultipleHiddenInput)
+
+
+#
 # Prefixes
 #
 

+ 40 - 32
netbox/ipam/models.py

@@ -44,38 +44,6 @@ class VRF(models.Model):
         return reverse('ipam:vrf', args=[self.pk])
 
 
-class Status(models.Model):
-    """
-    The status of a prefix or VLAN (e.g. allocated, reserved, etc.)
-    """
-    name = models.CharField(max_length=50, unique=True)
-    slug = models.SlugField(unique=True)
-    weight = models.PositiveSmallIntegerField(default=1000)
-    bootstrap_class = models.PositiveSmallIntegerField(choices=BOOTSTRAP_CLASS_CHOICES, default=0)
-
-    class Meta:
-        ordering = ['weight', 'name']
-        verbose_name_plural = 'statuses'
-
-    def __unicode__(self):
-        return self.name
-
-
-class Role(models.Model):
-    """
-    The role of an address resource (e.g. customer, infrastructure, mgmt, etc.)
-    """
-    name = models.CharField(max_length=50, unique=True)
-    slug = models.SlugField(unique=True)
-    weight = models.PositiveSmallIntegerField(default=1000)
-    
-    class Meta:
-        ordering = ['weight', 'name']
-
-    def __unicode__(self):
-        return self.name
-
-
 class RIR(models.Model):
     """
     A regional Internet registry (e.g. ARIN) or governing standard (e.g. RFC 1918)
@@ -149,6 +117,46 @@ class Aggregate(models.Model):
         return int(children_size / self.prefix.size * 100)
 
 
+class Status(models.Model):
+    """
+    The status of a prefix or VLAN (e.g. allocated, reserved, etc.)
+    """
+    name = models.CharField(max_length=50, unique=True)
+    slug = models.SlugField(unique=True)
+    weight = models.PositiveSmallIntegerField(default=1000)
+    bootstrap_class = models.PositiveSmallIntegerField(choices=BOOTSTRAP_CLASS_CHOICES, default=0)
+
+    class Meta:
+        ordering = ['weight', 'name']
+        verbose_name_plural = 'statuses'
+
+    def __unicode__(self):
+        return self.name
+
+
+class Role(models.Model):
+    """
+    The role of an address resource (e.g. customer, infrastructure, mgmt, etc.)
+    """
+    name = models.CharField(max_length=50, unique=True)
+    slug = models.SlugField(unique=True)
+    weight = models.PositiveSmallIntegerField(default=1000)
+
+    class Meta:
+        ordering = ['weight', 'name']
+
+    def __unicode__(self):
+        return self.name
+
+    @property
+    def count_prefixes(self):
+        return self.prefixes.count()
+
+    @property
+    def count_vlans(self):
+        return self.vlans.count()
+
+
 class PrefixQuerySet(models.QuerySet):
 
     def annotate_depth(self, limit=None):

+ 26 - 1
netbox/ipam/tables.py

@@ -1,7 +1,7 @@
 import django_tables2 as tables
 from django_tables2.utils import Accessor
 
-from .models import VRF, RIR, Aggregate, Prefix, IPAddress, VLAN
+from .models import VRF, RIR, Aggregate, Role, Prefix, IPAddress, VLAN
 
 
 RIR_EDIT_LINK = """
@@ -19,6 +19,10 @@ UTILIZATION_GRAPH = """
 {% endwith %}
 """
 
+ROLE_EDIT_LINK = """
+{% if perms.ipam.change_role %}<a href="{% url 'ipam:role_edit' slug=record.slug %}">Edit</a>{% endif %}
+"""
+
 PREFIX_LINK = """
 {% if record.has_children %}
     <span style="padding-left: {{ record.depth }}0px "><i class="fa fa-caret-right"></i></a>
@@ -106,6 +110,27 @@ class AggregateTable(tables.Table):
 
 
 #
+# Roles
+#
+
+class RoleTable(tables.Table):
+    pk = tables.CheckBoxColumn(visible=False, default='')
+    name = tables.Column(verbose_name='Name')
+    prefix_count = tables.Column(accessor=Accessor('count_prefixes'), orderable=False, verbose_name='Prefixes')
+    vlan_count = tables.Column(accessor=Accessor('count_vlans'), orderable=False, verbose_name='VLANs')
+    slug = tables.Column(verbose_name='Slug')
+    edit = tables.TemplateColumn(template_code=ROLE_EDIT_LINK, verbose_name='')
+
+    class Meta:
+        model = Role
+        fields = ('pk', 'name', 'prefix_count', 'vlan_count', 'slug', 'edit')
+        empty_text = "No roles were found."
+        attrs = {
+            'class': 'table table-hover',
+        }
+
+
+#
 # Prefixes
 #
 

+ 6 - 0
netbox/ipam/urls.py

@@ -30,6 +30,12 @@ urlpatterns = [
     url(r'^aggregates/(?P<pk>\d+)/edit/$', views.AggregateEditView.as_view(), name='aggregate_edit'),
     url(r'^aggregates/(?P<pk>\d+)/delete/$', views.AggregateDeleteView.as_view(), name='aggregate_delete'),
 
+    # Roles
+    url(r'^roles/$', views.RoleListView.as_view(), name='role_list'),
+    url(r'^roles/add/$', views.RoleEditView.as_view(), name='role_add'),
+    url(r'^roles/delete/$', views.RoleBulkDeleteView.as_view(), name='role_bulk_delete'),
+    url(r'^roles/(?P<slug>[\w-]+)/edit/$', views.RoleEditView.as_view(), name='role_edit'),
+
     # Prefixes
     url(r'^prefixes/$', views.PrefixListView.as_view(), name='prefix_list'),
     url(r'^prefixes/add/$', views.PrefixEditView.as_view(), name='prefix_add'),

+ 32 - 6
netbox/ipam/views.py

@@ -14,12 +14,12 @@ from utilities.views import BulkImportView, BulkEditView, BulkDeleteView, Object
 
 from .filters import AggregateFilter, PrefixFilter, IPAddressFilter, VLANFilter, VRFFilter
 from .forms import AggregateForm, AggregateImportForm, AggregateBulkEditForm, AggregateBulkDeleteForm,\
-    AggregateFilterForm, PrefixForm, PrefixImportForm, PrefixBulkEditForm, PrefixBulkDeleteForm, PrefixFilterForm,\
-    IPAddressForm, IPAddressImportForm, IPAddressBulkEditForm, IPAddressBulkDeleteForm, IPAddressFilterForm, VLANForm,\
-    VLANImportForm, VLANBulkEditForm, VLANBulkDeleteForm, VRFForm, VRFImportForm, VRFBulkEditForm, VRFBulkDeleteForm,\
-    VLANFilterForm, RIRForm, RIRBulkDeleteForm
-from .models import VRF, RIR, Aggregate, Prefix, IPAddress, VLAN
-from .tables import VRFTable, RIRTable, AggregateTable, PrefixTable, PrefixBriefTable, IPAddressBriefTable,\
+    AggregateFilterForm, RoleForm, RoleBulkDeleteForm, PrefixForm, PrefixImportForm, PrefixBulkEditForm,\
+    PrefixBulkDeleteForm, PrefixFilterForm, IPAddressForm, IPAddressImportForm, IPAddressBulkEditForm,\
+    IPAddressBulkDeleteForm, IPAddressFilterForm, VLANForm, VLANImportForm, VLANBulkEditForm, VLANBulkDeleteForm,\
+    VRFForm, VRFImportForm, VRFBulkEditForm, VRFBulkDeleteForm, VLANFilterForm, RIRForm, RIRBulkDeleteForm
+from .models import VRF, RIR, Aggregate, Role, Prefix, IPAddress, VLAN
+from .tables import VRFTable, RIRTable, AggregateTable, RoleTable, PrefixTable, PrefixBriefTable, IPAddressBriefTable,\
     IPAddressTable, VLANTable
 
 
@@ -218,6 +218,32 @@ class AggregateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
 
 
 #
+# Prefix/VLAN roles
+#
+
+class RoleListView(ObjectListView):
+    queryset = Role.objects.all()
+    table = RoleTable
+    edit_permissions = ['ipam.change_role', 'ipam.delete_role']
+    template_name = 'ipam/role_list.html'
+
+
+class RoleEditView(PermissionRequiredMixin, ObjectEditView):
+    permission_required = 'ipam.change_role'
+    model = Role
+    form_class = RoleForm
+    success_url = 'ipam:role_list'
+    cancel_url = 'ipam:role_list'
+
+
+class RoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
+    permission_required = 'ipam.delete_role'
+    cls = Role
+    form = RoleBulkDeleteForm
+    default_redirect_url = 'ipam:role_list'
+
+
+#
 # Prefixes
 #
 

+ 18 - 13
netbox/templates/_base.html

@@ -35,9 +35,7 @@
                                 <li><a href="{% url 'dcim:rack_add' %}"><i class="glyphicon glyphicon-plus" aria-hidden="true"></i> Add a Rack</a></li>
                                 <li><a href="{% url 'dcim:rack_import' %}"><i class="glyphicon glyphicon-import" aria-hidden="true"></i> Import Racks</a></li>
                             {% endif %}
-                            {% if perms.dcim.add_rack or perms.dcim.add_rackgroup %}
-                                <li class="divider"></li>
-                            {% endif %}
+                            <li class="divider"></li>
                             <li><a href="{% url 'dcim:rackgroup_list' %}"><i class="glyphicon glyphicon-search" aria-hidden="true"></i> Rack Groups</a></li>
                             {% if perms.dcim.add_rackgroup %}
                                 <li><a href="{% url 'dcim:rackgroup_add' %}"><i class="glyphicon glyphicon-plus" aria-hidden="true"></i> Add a Rack Group</a></li>
@@ -59,19 +57,21 @@
                             {% if perms.dcim.add_devicetype %}
                                 <li><a href="{% url 'dcim:devicetype_add' %}"><i class="glyphicon glyphicon-plus" aria-hidden="true"></i> Add a Device Type</a></li>
                             {% endif %}
-                            {% if perms.ipam.add_devicetype or perms.ipam.add_devicerole %}
-                                <li class="divider"></li>
-                            {% endif %}
+                            <li class="divider"></li>
                             <li><a href="{% url 'dcim:devicerole_list' %}"><i class="glyphicon glyphicon-search" aria-hidden="true"></i> Device Roles</a></li>
                             {% if perms.dcim.add_devicerole %}
                                 <li><a href="{% url 'dcim:devicerole_add' %}"><i class="glyphicon glyphicon-plus" aria-hidden="true"></i> Add a Device Role</a></li>
                             {% endif %}
-                            <li class="divider"></li>
+                            {% if perms.dcim.add_devicerole or perms.dcim.add_manufacturer %}
+                                <li class="divider"></li>
+                            {% endif %}
                             <li><a href="{% url 'dcim:manufacturer_list' %}"><i class="glyphicon glyphicon-search" aria-hidden="true"></i> Manufacturers</a></li>
                             {% if perms.dcim.add_manufacturer %}
                                 <li><a href="{% url 'dcim:manufacturer_add' %}"><i class="glyphicon glyphicon-plus" aria-hidden="true"></i> Add a Manufacturer</a></li>
                             {% endif %}
-                            <li class="divider"></li>
+                            {% if perms.dcim.add_manufacturer or perms.dcim.add_platform %}
+                                <li class="divider"></li>
+                            {% endif %}
                             <li><a href="{% url 'dcim:platform_list' %}"><i class="glyphicon glyphicon-search" aria-hidden="true"></i> Platforms</a></li>
                             {% if perms.dcim.add_platform %}
                                 <li><a href="{% url 'dcim:platform_add' %}"><i class="glyphicon glyphicon-plus" aria-hidden="true"></i> Add a Platform</a></li>
@@ -101,7 +101,7 @@
                             {% endif %}
                         </ul>
                     </li>
-                    <li class="dropdown{% if '/ipam/ip-addresses/' in request.path or '/prefixes/' in request.path or '/aggregates/' in request.path or '/vrfs/' in request.path or '/rirs/' in request.path %} active{% endif %}">
+                    <li class="dropdown{% if '/ipam/' in request.path and not '/ipam/vlans/' in request.path %} active{% endif %}">
                         <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">IP Space <span class="caret"></span></a>
                         <ul class="dropdown-menu">
                             <li><a href="{% url 'ipam:ipaddress_list' %}"><i class="glyphicon glyphicon-search" aria-hidden="true"></i> IP Addresses</a></li>
@@ -138,9 +138,16 @@
                             {% if perms.ipam.add_rir %}
                                 <li><a href="{% url 'ipam:rir_add' %}"><i class="glyphicon glyphicon-plus" aria-hidden="true"></i> Add a RIR</a></li>
                             {% endif %}
+                            {% if perms.ipam.add_rir or perms.ipam.add_role %}
+                                <li class="divider"></li>
+                            {% endif %}
+                            <li><a href="{% url 'ipam:role_list' %}"><i class="glyphicon glyphicon-search" aria-hidden="true"></i> Prefix/VLAN Roles</a></li>
+                            {% if perms.ipam.add_role %}
+                                <li><a href="{% url 'ipam:role_add' %}"><i class="glyphicon glyphicon-plus" aria-hidden="true"></i> Add a Role</a></li>
+                            {% endif %}
                         </ul>
                     </li>
-                    <li class="dropdown{% if '/vlans/' in request.path %} active{% endif %}">
+                    <li class="dropdown{% if '/ipam/vlans/' in request.path %} active{% endif %}">
                         {% if perms.ipam.add_vlan %}
                             <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">VLANs <span class="caret"></span></a>
                             <ul class="dropdown-menu">
@@ -168,9 +175,7 @@
                                 <li><a href="{% url 'circuits:circuit_add' %}"><i class="glyphicon glyphicon-plus" aria-hidden="true"></i> Add a Circuit</a></li>
                                 <li><a href="{% url 'circuits:circuit_import' %}"><i class="glyphicon glyphicon-import" aria-hidden="true"></i> Import Circuits</a></li>
                             {% endif %}
-                            {% if perms.circuits.add_circuit or perms.circuits.add_circuittype %}
-                                <li class="divider"></li>
-                            {% endif %}
+                            <li class="divider"></li>
                             <li><a href="{% url 'circuits:circuittype_list' %}"><i class="glyphicon glyphicon-search" aria-hidden="true"></i> Circuit Types</a></li>
                             {% if perms.circuits.add_circuittype %}
                                 <li><a href="{% url 'circuits:circuittype_add' %}"><i class="glyphicon glyphicon-plus" aria-hidden="true"></i> Add a Circuit Type</a></li>

+ 1 - 1
netbox/templates/ipam/rir_list.html

@@ -5,7 +5,7 @@
 
 {% block content %}
 <div class="pull-right">
-    {% if perms.dcim.add_devicerole %}
+    {% if perms.ipam.add_rir %}
         <a href="{% url 'ipam:rir_add' %}" class="btn btn-primary">
             <span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
             Add a RIR

+ 21 - 0
netbox/templates/ipam/role_list.html

@@ -0,0 +1,21 @@
+{% extends '_base.html' %}
+{% load helpers %}
+
+{% block title %}Prefix/VLAN Roles{% endblock %}
+
+{% block content %}
+<div class="pull-right">
+    {% if perms.dcim.add_devicerole %}
+        <a href="{% url 'ipam:role_add' %}" class="btn btn-primary">
+            <span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
+            Add a role
+        </a>
+    {% endif %}
+</div>
+<h1>Prefix/VLAN Roles</h1>
+<div class="row">
+	<div class="col-md-12">
+        {% include 'utilities/obj_table.html' with bulk_delete_url='ipam:role_bulk_delete' %}
+    </div>
+</div>
+{% endblock %}

+ 7 - 4
netbox/utilities/views.py

@@ -116,10 +116,13 @@ class ObjectEditView(View):
             obj = form.save(commit=False)
             obj_created = not obj.pk
             obj.save()
-            messages.success(request, '{} {} <a href="{}">{}</a>'.format('Created' if obj_created else 'Modified',
-                                                                         self.model._meta.verbose_name,
-                                                                         obj.get_absolute_url(),
-                                                                         obj))
+            msg = 'Created ' if obj_created else 'Modified '
+            msg += self.model._meta.verbose_name
+            if hasattr(obj, 'get_absolute_url'):
+                msg += ' <a href="{}">{}</a>'.format(obj.get_absolute_url(), obj)
+            else:
+                msg += ' {}'.format(obj)
+            messages.success(request, msg)
             if '_addanother' in request.POST:
                 return redirect(request.path)
             elif self.success_url: