Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions docs/installation/ldap.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ sudo pip install django-auth-ldap

# Configuration

Create a file in the same directory as `configuration.py` (typically `netbox/netbox/`) named `ldap_config.py`. Define all of the parameters required below in `ldap_config.py`.
Create a file in the same directory as `configuration.py` (typically `netbox/netbox/`) named `ldap_config.py`. Define all of the parameters required below in `ldap_config.py`. Complete documentation of all `django-auth-ldap` configuration options is included in the project's [official documentation](http://django-auth-ldap.readthedocs.io/).

## General Server Configuration

Expand Down Expand Up @@ -52,6 +52,8 @@ AUTH_LDAP_BIND_PASSWORD = "demo"
LDAP_IGNORE_CERT_ERRORS = True
```

STARTTLS can be configured by setting `AUTH_LDAP_START_TLS = True` and using the `ldap://` URI scheme.

## User Authentication

!!! info
Expand All @@ -78,7 +80,7 @@ AUTH_LDAP_USER_ATTR_MAP = {
```

# User Groups for Permissions
!!! Info
!!! info
When using Microsoft Active Directory, Support for nested Groups can be activated by using `GroupOfNamesType()` instead of `NestedGroupOfNamesType()` for `AUTH_LDAP_GROUP_TYPE`.

```python
Expand Down
6 changes: 3 additions & 3 deletions netbox/dcim/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ class RackFilter(CustomFieldFilterSet, django_filters.FilterSet):

class Meta:
model = Rack
fields = ['serial', 'type', 'width', 'u_height', 'desc_units']
fields = ['name', 'serial', 'type', 'width', 'u_height', 'desc_units']

def search(self, queryset, name, value):
if not value.strip():
Expand Down Expand Up @@ -330,7 +330,7 @@ class DeviceRoleFilter(django_filters.FilterSet):

class Meta:
model = DeviceRole
fields = ['name', 'slug', 'color']
fields = ['name', 'slug', 'color', 'vm_role']


class PlatformFilter(django_filters.FilterSet):
Expand Down Expand Up @@ -455,7 +455,7 @@ class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet):

class Meta:
model = Device
fields = ['serial']
fields = ['serial', 'position']

def search(self, queryset, name, value):
if not value.strip():
Expand Down
43 changes: 28 additions & 15 deletions netbox/ipam/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,12 +281,28 @@ def get_status_class(self):
def get_duplicates(self):
return Prefix.objects.filter(vrf=self.vrf, prefix=str(self.prefix)).exclude(pk=self.pk)

def get_child_prefixes(self):
"""
Return all Prefixes within this Prefix and VRF.
"""
return Prefix.objects.filter(prefix__net_contained=str(self.prefix), vrf=self.vrf)

def get_child_ips(self):
"""
Return all IPAddresses within this Prefix.
Return all IPAddresses within this Prefix and VRF.
"""
return IPAddress.objects.filter(address__net_host_contained=str(self.prefix), vrf=self.vrf)

def get_available_prefixes(self):
"""
Return all available Prefixes within this prefix as an IPSet.
"""
prefix = netaddr.IPSet(self.prefix)
child_prefixes = netaddr.IPSet([child.prefix for child in self.get_child_prefixes()])
available_prefixes = prefix - child_prefixes

return available_prefixes

def get_available_ips(self):
"""
Return all available IPs within this prefix as an IPSet.
Expand All @@ -304,15 +320,23 @@ def get_available_ips(self):

return available_ips

def get_first_available_prefix(self):
"""
Return the first available child prefix within the prefix (or None).
"""
available_prefixes = self.get_available_prefixes()
if not available_prefixes:
return None
return available_prefixes.iter_cidrs()[0]

def get_first_available_ip(self):
"""
Return the first available IP within the prefix (or None).
"""
available_ips = self.get_available_ips()
if available_ips:
return '{}/{}'.format(next(available_ips.__iter__()), self.prefix.prefixlen)
else:
if not available_ips:
return None
return '{}/{}'.format(next(available_ips.__iter__()), self.prefix.prefixlen)

def get_utilization(self):
"""
Expand All @@ -330,17 +354,6 @@ def get_utilization(self):
prefix_size -= 2
return int(float(child_count) / prefix_size * 100)

@property
def new_subnet(self):
if self.family == 4:
if self.prefix.prefixlen <= 30:
return netaddr.IPNetwork('{}/{}'.format(self.prefix.network, self.prefix.prefixlen + 1))
return None
if self.family == 6:
if self.prefix.prefixlen <= 126:
return netaddr.IPNetwork('{}/{}'.format(self.prefix.network, self.prefix.prefixlen + 1))
return None


class IPAddressManager(models.Manager):

Expand Down
1 change: 1 addition & 0 deletions netbox/ipam/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
url(r'^prefixes/(?P<pk>\d+)/$', views.PrefixView.as_view(), name='prefix'),
url(r'^prefixes/(?P<pk>\d+)/edit/$', views.PrefixEditView.as_view(), name='prefix_edit'),
url(r'^prefixes/(?P<pk>\d+)/delete/$', views.PrefixDeleteView.as_view(), name='prefix_delete'),
url(r'^prefixes/(?P<pk>\d+)/prefixes/$', views.PrefixPrefixesView.as_view(), name='prefix_prefixes'),
url(r'^prefixes/(?P<pk>\d+)/ip-addresses/$', views.PrefixIPAddressesView.as_view(), name='prefix_ipaddresses'),

# IP addresses
Expand Down
33 changes: 23 additions & 10 deletions netbox/ipam/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,20 @@ def get(self, request, pk):
duplicate_prefix_table = tables.PrefixTable(list(duplicate_prefixes), orderable=False)
duplicate_prefix_table.exclude = ('vrf',)

return render(request, 'ipam/prefix.html', {
'prefix': prefix,
'aggregate': aggregate,
'parent_prefix_table': parent_prefix_table,
'duplicate_prefix_table': duplicate_prefix_table,
})


class PrefixPrefixesView(View):

def get(self, request, pk):

prefix = get_object_or_404(Prefix.objects.all(), pk=pk)

# Child prefixes table
child_prefixes = Prefix.objects.filter(
vrf=prefix.vrf, prefix__net_contained=str(prefix.prefix)
Expand All @@ -484,15 +498,16 @@ def get(self, request, pk):
).annotate_depth(limit=0)
if child_prefixes:
child_prefixes = add_available_prefixes(prefix.prefix, child_prefixes)
child_prefix_table = tables.PrefixDetailTable(child_prefixes)

prefix_table = tables.PrefixDetailTable(child_prefixes)
if request.user.has_perm('ipam.change_prefix') or request.user.has_perm('ipam.delete_prefix'):
child_prefix_table.columns.show('pk')
prefix_table.columns.show('pk')

paginate = {
'klass': EnhancedPaginator,
'per_page': request.GET.get('per_page', settings.PAGINATE_COUNT)
}
RequestConfig(request, paginate).configure(child_prefix_table)
RequestConfig(request, paginate).configure(prefix_table)

# Compile permissions list for rendering the object table
permissions = {
Expand All @@ -501,15 +516,12 @@ def get(self, request, pk):
'delete': request.user.has_perm('ipam.delete_prefix'),
}

return render(request, 'ipam/prefix.html', {
return render(request, 'ipam/prefix_prefixes.html', {
'prefix': prefix,
'aggregate': aggregate,
'parent_prefix_table': parent_prefix_table,
'child_prefix_table': child_prefix_table,
'duplicate_prefix_table': duplicate_prefix_table,
'bulk_querystring': 'vrf_id={}&within={}'.format(prefix.vrf or '0', prefix.prefix),
'first_available_prefix': prefix.get_first_available_prefix(),
'prefix_table': prefix_table,
'permissions': permissions,
'return_url': prefix.get_absolute_url(),
'bulk_querystring': 'vrf_id={}&within={}'.format(prefix.vrf.pk if prefix.vrf else '0', prefix.prefix),
})


Expand Down Expand Up @@ -544,6 +556,7 @@ def get(self, request, pk):

return render(request, 'ipam/prefix_ipaddresses.html', {
'prefix': prefix,
'first_available_ip': prefix.get_first_available_ip(),
'ip_table': ip_table,
'permissions': permissions,
'bulk_querystring': 'vrf_id={}&parent={}'.format(prefix.vrf.pk if prefix.vrf else '0', prefix.prefix),
Expand Down
3 changes: 3 additions & 0 deletions netbox/netbox/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ class FormlessBrowsableAPIRenderer(BrowsableAPIRenderer):
def show_form_for_method(self, *args, **kwargs):
return False

def get_filter_form(self, data, view, request):
return None


#
# Authentication
Expand Down
2 changes: 1 addition & 1 deletion netbox/netbox/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
)


VERSION = '2.2.7'
VERSION = '2.2.8'

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

Expand Down
2 changes: 1 addition & 1 deletion netbox/templates/dcim/device.html
Original file line number Diff line number Diff line change
Expand Up @@ -535,7 +535,7 @@
<div class="panel-heading">
<strong>Power Outlets</strong>
<div class="pull-right">
{% if perms.dcim.change_poweroutlet and cs_ports|length > 1 %}
{% if perms.dcim.change_poweroutlet and power_outlets|length > 1 %}
<button class="btn btn-default btn-xs toggle">
<span class="glyphicon glyphicon-unchecked" aria-hidden="true"></span> Select all
</button>
Expand Down
2 changes: 1 addition & 1 deletion netbox/templates/dcim/devicetype_list.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
Import device types
</a>
{% endif %}
{% include 'inc/export_button.html' with obj_type='devicetypes' %}
{% include 'inc/export_button.html' with obj_type='device types' %}
</div>
<h1>{% block title %}Device Types{% endblock %}</h1>
<div class="row">
Expand Down
2 changes: 1 addition & 1 deletion netbox/templates/dcim/rackgroup_list.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
Import rack groups
</a>
{% endif %}
{% include 'inc/export_button.html' with obj_type='rackgroups' %}
{% include 'inc/export_button.html' with obj_type='rack groups' %}
</div>
<h1>{% block title %}Rack Groups{% endblock %}</h1>
<div class="row">
Expand Down
10 changes: 8 additions & 2 deletions netbox/templates/ipam/inc/prefix_header.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,13 @@
</div>
</div>
<div class="pull-right">
{% if perms.ipam.add_ipaddress %}
<a href="{% url 'ipam:ipaddress_add' %}?address={{ prefix.get_first_available_ip }}&vrf={{ prefix.vrf.pk }}&tenant_group={{ prefix.tenant.group.pk }}&tenant={{ prefix.tenant.pk }}" class="btn btn-success">
{% if perms.ipam.add_prefix and active_tab == 'prefixes' and first_available_prefix %}
<a href="{% url 'ipam:prefix_add' %}?prefix={{ first_available_prefix }}&vrf={{ prefix.vrf.pk }}&site={{ prefix.site.pk }}&tenant_group={{ prefix.tenant.group.pk }}&tenant={{ prefix.tenant.pk }}" class="btn btn-success">
<i class="fa fa-plus" aria-hidden="true"></i> Add Child Prefix
</a>
{% endif %}
{% if perms.ipam.add_ipaddress and active_tab == 'ip-addresses' and first_available_ip %}
<a href="{% url 'ipam:ipaddress_add' %}?address={{ first_available_ip }}&vrf={{ prefix.vrf.pk }}&tenant_group={{ prefix.tenant.group.pk }}&tenant={{ prefix.tenant.pk }}" class="btn btn-success">
<span class="fa fa-plus" aria-hidden="true"></span>
Add an IP Address
</a>
Expand All @@ -45,5 +50,6 @@ <h1>{{ prefix }}</h1>
{% include 'inc/created_updated.html' with obj=prefix %}
<ul class="nav nav-tabs" style="margin-bottom: 20px">
<li role="presentation"{% if active_tab == 'prefix' %} class="active"{% endif %}><a href="{% url 'ipam:prefix' pk=prefix.pk %}">Prefix</a></li>
<li role="presentation"{% if active_tab == 'prefixes' %} class="active"{% endif %}><a href="{% url 'ipam:prefix_prefixes' pk=prefix.pk %}">Child Prefixes <span class="badge">{{ prefix.get_child_prefixes.count }}</span></a></li>
<li role="presentation"{% if active_tab == 'ip-addresses' %} class="active"{% endif %}><a href="{% url 'ipam:prefix_ipaddresses' pk=prefix.pk %}">IP Addresses <span class="badge">{{ prefix.get_child_ips.count }}</span></a></li>
</ul>
11 changes: 0 additions & 11 deletions netbox/templates/ipam/prefix.html
Original file line number Diff line number Diff line change
Expand Up @@ -139,15 +139,4 @@
{% include 'panel_table.html' with table=parent_prefix_table heading='Parent Prefixes' %}
</div>
</div>
<div class="row">
<div class="col-md-12">
{% if child_prefix_table.rows %}
{% include 'utilities/obj_table.html' with table=child_prefix_table table_template='panel_table.html' heading='Child Prefixes' parent=prefix bulk_edit_url='ipam:prefix_bulk_edit' bulk_delete_url='ipam:prefix_bulk_delete' %}
{% elif prefix.new_subnet %}
<a href="{% url 'ipam:prefix_add' %}?prefix={{ prefix.new_subnet }}{% if prefix.vrf %}&vrf={{ prefix.vrf.pk }}{% endif %}{% if prefix.site %}&site={{ prefix.site.pk }}{% endif %}" class="btn btn-success">
<i class="fa fa-plus" aria-hidden="true"></i> Add Child Prefix
</a>
{% endif %}
</div>
</div>
{% endblock %}
10 changes: 5 additions & 5 deletions netbox/templates/ipam/prefix_ipaddresses.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
{% block title %}{{ prefix }} - IP Addresses{% endblock %}

{% block content %}
{% include 'ipam/inc/prefix_header.html' with active_tab='ip-addresses' %}
<div class="row">
<div class="col-md-12">
{% include 'utilities/obj_table.html' with table=ip_table table_template='panel_table.html' heading='IP Addresses' bulk_edit_url='ipam:ipaddress_bulk_edit' bulk_delete_url='ipam:ipaddress_bulk_delete' %}
{% include 'ipam/inc/prefix_header.html' with active_tab='ip-addresses' %}
<div class="row">
<div class="col-md-12">
{% include 'utilities/obj_table.html' with table=ip_table table_template='panel_table.html' heading='IP Addresses' bulk_edit_url='ipam:ipaddress_bulk_edit' bulk_delete_url='ipam:ipaddress_bulk_delete' %}
</div>
</div>
</div>
{% endblock %}
12 changes: 12 additions & 0 deletions netbox/templates/ipam/prefix_prefixes.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{% extends '_base.html' %}

{% block title %}{{ prefix }} - Prefixes{% endblock %}

{% block content %}
{% include 'ipam/inc/prefix_header.html' with active_tab='prefixes' %}
<div class="row">
<div class="col-md-12">
{% include 'utilities/obj_table.html' with table=prefix_table table_template='panel_table.html' heading='Child Prefixes' bulk_edit_url='ipam:prefix_bulk_edit' bulk_delete_url='ipam:prefix_bulk_delete' %}
</div>
</div>
{% endblock %}
12 changes: 7 additions & 5 deletions netbox/templates/search.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@
{% for obj_type in results %}
<h3 id="{{ obj_type.name|lower }}">{{ obj_type.name|bettertitle }}</h3>
{% include 'panel_table.html' with table=obj_type.table hide_paginator=True %}
{% if obj_type.table.page.has_next %}
<a href="{{ obj_type.url }}" class="btn btn-primary pull-right">
<span class="fa fa-arrow-right" aria-hidden="true"></span>
<a href="{{ obj_type.url }}" class="btn btn-primary pull-right">
<span class="fa fa-arrow-right" aria-hidden="true"></span>
{% if obj_type.table.page.has_next %}
See all {{ obj_type.table.page.paginator.count }} results
</a>
{% endif %}
{% else %}
Refine search
{% endif %}
</a>
<div class="clearfix"></div>
{% endfor %}
</div>
Expand Down
6 changes: 5 additions & 1 deletion netbox/utilities/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from django.conf import settings
from django.db import ProgrammingError
from django.http import HttpResponseRedirect
from django.http import Http404, HttpResponseRedirect
from django.shortcuts import render
from django.urls import reverse

Expand Down Expand Up @@ -61,6 +61,10 @@ def process_exception(self, request, exception):
if settings.DEBUG:
return

# Ignore Http404s (defer to Django's built-in 404 handling)
if isinstance(exception, Http404):
return

# Determine the type of exception
if isinstance(exception, ProgrammingError):
template_name = 'exceptions/programming_error.html'
Expand Down
8 changes: 7 additions & 1 deletion netbox/utilities/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -308,8 +308,14 @@ class BulkCreateView(View):

def get(self, request):

# Set initial values for visible form fields from query args
initial = {}
for field in getattr(self.model_form._meta, 'fields', []):
if request.GET.get(field):
initial[field] = request.GET[field]

form = self.form()
model_form = self.model_form()
model_form = self.model_form(initial=initial)

return render(request, self.template_name, {
'obj_type': self.model_form._meta.model._meta.verbose_name,
Expand Down
11 changes: 11 additions & 0 deletions netbox/virtualization/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,17 @@ class VirtualMachineFilter(CustomFieldFilterSet):
to_field_name='slug',
label='Cluster group (slug)',
)
cluster_type_id = django_filters.ModelMultipleChoiceFilter(
name='cluster__type',
queryset=ClusterType.objects.all(),
label='Cluster type (ID)',
)
cluster_type = django_filters.ModelMultipleChoiceFilter(
name='cluster__type__slug',
queryset=ClusterType.objects.all(),
to_field_name='slug',
label='Cluster type (slug)',
)
cluster_id = django_filters.ModelMultipleChoiceFilter(
queryset=Cluster.objects.all(),
label='Cluster (ID)',
Expand Down
Loading