diff --git a/netbox/ipam/forms.py b/netbox/ipam/forms.py
index db8ff7a85..869d95aa9 100644
--- a/netbox/ipam/forms.py
+++ b/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
#
@@ -110,6 +110,21 @@ class AggregateFilterForm(forms.Form, BootstrapMixin):
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
#
diff --git a/netbox/ipam/models.py b/netbox/ipam/models.py
index b00f16104..a3ea25d11 100644
--- a/netbox/ipam/models.py
+++ b/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):
diff --git a/netbox/ipam/tables.py b/netbox/ipam/tables.py
index adf8617b2..7862b4233 100644
--- a/netbox/ipam/tables.py
+++ b/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 %}Edit{% endif %}
+"""
+
PREFIX_LINK = """
{% if record.has_children %}
@@ -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
#
diff --git a/netbox/ipam/urls.py b/netbox/ipam/urls.py
index 97d7d7543..58f73c247 100644
--- a/netbox/ipam/urls.py
+++ b/netbox/ipam/urls.py
@@ -30,6 +30,12 @@ urlpatterns = [
url(r'^aggregates/(?P\d+)/edit/$', views.AggregateEditView.as_view(), name='aggregate_edit'),
url(r'^aggregates/(?P\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[\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'),
diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py
index bec993762..f53bb05ed 100644
--- a/netbox/ipam/views.py
+++ b/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
@@ -217,6 +217,32 @@ class AggregateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
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
#
diff --git a/netbox/templates/_base.html b/netbox/templates/_base.html
index c37f8dcce..a357922ec 100644
--- a/netbox/templates/_base.html
+++ b/netbox/templates/_base.html
@@ -35,9 +35,7 @@
Add a Rack
Import Racks
{% endif %}
- {% if perms.dcim.add_rack or perms.dcim.add_rackgroup %}
-
- {% endif %}
+
Rack Groups
{% if perms.dcim.add_rackgroup %}
Add a Rack Group
@@ -59,19 +57,21 @@
{% if perms.dcim.add_devicetype %}
Add a Device Type
{% endif %}
- {% if perms.ipam.add_devicetype or perms.ipam.add_devicerole %}
-
- {% endif %}
+
Device Roles
{% if perms.dcim.add_devicerole %}
Add a Device Role
{% endif %}
-
+ {% if perms.dcim.add_devicerole or perms.dcim.add_manufacturer %}
+
+ {% endif %}
Manufacturers
{% if perms.dcim.add_manufacturer %}
Add a Manufacturer
{% endif %}
-
+ {% if perms.dcim.add_manufacturer or perms.dcim.add_platform %}
+
+ {% endif %}
Platforms
{% if perms.dcim.add_platform %}
Add a Platform
@@ -101,7 +101,7 @@
{% endif %}
-
+
IP Space
-
+
{% if perms.ipam.add_vlan %}
VLANs