Skip to content

Commit

Permalink
Merge pull request #1668 from digitalocean/develop
Browse files Browse the repository at this point in the history
Release v2.2.3
  • Loading branch information
jeremystretch authored Oct 31, 2017
2 parents 7a64404 + cfa6bee commit 3067c3f
Show file tree
Hide file tree
Showing 30 changed files with 383 additions and 175 deletions.
2 changes: 2 additions & 0 deletions docs/miscellaneous/reports.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ The following methods are available to log results within a report:

The recording of one or more failure messages will automatically flag a report as failed. It is advised to log a success for each object that is evaluated so that the results will reflect how many objects are being reported on. (The inclusion of a log message is optional for successes.) Messages recorded with `log()` will appear in a report's results but are not associated with a particular object or status.

To perform additional tasks, such as sending an email or calling a webhook, after a report has been run, extend the `post_run()` method. The status of the report is available as `self.failed` and the results object is `self.result`.

Once you have created a report, it will appear in the reports list. Initially, reports will have no results associated with them. To generate results, run the report.

## Running Reports
Expand Down
6 changes: 3 additions & 3 deletions netbox/circuits/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from dcim.models import Site
from extras.filters import CustomFieldFilterSet
from tenancy.models import Tenant
from utilities.filters import NullableModelMultipleChoiceFilter, NumericInFilter
from utilities.filters import NumericInFilter
from .models import Provider, Circuit, CircuitTermination, CircuitType


Expand Down Expand Up @@ -78,11 +78,11 @@ class CircuitFilter(CustomFieldFilterSet, django_filters.FilterSet):
to_field_name='slug',
label='Circuit type (slug)',
)
tenant_id = NullableModelMultipleChoiceFilter(
tenant_id = django_filters.ModelMultipleChoiceFilter(
queryset=Tenant.objects.all(),
label='Tenant (ID)',
)
tenant = NullableModelMultipleChoiceFilter(
tenant = django_filters.ModelMultipleChoiceFilter(
name='tenant',
queryset=Tenant.objects.all(),
to_field_name='slug',
Expand Down
29 changes: 20 additions & 9 deletions netbox/circuits/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import django_tables2 as tables
from django_tables2.utils import Accessor

from django.utils.safestring import mark_safe

from utilities.tables import BaseTable, ToggleColumn
from .models import Circuit, CircuitType, Provider

Expand All @@ -14,6 +16,21 @@
"""


class CircuitTerminationColumn(tables.Column):

def render(self, value):
if value.interface:
return mark_safe('<a href="{}" title="{}">{}</a>'.format(
value.interface.device.get_absolute_url(),
value.site,
value.interface.device
))
return mark_safe('<a href="{}">{}</a>'.format(
value.site.get_absolute_url(),
value.site
))


#
# Providers
#
Expand Down Expand Up @@ -61,15 +78,9 @@ class CircuitTable(BaseTable):
cid = tables.LinkColumn(verbose_name='ID')
provider = tables.LinkColumn('circuits:provider', args=[Accessor('provider.slug')])
tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')])
a_side = tables.LinkColumn(
'dcim:site', accessor=Accessor('termination_a.site'), orderable=False,
args=[Accessor('termination_a.site.slug')]
)
z_side = tables.LinkColumn(
'dcim:site', accessor=Accessor('termination_z.site'), orderable=False,
args=[Accessor('termination_z.site.slug')]
)
termination_a = CircuitTerminationColumn(orderable=False, verbose_name='A Side')
termination_z = CircuitTerminationColumn(orderable=False, verbose_name='Z Side')

class Meta(BaseTable.Meta):
model = Circuit
fields = ('pk', 'cid', 'type', 'provider', 'tenant', 'a_side', 'z_side', 'description')
fields = ('pk', 'cid', 'type', 'provider', 'tenant', 'termination_a', 'termination_z', 'description')
6 changes: 5 additions & 1 deletion netbox/circuits/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,11 @@ class CircuitTypeBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
#

class CircuitListView(ObjectListView):
queryset = Circuit.objects.select_related('provider', 'type', 'tenant').prefetch_related('terminations__site')
queryset = Circuit.objects.select_related(
'provider', 'type', 'tenant'
).prefetch_related(
'terminations__site', 'terminations__interface__device'
)
filter = filters.CircuitFilter
filter_form = forms.CircuitFilterForm
table = tables.CircuitTable
Expand Down
2 changes: 1 addition & 1 deletion netbox/dcim/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ class WritableRackReservationSerializer(ValidatedModelSerializer):

class Meta:
model = RackReservation
fields = ['id', 'rack', 'units', 'description']
fields = ['id', 'rack', 'units', 'user', 'description']


#
Expand Down
44 changes: 22 additions & 22 deletions netbox/dcim/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

from extras.filters import CustomFieldFilterSet
from tenancy.models import Tenant
from utilities.filters import NullableCharFieldFilter, NullableModelMultipleChoiceFilter, NumericInFilter
from utilities.filters import NullableCharFieldFilter, NumericInFilter
from virtualization.models import Cluster
from .models import (
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
Expand All @@ -21,11 +21,11 @@


class RegionFilter(django_filters.FilterSet):
parent_id = NullableModelMultipleChoiceFilter(
parent_id = django_filters.ModelMultipleChoiceFilter(
queryset=Region.objects.all(),
label='Parent region (ID)',
)
parent = NullableModelMultipleChoiceFilter(
parent = django_filters.ModelMultipleChoiceFilter(
queryset=Region.objects.all(),
to_field_name='slug',
label='Parent region (slug)',
Expand All @@ -42,20 +42,20 @@ class SiteFilter(CustomFieldFilterSet, django_filters.FilterSet):
method='search',
label='Search',
)
region_id = NullableModelMultipleChoiceFilter(
region_id = django_filters.ModelMultipleChoiceFilter(
queryset=Region.objects.all(),
label='Region (ID)',
)
region = NullableModelMultipleChoiceFilter(
region = django_filters.ModelMultipleChoiceFilter(
queryset=Region.objects.all(),
to_field_name='slug',
label='Region (slug)',
)
tenant_id = NullableModelMultipleChoiceFilter(
tenant_id = django_filters.ModelMultipleChoiceFilter(
queryset=Tenant.objects.all(),
label='Tenant (ID)',
)
tenant = NullableModelMultipleChoiceFilter(
tenant = django_filters.ModelMultipleChoiceFilter(
queryset=Tenant.objects.all(),
to_field_name='slug',
label='Tenant (slug)',
Expand Down Expand Up @@ -126,31 +126,31 @@ class RackFilter(CustomFieldFilterSet, django_filters.FilterSet):
to_field_name='slug',
label='Site (slug)',
)
group_id = NullableModelMultipleChoiceFilter(
group_id = django_filters.ModelMultipleChoiceFilter(
queryset=RackGroup.objects.all(),
label='Group (ID)',
)
group = NullableModelMultipleChoiceFilter(
group = django_filters.ModelMultipleChoiceFilter(
name='group',
queryset=RackGroup.objects.all(),
to_field_name='slug',
label='Group',
)
tenant_id = NullableModelMultipleChoiceFilter(
tenant_id = django_filters.ModelMultipleChoiceFilter(
queryset=Tenant.objects.all(),
label='Tenant (ID)',
)
tenant = NullableModelMultipleChoiceFilter(
tenant = django_filters.ModelMultipleChoiceFilter(
name='tenant',
queryset=Tenant.objects.all(),
to_field_name='slug',
label='Tenant (slug)',
)
role_id = NullableModelMultipleChoiceFilter(
role_id = django_filters.ModelMultipleChoiceFilter(
queryset=RackRole.objects.all(),
label='Role (ID)',
)
role = NullableModelMultipleChoiceFilter(
role = django_filters.ModelMultipleChoiceFilter(
name='role',
queryset=RackRole.objects.all(),
to_field_name='slug',
Expand Down Expand Up @@ -193,12 +193,12 @@ class RackReservationFilter(django_filters.FilterSet):
to_field_name='slug',
label='Site (slug)',
)
group_id = NullableModelMultipleChoiceFilter(
group_id = django_filters.ModelMultipleChoiceFilter(
name='rack__group',
queryset=RackGroup.objects.all(),
label='Group (ID)',
)
group = NullableModelMultipleChoiceFilter(
group = django_filters.ModelMultipleChoiceFilter(
name='rack__group',
queryset=RackGroup.objects.all(),
to_field_name='slug',
Expand Down Expand Up @@ -368,21 +368,21 @@ class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet):
to_field_name='slug',
label='Role (slug)',
)
tenant_id = NullableModelMultipleChoiceFilter(
tenant_id = django_filters.ModelMultipleChoiceFilter(
queryset=Tenant.objects.all(),
label='Tenant (ID)',
)
tenant = NullableModelMultipleChoiceFilter(
tenant = django_filters.ModelMultipleChoiceFilter(
name='tenant',
queryset=Tenant.objects.all(),
to_field_name='slug',
label='Tenant (slug)',
)
platform_id = NullableModelMultipleChoiceFilter(
platform_id = django_filters.ModelMultipleChoiceFilter(
queryset=Platform.objects.all(),
label='Platform (ID)',
)
platform = NullableModelMultipleChoiceFilter(
platform = django_filters.ModelMultipleChoiceFilter(
name='platform',
queryset=Platform.objects.all(),
to_field_name='slug',
Expand All @@ -405,12 +405,12 @@ class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet):
queryset=RackGroup.objects.all(),
label='Rack group (ID)',
)
rack_id = NullableModelMultipleChoiceFilter(
rack_id = django_filters.ModelMultipleChoiceFilter(
name='rack',
queryset=Rack.objects.all(),
label='Rack (ID)',
)
cluster_id = NullableModelMultipleChoiceFilter(
cluster_id = django_filters.ModelMultipleChoiceFilter(
queryset=Cluster.objects.all(),
label='VM cluster (ID)',
)
Expand Down Expand Up @@ -595,7 +595,7 @@ class Meta:


class InventoryItemFilter(DeviceComponentFilterSet):
parent_id = NullableModelMultipleChoiceFilter(
parent_id = django_filters.ModelMultipleChoiceFilter(
queryset=InventoryItem.objects.all(),
label='Parent inventory item (ID)',
)
Expand Down
14 changes: 13 additions & 1 deletion netbox/dcim/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import re

from django import forms
from django.contrib.auth.models import User
from django.contrib.postgres.forms.array import SimpleArrayField
from django.db.models import Count, Q

Expand Down Expand Up @@ -376,10 +377,11 @@ class RackFilterForm(BootstrapMixin, CustomFieldFilterForm):

class RackReservationForm(BootstrapMixin, forms.ModelForm):
units = SimpleArrayField(forms.IntegerField(), widget=ArrayFieldSelectMultiple(attrs={'size': 10}))
user = forms.ModelChoiceField(queryset=User.objects.order_by('username'))

class Meta:
model = RackReservation
fields = ['units', 'description']
fields = ['units', 'user', 'description']

def __init__(self, *args, **kwargs):

Expand Down Expand Up @@ -411,6 +413,15 @@ class RackReservationFilterForm(BootstrapMixin, forms.Form):
)


class RackReservationBulkEditForm(BootstrapMixin, BulkEditForm):
pk = forms.ModelMultipleChoiceField(queryset=RackReservation.objects.all(), widget=forms.MultipleHiddenInput)
user = forms.ModelChoiceField(queryset=User.objects.order_by('username'), required=False)
description = forms.CharField(max_length=100, required=False)

class Meta:
nullable_fields = []


#
# Manufacturers
#
Expand Down Expand Up @@ -953,6 +964,7 @@ class ChildDeviceCSVForm(BaseDeviceCSVForm):
cluster = forms.ModelChoiceField(
queryset=Cluster.objects.all(),
to_field_name='name',
required=False,
help_text='Virtualization cluster',
error_messages={
'invalid_choice': 'Invalid cluster name.',
Expand Down
22 changes: 22 additions & 0 deletions netbox/dcim/migrations/0049_rackreservation_change_user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.6 on 2017-10-31 17:32
from __future__ import unicode_literals

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('dcim', '0048_rack_serial'),
]

operations = [
migrations.AlterField(
model_name='rackreservation',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL),
),
]
11 changes: 9 additions & 2 deletions netbox/dcim/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,8 +256,8 @@ def get_absolute_url(self):

def clean(self):

# Validate that Rack is tall enough to house the installed Devices
if self.pk:
# Validate that Rack is tall enough to house the installed Devices
top_device = Device.objects.filter(rack=self).exclude(position__isnull=True).order_by('-position').first()
if top_device:
min_height = top_device.position + top_device.device_type.u_height - 1
Expand All @@ -267,6 +267,12 @@ def clean(self):
min_height
)
})
# Validate that Rack was assigned a group of its same site, if applicable
if self.group:
if self.group.site != self.site:
raise ValidationError({
'group': "Rack group must be from the same site, {}.".format(self.site)
})

def save(self, *args, **kwargs):

Expand All @@ -290,6 +296,7 @@ def to_csv(self):
self.tenant.name if self.tenant else None,
self.role.name if self.role else None,
self.get_type_display() if self.type else None,
self.serial,
self.width,
self.u_height,
self.desc_units,
Expand Down Expand Up @@ -411,7 +418,7 @@ class RackReservation(models.Model):
rack = models.ForeignKey('Rack', related_name='reservations', on_delete=models.CASCADE)
units = ArrayField(models.PositiveSmallIntegerField())
created = models.DateTimeField(auto_now_add=True)
user = models.ForeignKey(User, editable=False, on_delete=models.PROTECT)
user = models.ForeignKey(User, on_delete=models.PROTECT)
description = models.CharField(max_length=100)

class Meta:
Expand Down
3 changes: 2 additions & 1 deletion netbox/dcim/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -362,14 +362,15 @@ class DeviceRoleTable(BaseTable):
pk = ToggleColumn()
name = tables.LinkColumn(verbose_name='Name')
device_count = tables.Column(verbose_name='Devices')
vm_count = tables.Column(verbose_name='VMs')
color = tables.TemplateColumn(COLOR_LABEL, verbose_name='Label')
slug = tables.Column(verbose_name='Slug')
actions = tables.TemplateColumn(template_code=DEVICEROLE_ACTIONS, attrs={'td': {'class': 'text-right'}},
verbose_name='')

class Meta(BaseTable.Meta):
model = DeviceRole
fields = ('pk', 'name', 'device_count', 'color', 'vm_role', 'slug', 'actions')
fields = ('pk', 'name', 'device_count', 'vm_count', 'color', 'vm_role', 'slug', 'actions')


#
Expand Down
Loading

0 comments on commit 3067c3f

Please sign in to comment.