Skip to content

Commit

Permalink
Merge pull request #1913 from digitalocean/develop
Browse files Browse the repository at this point in the history
Release v2.3.0
  • Loading branch information
jeremystretch authored Feb 26, 2018
2 parents c4f7e81 + 970759e commit 957074a
Show file tree
Hide file tree
Showing 91 changed files with 4,632 additions and 1,145 deletions.
8 changes: 8 additions & 0 deletions docs/data-model/dcim.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,11 @@ Console ports connect only to console server ports, and power ports connect only
Each interface is a assigned a form factor denoting its physical properties. Two special form factors exist: the "virtual" form factor can be used to designate logical interfaces (such as SVIs), and the "LAG" form factor can be used to desinate link aggregation groups to which physical interfaces can be assigned. Each interface can also be designated as management-only (for out-of-band management) and assigned a short description.

Device bays represent the ability of a device to house child devices. For example, you might install four blade servers into a 2U chassis. The chassis would appear in the rack elevation as a 2U device with four device bays. Each server within it would be defined as a 0U device installed in one of the device bays. Child devices do not appear on rack elevations, but they are included in the "Non-Racked Devices" list within the rack view.

---

# Virtual Chassis

A virtual chassis represents a set of devices which share a single control plane: for example, a stack of switches which are managed as a single device. Each device in the virtual chassis is assigned a position and (optionally) a priority. Exactly one device is designated the virtual chassis master: This device will typically be assigned a name, secrets, services, and other attributes related to its management.

It's important to recognize the distinction between a virtual chassis and a chassis-based device. For instance, a virtual chassis is not used to model a chassis switch with removable line cards such as the Juniper EX9208, as its line cards are _not_ physically separate devices capable of operating independently.
4 changes: 1 addition & 3 deletions docs/installation/netbox.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,7 @@ Checking connectivity... done.
!!! warning
Ensure that the media directory (`/opt/netbox/netbox/media/` in this example) and all its subdirectories are writable by the user account as which NetBox runs. If the NetBox process does not have permission to write to this directory, attempts to upload files (e.g. image attachments) will fail. (The appropriate user account will vary by platform.)

```
# chown -R netbox:netbox /opt/netbox/netbox/media/
```
`# chown -R netbox:netbox /opt/netbox/netbox/media/`

## Install Python Packages

Expand Down
16 changes: 9 additions & 7 deletions netbox/circuits/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@

from rest_framework import serializers

from circuits.constants import CIRCUIT_STATUS_CHOICES
from circuits.models import Provider, Circuit, CircuitTermination, CircuitType
from dcim.api.serializers import NestedSiteSerializer, InterfaceSerializer
from extras.api.customfields import CustomFieldModelSerializer
from tenancy.api.serializers import NestedTenantSerializer
from utilities.api import ValidatedModelSerializer
from utilities.api import ChoiceFieldSerializer, ValidatedModelSerializer


#
Expand All @@ -19,7 +20,7 @@ class Meta:
model = Provider
fields = [
'id', 'name', 'slug', 'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments',
'custom_fields',
'custom_fields', 'created', 'last_updated',
]


Expand All @@ -37,7 +38,7 @@ class Meta:
model = Provider
fields = [
'id', 'name', 'slug', 'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments',
'custom_fields',
'custom_fields', 'created', 'last_updated',
]


Expand Down Expand Up @@ -66,14 +67,15 @@ class Meta:

class CircuitSerializer(CustomFieldModelSerializer):
provider = NestedProviderSerializer()
status = ChoiceFieldSerializer(choices=CIRCUIT_STATUS_CHOICES)
type = NestedCircuitTypeSerializer()
tenant = NestedTenantSerializer()

class Meta:
model = Circuit
fields = [
'id', 'cid', 'provider', 'type', 'tenant', 'install_date', 'commit_rate', 'description', 'comments',
'custom_fields',
'id', 'cid', 'provider', 'type', 'status', 'tenant', 'install_date', 'commit_rate', 'description',
'comments', 'custom_fields', 'created', 'last_updated',
]


Expand All @@ -90,8 +92,8 @@ class WritableCircuitSerializer(CustomFieldModelSerializer):
class Meta:
model = Circuit
fields = [
'id', 'cid', 'provider', 'type', 'tenant', 'install_date', 'commit_rate', 'description', 'comments',
'custom_fields',
'id', 'cid', 'provider', 'type', 'status', 'tenant', 'install_date', 'commit_rate', 'description',
'comments', 'custom_fields', 'created', 'last_updated',
]


Expand Down
9 changes: 4 additions & 5 deletions netbox/circuits/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@
from django.shortcuts import get_object_or_404
from rest_framework.decorators import detail_route
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet

from circuits import filters
from circuits.models import Provider, CircuitTermination, CircuitType, Circuit
from extras.api.serializers import RenderedGraphSerializer
from extras.api.views import CustomFieldModelViewSet
from extras.models import Graph, GRAPH_TYPE_PROVIDER
from utilities.api import FieldChoicesViewSet, WritableSerializerMixin
from utilities.api import FieldChoicesViewSet, ModelViewSet
from . import serializers


Expand All @@ -28,7 +27,7 @@ class CircuitsFieldChoicesViewSet(FieldChoicesViewSet):
# Providers
#

class ProviderViewSet(WritableSerializerMixin, CustomFieldModelViewSet):
class ProviderViewSet(CustomFieldModelViewSet):
queryset = Provider.objects.all()
serializer_class = serializers.ProviderSerializer
write_serializer_class = serializers.WritableProviderSerializer
Expand Down Expand Up @@ -59,7 +58,7 @@ class CircuitTypeViewSet(ModelViewSet):
# Circuits
#

class CircuitViewSet(WritableSerializerMixin, CustomFieldModelViewSet):
class CircuitViewSet(CustomFieldModelViewSet):
queryset = Circuit.objects.select_related('type', 'tenant', 'provider')
serializer_class = serializers.CircuitSerializer
write_serializer_class = serializers.WritableCircuitSerializer
Expand All @@ -70,7 +69,7 @@ class CircuitViewSet(WritableSerializerMixin, CustomFieldModelViewSet):
# Circuit Terminations
#

class CircuitTerminationViewSet(WritableSerializerMixin, ModelViewSet):
class CircuitTerminationViewSet(ModelViewSet):
queryset = CircuitTermination.objects.select_related('circuit', 'site', 'interface__device')
serializer_class = serializers.CircuitTerminationSerializer
write_serializer_class = serializers.WritableCircuitTerminationSerializer
Expand Down
16 changes: 16 additions & 0 deletions netbox/circuits/constants.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,22 @@
from __future__ import unicode_literals


# Circuit statuses
CIRCUIT_STATUS_DEPROVISIONING = 0
CIRCUIT_STATUS_ACTIVE = 1
CIRCUIT_STATUS_PLANNED = 2
CIRCUIT_STATUS_PROVISIONING = 3
CIRCUIT_STATUS_OFFLINE = 4
CIRCUIT_STATUS_DECOMMISSIONED = 5
CIRCUIT_STATUS_CHOICES = [
[CIRCUIT_STATUS_PLANNED, 'Planned'],
[CIRCUIT_STATUS_PROVISIONING, 'Provisioning'],
[CIRCUIT_STATUS_ACTIVE, 'Active'],
[CIRCUIT_STATUS_OFFLINE, 'Offline'],
[CIRCUIT_STATUS_DEPROVISIONING, 'Deprovisioning'],
[CIRCUIT_STATUS_DECOMMISSIONED, 'Decommissioned'],
]

# CircuitTermination sides
TERM_SIDE_A = 'A'
TERM_SIDE_Z = 'Z'
Expand Down
5 changes: 5 additions & 0 deletions netbox/circuits/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from extras.filters import CustomFieldFilterSet
from tenancy.models import Tenant
from utilities.filters import NumericInFilter
from .constants import CIRCUIT_STATUS_CHOICES
from .models import Provider, Circuit, CircuitTermination, CircuitType


Expand Down Expand Up @@ -77,6 +78,10 @@ class CircuitFilter(CustomFieldFilterSet, django_filters.FilterSet):
to_field_name='slug',
label='Circuit type (slug)',
)
status = django_filters.MultipleChoiceFilter(
choices=CIRCUIT_STATUS_CHOICES,
null_value=None
)
tenant_id = django_filters.ModelMultipleChoiceFilter(
queryset=Tenant.objects.all(),
label='Tenant (ID)',
Expand Down
25 changes: 21 additions & 4 deletions netbox/circuits/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@
from tenancy.forms import TenancyForm
from tenancy.models import Tenant
from utilities.forms import (
APISelect, BootstrapMixin, ChainedFieldsMixin, ChainedModelChoiceField, CommentField, FilterChoiceField,
SmallTextarea, SlugField,
APISelect, add_blank_choice, BootstrapMixin, ChainedFieldsMixin, ChainedModelChoiceField, CommentField,
CSVChoiceField, FilterChoiceField, SmallTextarea, SlugField,
)
from .constants import CIRCUIT_STATUS_CHOICES
from .models import Circuit, CircuitTermination, CircuitType, Provider


Expand Down Expand Up @@ -105,7 +106,7 @@ class CircuitForm(BootstrapMixin, TenancyForm, CustomFieldForm):
class Meta:
model = Circuit
fields = [
'cid', 'type', 'provider', 'install_date', 'commit_rate', 'description', 'tenant_group', 'tenant',
'cid', 'type', 'provider', 'status', 'install_date', 'commit_rate', 'description', 'tenant_group', 'tenant',
'comments',
]
help_texts = {
Expand All @@ -132,6 +133,11 @@ class CircuitCSVForm(forms.ModelForm):
'invalid_choice': 'Invalid circuit type.'
}
)
status = CSVChoiceField(
choices=CIRCUIT_STATUS_CHOICES,
required=False,
help_text='Operational status'
)
tenant = forms.ModelChoiceField(
queryset=Tenant.objects.all(),
required=False,
Expand All @@ -144,13 +150,16 @@ class CircuitCSVForm(forms.ModelForm):

class Meta:
model = Circuit
fields = ['cid', 'provider', 'type', 'tenant', 'install_date', 'commit_rate', 'description', 'comments']
fields = [
'cid', 'provider', 'type', 'status', 'tenant', 'install_date', 'commit_rate', 'description', 'comments',
]


class CircuitBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
pk = forms.ModelMultipleChoiceField(queryset=Circuit.objects.all(), widget=forms.MultipleHiddenInput)
type = forms.ModelChoiceField(queryset=CircuitType.objects.all(), required=False)
provider = forms.ModelChoiceField(queryset=Provider.objects.all(), required=False)
status = forms.ChoiceField(choices=add_blank_choice(CIRCUIT_STATUS_CHOICES), required=False, initial='')
tenant = forms.ModelChoiceField(queryset=Tenant.objects.all(), required=False)
commit_rate = forms.IntegerField(required=False, label='Commit rate (Kbps)')
description = forms.CharField(max_length=100, required=False)
Expand All @@ -160,6 +169,13 @@ class Meta:
nullable_fields = ['tenant', 'commit_rate', 'description', 'comments']


def circuit_status_choices():
status_counts = {}
for status in Circuit.objects.values('status').annotate(count=Count('status')).order_by('status'):
status_counts[status['status']] = status['count']
return [(s[0], '{} ({})'.format(s[1], status_counts.get(s[0], 0))) for s in CIRCUIT_STATUS_CHOICES]


class CircuitFilterForm(BootstrapMixin, CustomFieldFilterForm):
model = Circuit
q = forms.CharField(required=False, label='Search')
Expand All @@ -171,6 +187,7 @@ class CircuitFilterForm(BootstrapMixin, CustomFieldFilterForm):
queryset=Provider.objects.annotate(filter_count=Count('circuits')),
to_field_name='slug'
)
status = forms.MultipleChoiceField(choices=circuit_status_choices, required=False)
tenant = FilterChoiceField(
queryset=Tenant.objects.annotate(filter_count=Count('circuits')),
to_field_name='slug',
Expand Down
20 changes: 20 additions & 0 deletions netbox/circuits/migrations/0010_circuit_status.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.9 on 2018-02-06 18:48
from __future__ import unicode_literals

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('circuits', '0009_unicode_literals'),
]

operations = [
migrations.AddField(
model_name='circuit',
name='status',
field=models.PositiveSmallIntegerField(choices=[[2, 'Planned'], [3, 'Provisioning'], [1, 'Active'], [4, 'Offline'], [0, 'Deprovisioning'], [5, 'Decommissioned']], default=1),
),
]
12 changes: 10 additions & 2 deletions netbox/circuits/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
from django.urls import reverse
from django.utils.encoding import python_2_unicode_compatible

from dcim.constants import STATUS_CLASSES
from dcim.fields import ASNField
from extras.models import CustomFieldModel, CustomFieldValue
from tenancy.models import Tenant
from utilities.models import CreatedUpdatedModel
from .constants import *
from .constants import CIRCUIT_STATUS_ACTIVE, CIRCUIT_STATUS_CHOICES, TERM_SIDE_CHOICES


@python_2_unicode_compatible
Expand Down Expand Up @@ -89,14 +90,17 @@ class Circuit(CreatedUpdatedModel, CustomFieldModel):
cid = models.CharField(max_length=50, verbose_name='Circuit ID')
provider = models.ForeignKey('Provider', related_name='circuits', on_delete=models.PROTECT)
type = models.ForeignKey('CircuitType', related_name='circuits', on_delete=models.PROTECT)
status = models.PositiveSmallIntegerField(choices=CIRCUIT_STATUS_CHOICES, default=CIRCUIT_STATUS_ACTIVE)
tenant = models.ForeignKey(Tenant, related_name='circuits', blank=True, null=True, on_delete=models.PROTECT)
install_date = models.DateField(blank=True, null=True, verbose_name='Date installed')
commit_rate = models.PositiveIntegerField(blank=True, null=True, verbose_name='Commit rate (Kbps)')
description = models.CharField(max_length=100, blank=True)
comments = models.TextField(blank=True)
custom_field_values = GenericRelation(CustomFieldValue, content_type_field='obj_type', object_id_field='obj_id')

csv_headers = ['cid', 'provider', 'type', 'tenant', 'install_date', 'commit_rate', 'description', 'comments']
csv_headers = [
'cid', 'provider', 'type', 'status', 'tenant', 'install_date', 'commit_rate', 'description', 'comments',
]

class Meta:
ordering = ['provider', 'cid']
Expand All @@ -113,13 +117,17 @@ def to_csv(self):
self.cid,
self.provider.name,
self.type.name,
self.get_status_display(),
self.tenant.name if self.tenant else None,
self.install_date,
self.commit_rate,
self.description,
self.comments,
)

def get_status_class(self):
return STATUS_CLASSES[self.status]

def _get_termination(self, side):
for ct in self.terminations.all():
if ct.term_side == side:
Expand Down
7 changes: 6 additions & 1 deletion netbox/circuits/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
{% endif %}
"""

STATUS_LABEL = """
<span class="label label-{{ record.get_status_class }}">{{ record.get_status_display }}</span>
"""


class CircuitTerminationColumn(tables.Column):

Expand Down Expand Up @@ -76,10 +80,11 @@ class CircuitTable(BaseTable):
pk = ToggleColumn()
cid = tables.LinkColumn(verbose_name='ID')
provider = tables.LinkColumn('circuits:provider', args=[Accessor('provider.slug')])
status = tables.TemplateColumn(template_code=STATUS_LABEL, verbose_name='Status')
tenant = tables.TemplateColumn(template_code=COL_TENANT)
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', 'termination_a', 'termination_z', 'description')
fields = ('pk', 'cid', 'status', 'type', 'provider', 'tenant', 'termination_a', 'termination_z', 'description')
Loading

0 comments on commit 957074a

Please sign in to comment.