Introduced CBVs for IPAM Roles

This commit is contained in:
Jeremy Stretch 2016-05-17 15:04:16 -04:00
parent a527e0e527
commit ff46970ba9
9 changed files with 167 additions and 58 deletions

View File

@ -5,7 +5,7 @@ from django.db.models import Count
from dcim.models import Site, Device, Interface from dcim.models import Site, Device, Interface
from utilities.forms import BootstrapMixin, ConfirmationForm, APISelect, Livesearch, CSVDataField, BulkImportForm 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
# #
@ -110,6 +110,21 @@ class AggregateFilterForm(forms.Form, BootstrapMixin):
widget=forms.SelectMultiple(attrs={'size': 8})) widget=forms.SelectMultiple(attrs={'size': 8}))
#
# 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 # Prefixes
# #

View File

@ -44,38 +44,6 @@ class VRF(models.Model):
return reverse('ipam:vrf', args=[self.pk]) 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): class RIR(models.Model):
""" """
A regional Internet registry (e.g. ARIN) or governing standard (e.g. RFC 1918) 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) 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): class PrefixQuerySet(models.QuerySet):
def annotate_depth(self, limit=None): def annotate_depth(self, limit=None):

View File

@ -1,7 +1,7 @@
import django_tables2 as tables import django_tables2 as tables
from django_tables2.utils import Accessor 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 = """ RIR_EDIT_LINK = """
@ -19,6 +19,10 @@ UTILIZATION_GRAPH = """
{% endwith %} {% endwith %}
""" """
ROLE_EDIT_LINK = """
{% if perms.ipam.change_role %}<a href="{% url 'ipam:role_edit' slug=record.slug %}">Edit</a>{% endif %}
"""
PREFIX_LINK = """ PREFIX_LINK = """
{% if record.has_children %} {% if record.has_children %}
<span style="padding-left: {{ record.depth }}0px "><i class="fa fa-caret-right"></i></a> <span style="padding-left: {{ record.depth }}0px "><i class="fa fa-caret-right"></i></a>
@ -105,6 +109,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 # Prefixes
# #

View File

@ -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+)/edit/$', views.AggregateEditView.as_view(), name='aggregate_edit'),
url(r'^aggregates/(?P<pk>\d+)/delete/$', views.AggregateDeleteView.as_view(), name='aggregate_delete'), 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 # Prefixes
url(r'^prefixes/$', views.PrefixListView.as_view(), name='prefix_list'), url(r'^prefixes/$', views.PrefixListView.as_view(), name='prefix_list'),
url(r'^prefixes/add/$', views.PrefixEditView.as_view(), name='prefix_add'), url(r'^prefixes/add/$', views.PrefixEditView.as_view(), name='prefix_add'),

View File

@ -14,12 +14,12 @@ from utilities.views import BulkImportView, BulkEditView, BulkDeleteView, Object
from .filters import AggregateFilter, PrefixFilter, IPAddressFilter, VLANFilter, VRFFilter from .filters import AggregateFilter, PrefixFilter, IPAddressFilter, VLANFilter, VRFFilter
from .forms import AggregateForm, AggregateImportForm, AggregateBulkEditForm, AggregateBulkDeleteForm,\ from .forms import AggregateForm, AggregateImportForm, AggregateBulkEditForm, AggregateBulkDeleteForm,\
AggregateFilterForm, PrefixForm, PrefixImportForm, PrefixBulkEditForm, PrefixBulkDeleteForm, PrefixFilterForm,\ AggregateFilterForm, RoleForm, RoleBulkDeleteForm, PrefixForm, PrefixImportForm, PrefixBulkEditForm,\
IPAddressForm, IPAddressImportForm, IPAddressBulkEditForm, IPAddressBulkDeleteForm, IPAddressFilterForm, VLANForm,\ PrefixBulkDeleteForm, PrefixFilterForm, IPAddressForm, IPAddressImportForm, IPAddressBulkEditForm,\
VLANImportForm, VLANBulkEditForm, VLANBulkDeleteForm, VRFForm, VRFImportForm, VRFBulkEditForm, VRFBulkDeleteForm,\ IPAddressBulkDeleteForm, IPAddressFilterForm, VLANForm, VLANImportForm, VLANBulkEditForm, VLANBulkDeleteForm,\
VLANFilterForm, RIRForm, RIRBulkDeleteForm VRFForm, VRFImportForm, VRFBulkEditForm, VRFBulkDeleteForm, VLANFilterForm, RIRForm, RIRBulkDeleteForm
from .models import VRF, RIR, Aggregate, Prefix, IPAddress, VLAN from .models import VRF, RIR, Aggregate, Role, Prefix, IPAddress, VLAN
from .tables import VRFTable, RIRTable, AggregateTable, PrefixTable, PrefixBriefTable, IPAddressBriefTable,\ from .tables import VRFTable, RIRTable, AggregateTable, RoleTable, PrefixTable, PrefixBriefTable, IPAddressBriefTable,\
IPAddressTable, VLANTable IPAddressTable, VLANTable
@ -217,6 +217,32 @@ class AggregateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
default_redirect_url = 'ipam:aggregate_list' default_redirect_url = 'ipam:aggregate_list'
#
# 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 # Prefixes
# #

View File

@ -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_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> <li><a href="{% url 'dcim:rack_import' %}"><i class="glyphicon glyphicon-import" aria-hidden="true"></i> Import Racks</a></li>
{% endif %} {% endif %}
{% if perms.dcim.add_rack or perms.dcim.add_rackgroup %} <li class="divider"></li>
<li class="divider"></li>
{% endif %}
<li><a href="{% url 'dcim:rackgroup_list' %}"><i class="glyphicon glyphicon-search" aria-hidden="true"></i> Rack Groups</a></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 %} {% 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> <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 %} {% 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> <li><a href="{% url 'dcim:devicetype_add' %}"><i class="glyphicon glyphicon-plus" aria-hidden="true"></i> Add a Device Type</a></li>
{% endif %} {% endif %}
{% if perms.ipam.add_devicetype or perms.ipam.add_devicerole %} <li class="divider"></li>
<li class="divider"></li>
{% endif %}
<li><a href="{% url 'dcim:devicerole_list' %}"><i class="glyphicon glyphicon-search" aria-hidden="true"></i> Device Roles</a></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 %} {% 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> <li><a href="{% url 'dcim:devicerole_add' %}"><i class="glyphicon glyphicon-plus" aria-hidden="true"></i> Add a Device Role</a></li>
{% endif %} {% 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> <li><a href="{% url 'dcim:manufacturer_list' %}"><i class="glyphicon glyphicon-search" aria-hidden="true"></i> Manufacturers</a></li>
{% if perms.dcim.add_manufacturer %} {% 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> <li><a href="{% url 'dcim:manufacturer_add' %}"><i class="glyphicon glyphicon-plus" aria-hidden="true"></i> Add a Manufacturer</a></li>
{% endif %} {% 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> <li><a href="{% url 'dcim:platform_list' %}"><i class="glyphicon glyphicon-search" aria-hidden="true"></i> Platforms</a></li>
{% if perms.dcim.add_platform %} {% 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> <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 %} {% endif %}
</ul> </ul>
</li> </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> <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"> <ul class="dropdown-menu">
<li><a href="{% url 'ipam:ipaddress_list' %}"><i class="glyphicon glyphicon-search" aria-hidden="true"></i> IP Addresses</a></li> <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 %} {% 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> <li><a href="{% url 'ipam:rir_add' %}"><i class="glyphicon glyphicon-plus" aria-hidden="true"></i> Add a RIR</a></li>
{% endif %} {% 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> </ul>
</li> </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 %} {% 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> <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"> <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_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> <li><a href="{% url 'circuits:circuit_import' %}"><i class="glyphicon glyphicon-import" aria-hidden="true"></i> Import Circuits</a></li>
{% endif %} {% endif %}
{% if perms.circuits.add_circuit or perms.circuits.add_circuittype %} <li class="divider"></li>
<li class="divider"></li>
{% endif %}
<li><a href="{% url 'circuits:circuittype_list' %}"><i class="glyphicon glyphicon-search" aria-hidden="true"></i> Circuit Types</a></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 %} {% 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> <li><a href="{% url 'circuits:circuittype_add' %}"><i class="glyphicon glyphicon-plus" aria-hidden="true"></i> Add a Circuit Type</a></li>

View File

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

View File

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

View File

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