Skip to content

Commit

Permalink
Merge pull request #1789 from digitalocean/develop
Browse files Browse the repository at this point in the history
Release v2.2.8
  • Loading branch information
jeremystretch authored Dec 20, 2017
2 parents e98f0c3 + 8414711 commit ec0cb7a
Show file tree
Hide file tree
Showing 20 changed files with 127 additions and 59 deletions.
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

0 comments on commit ec0cb7a

Please sign in to comment.