Merge v2.5 work

This commit is contained in:
Jeremy Stretch
2018-12-07 10:51:28 -05:00
396 changed files with 8663 additions and 6511 deletions

View File

@@ -0,0 +1,52 @@
from rest_framework import serializers
from circuits.models import Circuit, CircuitTermination, CircuitType, Provider
from utilities.api import WritableNestedSerializer
__all__ = [
'NestedCircuitSerializer',
'NestedCircuitTerminationSerializer',
'NestedCircuitTypeSerializer',
'NestedProviderSerializer',
]
#
# Providers
#
class NestedProviderSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='circuits-api:provider-detail')
class Meta:
model = Provider
fields = ['id', 'url', 'name', 'slug']
#
# Circuits
#
class NestedCircuitTypeSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='circuits-api:circuittype-detail')
class Meta:
model = CircuitType
fields = ['id', 'url', 'name', 'slug']
class NestedCircuitSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='circuits-api:circuit-detail')
class Meta:
model = Circuit
fields = ['id', 'url', 'cid']
class NestedCircuitTerminationSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='circuits-api:circuittermination-detail')
circuit = NestedCircuitSerializer()
class Meta:
model = CircuitTermination
fields = ['id', 'url', 'circuit', 'term_side']

View File

@@ -1,14 +1,13 @@
from __future__ import unicode_literals
from rest_framework import serializers
from taggit_serializer.serializers import TaggitSerializer, TagListSerializerField
from circuits.constants import CIRCUIT_STATUS_CHOICES
from circuits.models import Circuit, CircuitTermination, CircuitType, Provider
from dcim.api.serializers import NestedInterfaceSerializer, NestedSiteSerializer
from circuits.models import Provider, Circuit, CircuitTermination, CircuitType
from dcim.api.nested_serializers import NestedCableSerializer, NestedSiteSerializer
from dcim.api.serializers import ConnectedEndpointSerializer
from extras.api.customfields import CustomFieldModelSerializer
from tenancy.api.serializers import NestedTenantSerializer
from utilities.api import ChoiceField, ValidatedModelSerializer, WritableNestedSerializer
from tenancy.api.nested_serializers import NestedTenantSerializer
from utilities.api import ChoiceField, ValidatedModelSerializer
from .nested_serializers import *
#
@@ -26,16 +25,8 @@ class ProviderSerializer(TaggitSerializer, CustomFieldModelSerializer):
]
class NestedProviderSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='circuits-api:provider-detail')
class Meta:
model = Provider
fields = ['id', 'url', 'name', 'slug']
#
# Circuit types
# Circuits
#
class CircuitTypeSerializer(ValidatedModelSerializer):
@@ -45,18 +36,6 @@ class CircuitTypeSerializer(ValidatedModelSerializer):
fields = ['id', 'name', 'slug']
class NestedCircuitTypeSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='circuits-api:circuittype-detail')
class Meta:
model = CircuitType
fields = ['id', 'url', 'name', 'slug']
#
# Circuits
#
class CircuitSerializer(TaggitSerializer, CustomFieldModelSerializer):
provider = NestedProviderSerializer()
status = ChoiceField(choices=CIRCUIT_STATUS_CHOICES, required=False)
@@ -72,25 +51,14 @@ class CircuitSerializer(TaggitSerializer, CustomFieldModelSerializer):
]
class NestedCircuitSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='circuits-api:circuit-detail')
class Meta:
model = Circuit
fields = ['id', 'url', 'cid']
#
# Circuit Terminations
#
class CircuitTerminationSerializer(ValidatedModelSerializer):
class CircuitTerminationSerializer(ConnectedEndpointSerializer):
circuit = NestedCircuitSerializer()
site = NestedSiteSerializer()
interface = NestedInterfaceSerializer(required=False, allow_null=True)
cable = NestedCableSerializer(read_only=True)
class Meta:
model = CircuitTermination
fields = [
'id', 'circuit', 'term_side', 'site', 'interface', 'port_speed', 'upstream_speed', 'xconnect_id', 'pp_info',
'id', 'circuit', 'term_side', 'site', 'port_speed', 'upstream_speed', 'xconnect_id', 'pp_info',
'description', 'connected_endpoint_type', 'connected_endpoint', 'connection_status', 'cable',
]

View File

@@ -1,5 +1,3 @@
from __future__ import unicode_literals
from rest_framework import routers
from . import views
@@ -17,7 +15,7 @@ router = routers.DefaultRouter()
router.APIRootView = CircuitsRootView
# Field choices
router.register(r'_choices', views.CircuitsFieldChoicesViewSet, base_name='field-choice')
router.register(r'_choices', views.CircuitsFieldChoicesViewSet, basename='field-choice')
# Providers
router.register(r'providers', views.ProviderViewSet)

View File

@@ -1,5 +1,3 @@
from __future__ import unicode_literals
from django.shortcuts import get_object_or_404
from rest_framework.decorators import action
from rest_framework.response import Response
@@ -31,7 +29,7 @@ class CircuitsFieldChoicesViewSet(FieldChoicesViewSet):
class ProviderViewSet(CustomFieldModelViewSet):
queryset = Provider.objects.prefetch_related('tags')
serializer_class = serializers.ProviderSerializer
filter_class = filters.ProviderFilter
filterset_class = filters.ProviderFilter
@action(detail=True)
def graphs(self, request, pk=None):
@@ -51,7 +49,7 @@ class ProviderViewSet(CustomFieldModelViewSet):
class CircuitTypeViewSet(ModelViewSet):
queryset = CircuitType.objects.all()
serializer_class = serializers.CircuitTypeSerializer
filter_class = filters.CircuitTypeFilter
filterset_class = filters.CircuitTypeFilter
#
@@ -61,7 +59,7 @@ class CircuitTypeViewSet(ModelViewSet):
class CircuitViewSet(CustomFieldModelViewSet):
queryset = Circuit.objects.select_related('type', 'tenant', 'provider').prefetch_related('tags')
serializer_class = serializers.CircuitSerializer
filter_class = filters.CircuitFilter
filterset_class = filters.CircuitFilter
#
@@ -69,6 +67,8 @@ class CircuitViewSet(CustomFieldModelViewSet):
#
class CircuitTerminationViewSet(ModelViewSet):
queryset = CircuitTermination.objects.select_related('circuit', 'site', 'interface__device')
queryset = CircuitTermination.objects.select_related(
'circuit', 'site', 'connected_endpoint__device', 'cable'
)
serializer_class = serializers.CircuitTerminationSerializer
filter_class = filters.CircuitTerminationFilter
filterset_class = filters.CircuitTerminationFilter

View File

@@ -1,5 +1,3 @@
from __future__ import unicode_literals
from django.apps import AppConfig

View File

@@ -1,5 +1,3 @@
from __future__ import unicode_literals
# Circuit statuses
CIRCUIT_STATUS_DEPROVISIONING = 0

View File

@@ -1,5 +1,3 @@
from __future__ import unicode_literals
import django_filters
from django.db.models import Q
@@ -12,18 +10,21 @@ from .models import Provider, Circuit, CircuitTermination, CircuitType
class ProviderFilter(CustomFieldFilterSet, django_filters.FilterSet):
id__in = NumericInFilter(name='id', lookup_expr='in')
id__in = NumericInFilter(
field_name='id',
lookup_expr='in'
)
q = django_filters.CharFilter(
method='search',
label='Search',
)
site_id = django_filters.ModelMultipleChoiceFilter(
name='circuits__terminations__site',
field_name='circuits__terminations__site',
queryset=Site.objects.all(),
label='Site',
)
site = django_filters.ModelMultipleChoiceFilter(
name='circuits__terminations__site__slug',
field_name='circuits__terminations__site__slug',
queryset=Site.objects.all(),
to_field_name='slug',
label='Site (slug)',
@@ -54,7 +55,10 @@ class CircuitTypeFilter(django_filters.FilterSet):
class CircuitFilter(CustomFieldFilterSet, django_filters.FilterSet):
id__in = NumericInFilter(name='id', lookup_expr='in')
id__in = NumericInFilter(
field_name='id',
lookup_expr='in'
)
q = django_filters.CharFilter(
method='search',
label='Search',
@@ -64,7 +68,7 @@ class CircuitFilter(CustomFieldFilterSet, django_filters.FilterSet):
label='Provider (ID)',
)
provider = django_filters.ModelMultipleChoiceFilter(
name='provider__slug',
field_name='provider__slug',
queryset=Provider.objects.all(),
to_field_name='slug',
label='Provider (slug)',
@@ -74,7 +78,7 @@ class CircuitFilter(CustomFieldFilterSet, django_filters.FilterSet):
label='Circuit type (ID)',
)
type = django_filters.ModelMultipleChoiceFilter(
name='type__slug',
field_name='type__slug',
queryset=CircuitType.objects.all(),
to_field_name='slug',
label='Circuit type (slug)',
@@ -88,18 +92,18 @@ class CircuitFilter(CustomFieldFilterSet, django_filters.FilterSet):
label='Tenant (ID)',
)
tenant = django_filters.ModelMultipleChoiceFilter(
name='tenant__slug',
field_name='tenant__slug',
queryset=Tenant.objects.all(),
to_field_name='slug',
label='Tenant (slug)',
)
site_id = django_filters.ModelMultipleChoiceFilter(
name='terminations__site',
field_name='terminations__site',
queryset=Site.objects.all(),
label='Site (ID)',
)
site = django_filters.ModelMultipleChoiceFilter(
name='terminations__site__slug',
field_name='terminations__site__slug',
queryset=Site.objects.all(),
to_field_name='slug',
label='Site (slug)',
@@ -117,6 +121,7 @@ class CircuitFilter(CustomFieldFilterSet, django_filters.FilterSet):
Q(cid__icontains=value) |
Q(terminations__xconnect_id__icontains=value) |
Q(terminations__pp_info__icontains=value) |
Q(terminations__description__icontains=value) |
Q(description__icontains=value) |
Q(comments__icontains=value)
).distinct()
@@ -136,7 +141,7 @@ class CircuitTerminationFilter(django_filters.FilterSet):
label='Site (ID)',
)
site = django_filters.ModelMultipleChoiceFilter(
name='site__slug',
field_name='site__slug',
queryset=Site.objects.all(),
to_field_name='slug',
label='Site (slug)',
@@ -152,5 +157,6 @@ class CircuitTerminationFilter(django_filters.FilterSet):
return queryset.filter(
Q(circuit__cid__icontains=value) |
Q(xconnect_id__icontains=value) |
Q(pp_info__icontains=value)
Q(pp_info__icontains=value) |
Q(description__icontains=value)
).distinct()

View File

@@ -1,16 +1,14 @@
from __future__ import unicode_literals
from django import forms
from django.db.models import Count
from taggit.forms import TagField
from dcim.models import Site, Device, Interface, Rack
from dcim.models import Site
from extras.forms import AddRemoveTagsForm, CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm
from tenancy.forms import TenancyForm
from tenancy.models import Tenant
from utilities.forms import (
AnnotatedMultipleChoiceField, APISelect, add_blank_choice, BootstrapMixin, ChainedFieldsMixin,
ChainedModelChoiceField, CommentField, CSVChoiceField, FilterChoiceField, SmallTextarea, SlugField,
AnnotatedMultipleChoiceField, add_blank_choice, BootstrapMixin, CommentField, CSVChoiceField, FilterChoiceField,
SmallTextarea, SlugField,
)
from .constants import CIRCUIT_STATUS_CHOICES
from .models import Circuit, CircuitTermination, CircuitType, Provider
@@ -23,14 +21,22 @@ from .models import Circuit, CircuitTermination, CircuitType, Provider
class ProviderForm(BootstrapMixin, CustomFieldForm):
slug = SlugField()
comments = CommentField()
tags = TagField(required=False)
tags = TagField(
required=False
)
class Meta:
model = Provider
fields = ['name', 'slug', 'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments', 'tags']
fields = [
'name', 'slug', 'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments', 'tags',
]
widgets = {
'noc_contact': SmallTextarea(attrs={'rows': 5}),
'admin_contact': SmallTextarea(attrs={'rows': 5}),
'noc_contact': SmallTextarea(
attrs={'rows': 5}
),
'admin_contact': SmallTextarea(
attrs={'rows': 5}
),
}
help_texts = {
'name': "Full name of the provider",
@@ -56,23 +62,57 @@ class ProviderCSVForm(forms.ModelForm):
class ProviderBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm):
pk = forms.ModelMultipleChoiceField(queryset=Provider.objects.all(), widget=forms.MultipleHiddenInput)
asn = forms.IntegerField(required=False, label='ASN')
account = forms.CharField(max_length=30, required=False, label='Account number')
portal_url = forms.URLField(required=False, label='Portal')
noc_contact = forms.CharField(required=False, widget=SmallTextarea, label='NOC contact')
admin_contact = forms.CharField(required=False, widget=SmallTextarea, label='Admin contact')
comments = CommentField(widget=SmallTextarea)
pk = forms.ModelMultipleChoiceField(
queryset=Provider.objects.all(),
widget=forms.MultipleHiddenInput
)
asn = forms.IntegerField(
required=False,
label='ASN'
)
account = forms.CharField(
max_length=30,
required=False,
label='Account number'
)
portal_url = forms.URLField(
required=False,
label='Portal'
)
noc_contact = forms.CharField(
required=False,
widget=SmallTextarea,
label='NOC contact'
)
admin_contact = forms.CharField(
required=False,
widget=SmallTextarea,
label='Admin contact'
)
comments = CommentField(
widget=SmallTextarea()
)
class Meta:
nullable_fields = ['asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments']
nullable_fields = [
'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'comments',
]
class ProviderFilterForm(BootstrapMixin, CustomFieldFilterForm):
model = Provider
q = forms.CharField(required=False, label='Search')
site = FilterChoiceField(queryset=Site.objects.all(), to_field_name='slug')
asn = forms.IntegerField(required=False, label='ASN')
q = forms.CharField(
required=False,
label='Search'
)
site = FilterChoiceField(
queryset=Site.objects.all(),
to_field_name='slug'
)
asn = forms.IntegerField(
required=False,
label='ASN'
)
#
@@ -84,7 +124,9 @@ class CircuitTypeForm(BootstrapMixin, forms.ModelForm):
class Meta:
model = CircuitType
fields = ['name', 'slug']
fields = [
'name', 'slug',
]
class CircuitTypeCSVForm(forms.ModelForm):
@@ -104,7 +146,9 @@ class CircuitTypeCSVForm(forms.ModelForm):
class CircuitForm(BootstrapMixin, TenancyForm, CustomFieldForm):
comments = CommentField()
tags = TagField(required=False)
tags = TagField(
required=False
)
class Meta:
model = Circuit
@@ -159,28 +203,61 @@ class CircuitCSVForm(forms.ModelForm):
class CircuitBulkEditForm(BootstrapMixin, AddRemoveTagsForm, 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)
comments = CommentField(widget=SmallTextarea)
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
)
comments = CommentField(
widget=SmallTextarea
)
class Meta:
nullable_fields = ['tenant', 'commit_rate', 'description', 'comments']
nullable_fields = [
'tenant', 'commit_rate', 'description', 'comments',
]
class CircuitFilterForm(BootstrapMixin, CustomFieldFilterForm):
model = Circuit
q = forms.CharField(required=False, label='Search')
q = forms.CharField(
required=False,
label='Search'
)
type = FilterChoiceField(
queryset=CircuitType.objects.annotate(filter_count=Count('circuits')),
queryset=CircuitType.objects.annotate(
filter_count=Count('circuits')
),
to_field_name='slug'
)
provider = FilterChoiceField(
queryset=Provider.objects.annotate(filter_count=Count('circuits')),
queryset=Provider.objects.annotate(
filter_count=Count('circuits')
),
to_field_name='slug'
)
status = AnnotatedMultipleChoiceField(
@@ -190,74 +267,35 @@ class CircuitFilterForm(BootstrapMixin, CustomFieldFilterForm):
required=False
)
tenant = FilterChoiceField(
queryset=Tenant.objects.annotate(filter_count=Count('circuits')),
queryset=Tenant.objects.annotate(
filter_count=Count('circuits')
),
to_field_name='slug',
null_label='-- None --'
)
site = FilterChoiceField(
queryset=Site.objects.annotate(filter_count=Count('circuit_terminations')),
queryset=Site.objects.annotate(
filter_count=Count('circuit_terminations')
),
to_field_name='slug'
)
commit_rate = forms.IntegerField(required=False, min_value=0, label='Commit rate (Kbps)')
commit_rate = forms.IntegerField(
required=False,
min_value=0,
label='Commit rate (Kbps)'
)
#
# Circuit terminations
#
class CircuitTerminationForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelForm):
site = forms.ModelChoiceField(
queryset=Site.objects.all(),
widget=forms.Select(
attrs={'filter-for': 'rack'}
)
)
rack = ChainedModelChoiceField(
queryset=Rack.objects.all(),
chains=(
('site', 'site'),
),
required=False,
label='Rack',
widget=APISelect(
api_url='/api/dcim/racks/?site_id={{site}}',
attrs={'filter-for': 'device', 'nullable': 'true'}
)
)
device = ChainedModelChoiceField(
queryset=Device.objects.all(),
chains=(
('site', 'site'),
('rack', 'rack'),
),
required=False,
label='Device',
widget=APISelect(
api_url='/api/dcim/devices/?site_id={{site}}&rack_id={{rack}}',
display_field='display_name',
attrs={'filter-for': 'interface'}
)
)
interface = ChainedModelChoiceField(
queryset=Interface.objects.connectable().select_related(
'circuit_termination', 'connected_as_a', 'connected_as_b'
),
chains=(
('device', 'device'),
),
required=False,
label='Interface',
widget=APISelect(
api_url='/api/dcim/interfaces/?device_id={{device}}&type=physical',
disabled_indicator='is_connected'
)
)
class CircuitTerminationForm(BootstrapMixin, forms.ModelForm):
class Meta:
model = CircuitTermination
fields = [
'term_side', 'site', 'rack', 'device', 'interface', 'port_speed', 'upstream_speed', 'xconnect_id',
'pp_info',
'term_side', 'site', 'port_speed', 'upstream_speed', 'xconnect_id', 'pp_info', 'description',
]
help_texts = {
'port_speed': "Physical circuit speed",
@@ -267,25 +305,3 @@ class CircuitTerminationForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelForm
widgets = {
'term_side': forms.HiddenInput(),
}
def __init__(self, *args, **kwargs):
# Initialize helper selectors
instance = kwargs.get('instance')
if instance and instance.interface is not None:
initial = kwargs.get('initial', {}).copy()
initial['rack'] = instance.interface.device.rack
initial['device'] = instance.interface.device
kwargs['initial'] = initial
super(CircuitTerminationForm, self).__init__(*args, **kwargs)
# Mark connected interfaces as disabled
self.fields['interface'].choices = []
for iface in self.fields['interface'].queryset:
self.fields['interface'].choices.append(
(iface.id, {
'label': iface.name,
'disabled': iface.is_connected and iface.pk != self.initial.get('interface'),
})
)

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-06-22 18:21
from __future__ import unicode_literals
from django.db import migrations, models

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.14 on 2018-07-31 02:25
from __future__ import unicode_literals
import dcim.fields
from django.db import migrations, models
import django.db.models.deletion

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-06-22 18:21
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-07-13 19:24
from __future__ import unicode_literals
import dcim.fields
from django.db import migrations

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.8 on 2016-07-26 21:59
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.8 on 2016-08-08 20:24
from __future__ import unicode_literals
from django.db import migrations, models

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10 on 2016-12-13 16:30
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.4 on 2017-01-17 20:08
from __future__ import unicode_literals
from django.db import migrations, models

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2017-04-19 17:17
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2017-05-24 15:34
from __future__ import unicode_literals
import dcim.fields
from django.db import migrations, models

View File

@@ -1,7 +1,5 @@
# -*- 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

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.12 on 2018-05-22 19:04
from __future__ import unicode_literals
from django.db import migrations
import taggit.managers

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.12 on 2018-06-13 17:14
from __future__ import unicode_literals
from django.db import migrations, models

View File

@@ -0,0 +1,89 @@
import sys
from django.db import migrations, models
import django.db.models.deletion
from dcim.constants import CONNECTION_STATUS_CONNECTED
def circuit_terminations_to_cables(apps, schema_editor):
"""
Copy all existing CircuitTermination Interface associations as Cables
"""
ContentType = apps.get_model('contenttypes', 'ContentType')
CircuitTermination = apps.get_model('circuits', 'CircuitTermination')
Interface = apps.get_model('dcim', 'Interface')
Cable = apps.get_model('dcim', 'Cable')
# Load content types
circuittermination_type = ContentType.objects.get_for_model(CircuitTermination)
interface_type = ContentType.objects.get_for_model(Interface)
# Create a new Cable instance from each console connection
if 'test' not in sys.argv:
print("\n Adding circuit terminations... ", end='', flush=True)
for circuittermination in CircuitTermination.objects.filter(interface__isnull=False):
# Create the new Cable
cable = Cable.objects.create(
termination_a_type=circuittermination_type,
termination_a_id=circuittermination.id,
termination_b_type=interface_type,
termination_b_id=circuittermination.interface_id,
status=CONNECTION_STATUS_CONNECTED
)
# Cache the Cable on its two termination points
CircuitTermination.objects.filter(pk=circuittermination.pk).update(
cable=cable,
connected_endpoint=circuittermination.interface,
connection_status=CONNECTION_STATUS_CONNECTED
)
# Cache the connected Cable on the Interface
Interface.objects.filter(pk=circuittermination.interface_id).update(
cable=cable,
_connected_circuittermination=circuittermination,
connection_status=CONNECTION_STATUS_CONNECTED
)
cable_count = Cable.objects.filter(termination_a_type=circuittermination_type).count()
if 'test' not in sys.argv:
print("{} cables created".format(cable_count))
class Migration(migrations.Migration):
atomic = False
dependencies = [
('circuits', '0012_change_logging'),
('dcim', '0066_cables'),
]
operations = [
# Add new CircuitTermination fields
migrations.AddField(
model_name='circuittermination',
name='connected_endpoint',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='dcim.Interface'),
),
migrations.AddField(
model_name='circuittermination',
name='connection_status',
field=models.NullBooleanField(),
),
migrations.AddField(
model_name='circuittermination',
name='cable',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='dcim.Cable'),
),
# Copy CircuitTermination connections to Interfaces as Cables
migrations.RunPython(circuit_terminations_to_cables),
# Remove interface field from CircuitTermination
migrations.RemoveField(
model_name='circuittermination',
name='interface',
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 2.1.3 on 2018-11-05 18:38
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('circuits', '0013_cables'),
]
operations = [
migrations.AddField(
model_name='circuittermination',
name='description',
field=models.CharField(blank=True, max_length=100),
),
]

View File

@@ -1,20 +1,17 @@
from __future__ import unicode_literals
from django.contrib.contenttypes.fields import GenericRelation
from django.db import models
from django.urls import reverse
from django.utils.encoding import python_2_unicode_compatible
from taggit.managers import TaggableManager
from dcim.constants import STATUS_CLASSES
from dcim.constants import CONNECTION_STATUS_CHOICES, CONNECTION_STATUS_CONNECTED, STATUS_CLASSES
from dcim.fields import ASNField
from dcim.models import CableTermination
from extras.models import CustomFieldModel, ObjectChange
from utilities.models import ChangeLoggedModel
from utilities.utils import serialize_object
from .constants import CIRCUIT_STATUS_ACTIVE, CIRCUIT_STATUS_CHOICES, TERM_SIDE_CHOICES
@python_2_unicode_compatible
class Provider(ChangeLoggedModel, CustomFieldModel):
"""
Each Circuit belongs to a Provider. This is usually a telecommunications company or similar organization. This model
@@ -84,7 +81,6 @@ class Provider(ChangeLoggedModel, CustomFieldModel):
)
@python_2_unicode_compatible
class CircuitType(ChangeLoggedModel):
"""
Circuits can be organized by their functional role. For example, a user might wish to define CircuitTypes named
@@ -116,12 +112,11 @@ class CircuitType(ChangeLoggedModel):
)
@python_2_unicode_compatible
class Circuit(ChangeLoggedModel, CustomFieldModel):
"""
A communications circuit connects two points. Each Circuit belongs to a Provider; Providers may have multiple
circuits. Each circuit is also assigned a CircuitType and a Site. A Circuit may be terminated to a specific device
interface, but this is not required. Circuit port speed and commit rate are measured in Kbps.
circuits. Each circuit is also assigned a CircuitType and a Site. Circuit port speed and commit rate are measured
in Kbps.
"""
cid = models.CharField(
max_length=50,
@@ -217,8 +212,7 @@ class Circuit(ChangeLoggedModel, CustomFieldModel):
return self._get_termination('Z')
@python_2_unicode_compatible
class CircuitTermination(models.Model):
class CircuitTermination(CableTermination):
circuit = models.ForeignKey(
to='circuits.Circuit',
on_delete=models.CASCADE,
@@ -234,13 +228,17 @@ class CircuitTermination(models.Model):
on_delete=models.PROTECT,
related_name='circuit_terminations'
)
interface = models.OneToOneField(
connected_endpoint = models.OneToOneField(
to='dcim.Interface',
on_delete=models.PROTECT,
related_name='circuit_termination',
on_delete=models.SET_NULL,
related_name='+',
blank=True,
null=True
)
connection_status = models.NullBooleanField(
choices=CONNECTION_STATUS_CHOICES,
blank=True
)
port_speed = models.PositiveIntegerField(
verbose_name='Port speed (Kbps)'
)
@@ -260,13 +258,17 @@ class CircuitTermination(models.Model):
blank=True,
verbose_name='Patch panel/port(s)'
)
description = models.CharField(
max_length=100,
blank=True
)
class Meta:
ordering = ['circuit', 'term_side']
unique_together = ['circuit', 'term_side']
def __str__(self):
return '{} (Side {})'.format(self.circuit, self.get_term_side_display())
return 'Side {}'.format(self.get_term_side_display())
def log_change(self, user, request_id, action):
"""

View File

@@ -1,5 +1,3 @@
from __future__ import unicode_literals
from django.db.models.signals import post_delete, post_save
from django.dispatch import receiver
from django.utils import timezone

View File

@@ -1,5 +1,3 @@
from __future__ import unicode_literals
import django_tables2 as tables
from django.utils.safestring import mark_safe
from django_tables2.utils import Accessor
@@ -25,12 +23,6 @@ STATUS_LABEL = """
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

View File

@@ -1,5 +1,3 @@
from __future__ import unicode_literals
from django.urls import reverse
from rest_framework import status
@@ -15,7 +13,7 @@ class ProviderTest(APITestCase):
def setUp(self):
super(ProviderTest, self).setUp()
super().setUp()
self.provider1 = Provider.objects.create(name='Test Provider 1', slug='test-provider-1')
self.provider2 = Provider.objects.create(name='Test Provider 2', slug='test-provider-2')
@@ -137,7 +135,7 @@ class CircuitTypeTest(APITestCase):
def setUp(self):
super(CircuitTypeTest, self).setUp()
super().setUp()
self.circuittype1 = CircuitType.objects.create(name='Test Circuit Type 1', slug='test-circuit-type-1')
self.circuittype2 = CircuitType.objects.create(name='Test Circuit Type 2', slug='test-circuit-type-2')
@@ -212,7 +210,7 @@ class CircuitTest(APITestCase):
def setUp(self):
super(CircuitTest, self).setUp()
super().setUp()
self.provider1 = Provider.objects.create(name='Test Provider 1', slug='test-provider-1')
self.provider2 = Provider.objects.create(name='Test Provider 2', slug='test-provider-2')
@@ -328,46 +326,26 @@ class CircuitTerminationTest(APITestCase):
def setUp(self):
super(CircuitTerminationTest, self).setUp()
super().setUp()
self.site1 = Site.objects.create(name='Test Site 1', slug='test-site-1')
self.site2 = Site.objects.create(name='Test Site 2', slug='test-site-2')
manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
devicetype = DeviceType.objects.create(
manufacturer=manufacturer, model='Test Device Type 1', slug='test-device-type-1', is_network_device=True
)
devicerole = DeviceRole.objects.create(
name='Test Device Role 1', slug='test-device-role-1', color='ff0000'
)
device1 = Device.objects.create(
device_type=devicetype, device_role=devicerole, name='Test Device 1', site=self.site1
)
device2 = Device.objects.create(
device_type=devicetype, device_role=devicerole, name='Test Device 2', site=self.site2
)
self.interface1 = Interface.objects.create(device=device1, name='Test Interface 1')
self.interface2 = Interface.objects.create(device=device2, name='Test Interface 2')
self.interface3 = Interface.objects.create(device=device1, name='Test Interface 3')
self.interface4 = Interface.objects.create(device=device2, name='Test Interface 4')
self.interface5 = Interface.objects.create(device=device1, name='Test Interface 5')
self.interface6 = Interface.objects.create(device=device2, name='Test Interface 6')
provider = Provider.objects.create(name='Test Provider', slug='test-provider')
circuittype = CircuitType.objects.create(name='Test Circuit Type', slug='test-circuit-type')
self.circuit1 = Circuit.objects.create(cid='TEST0001', provider=provider, type=circuittype)
self.circuit2 = Circuit.objects.create(cid='TEST0002', provider=provider, type=circuittype)
self.circuit3 = Circuit.objects.create(cid='TEST0003', provider=provider, type=circuittype)
self.circuittermination1 = CircuitTermination.objects.create(
circuit=self.circuit1, term_side=TERM_SIDE_A, site=self.site1, interface=self.interface1, port_speed=1000000
circuit=self.circuit1, term_side=TERM_SIDE_A, site=self.site1, port_speed=1000000
)
self.circuittermination2 = CircuitTermination.objects.create(
circuit=self.circuit1, term_side=TERM_SIDE_Z, site=self.site2, interface=self.interface2, port_speed=1000000
circuit=self.circuit1, term_side=TERM_SIDE_Z, site=self.site2, port_speed=1000000
)
self.circuittermination3 = CircuitTermination.objects.create(
circuit=self.circuit2, term_side=TERM_SIDE_A, site=self.site1, interface=self.interface3, port_speed=1000000
circuit=self.circuit2, term_side=TERM_SIDE_A, site=self.site1, port_speed=1000000
)
self.circuittermination4 = CircuitTermination.objects.create(
circuit=self.circuit2, term_side=TERM_SIDE_Z, site=self.site2, interface=self.interface4, port_speed=1000000
circuit=self.circuit2, term_side=TERM_SIDE_Z, site=self.site2, port_speed=1000000
)
def test_get_circuittermination(self):
@@ -390,7 +368,6 @@ class CircuitTerminationTest(APITestCase):
'circuit': self.circuit3.pk,
'term_side': TERM_SIDE_A,
'site': self.site1.pk,
'interface': self.interface5.pk,
'port_speed': 1000000,
}
@@ -403,20 +380,18 @@ class CircuitTerminationTest(APITestCase):
self.assertEqual(circuittermination4.circuit_id, data['circuit'])
self.assertEqual(circuittermination4.term_side, data['term_side'])
self.assertEqual(circuittermination4.site_id, data['site'])
self.assertEqual(circuittermination4.interface_id, data['interface'])
self.assertEqual(circuittermination4.port_speed, data['port_speed'])
def test_update_circuittermination(self):
circuittermination5 = CircuitTermination.objects.create(
circuit=self.circuit3, term_side=TERM_SIDE_A, site=self.site1, interface=self.interface5, port_speed=1000000
circuit=self.circuit3, term_side=TERM_SIDE_A, site=self.site1, port_speed=1000000
)
data = {
'circuit': self.circuit3.pk,
'term_side': TERM_SIDE_Z,
'site': self.site2.pk,
'interface': self.interface6.pk,
'port_speed': 1000000,
}
@@ -428,7 +403,6 @@ class CircuitTerminationTest(APITestCase):
circuittermination1 = CircuitTermination.objects.get(pk=response.data['id'])
self.assertEqual(circuittermination1.term_side, data['term_side'])
self.assertEqual(circuittermination1.site_id, data['site'])
self.assertEqual(circuittermination1.interface_id, data['interface'])
self.assertEqual(circuittermination1.port_speed, data['port_speed'])
def test_delete_circuittermination(self):

View File

@@ -1,10 +1,9 @@
from __future__ import unicode_literals
from django.conf.urls import url
from dcim.views import CableCreateView, CableTraceView
from extras.views import ObjectChangeLogView
from . import views
from .models import Circuit, CircuitType, Provider
from .models import Circuit, CircuitTermination, CircuitType, Provider
app_name = 'circuits'
urlpatterns = [
@@ -44,5 +43,7 @@ urlpatterns = [
url(r'^circuits/(?P<circuit>\d+)/terminations/add/$', views.CircuitTerminationCreateView.as_view(), name='circuittermination_add'),
url(r'^circuit-terminations/(?P<pk>\d+)/edit/$', views.CircuitTerminationEditView.as_view(), name='circuittermination_edit'),
url(r'^circuit-terminations/(?P<pk>\d+)/delete/$', views.CircuitTerminationDeleteView.as_view(), name='circuittermination_delete'),
url(r'^circuit-terminations/(?P<termination_a_id>\d+)/connect/$', CableCreateView.as_view(), name='circuittermination_connect', kwargs={'termination_a_type': CircuitTermination}),
url(r'^circuit-terminations/(?P<pk>\d+)/trace/$', CableTraceView.as_view(), name='circuittermination_trace', kwargs={'model': CircuitTermination}),
]

View File

@@ -1,5 +1,3 @@
from __future__ import unicode_literals
from django.contrib import messages
from django.contrib.auth.decorators import permission_required
from django.contrib.auth.mixins import PermissionRequiredMixin
@@ -134,7 +132,7 @@ class CircuitListView(ObjectListView):
queryset = Circuit.objects.select_related(
'provider', 'type', 'tenant'
).prefetch_related(
'terminations__site', 'terminations__interface__device'
'terminations__site'
)
filter = filters.CircuitFilter
filter_form = forms.CircuitFilterForm
@@ -148,12 +146,12 @@ class CircuitView(View):
circuit = get_object_or_404(Circuit.objects.select_related('provider', 'type', 'tenant__group'), pk=pk)
termination_a = CircuitTermination.objects.select_related(
'site__region', 'interface__device'
'site__region', 'connected_endpoint__device'
).filter(
circuit=circuit, term_side=TERM_SIDE_A
).first()
termination_z = CircuitTermination.objects.select_related(
'site__region', 'interface__device'
'site__region', 'connected_endpoint__device'
).filter(
circuit=circuit, term_side=TERM_SIDE_Z
).first()

View File

@@ -1,5 +1,3 @@
from __future__ import unicode_literals
from rest_framework.exceptions import APIException

View File

@@ -0,0 +1,249 @@
from rest_framework import serializers
from dcim.constants import CONNECTION_STATUS_CHOICES
from dcim.models import (
Cable, ConsolePort, ConsoleServerPort, Device, DeviceBay, DeviceType, DeviceRole, FrontPort, FrontPortTemplate,
Interface, Manufacturer, Platform, PowerOutlet, PowerPort, Rack, RackGroup, RackRole, RearPort, RearPortTemplate,
Region, Site, VirtualChassis,
)
from utilities.api import ChoiceField, WritableNestedSerializer
__all__ = [
'NestedCableSerializer',
'NestedConsolePortSerializer',
'NestedConsoleServerPortSerializer',
'NestedDeviceBaySerializer',
'NestedDeviceRoleSerializer',
'NestedDeviceSerializer',
'NestedDeviceTypeSerializer',
'NestedFrontPortSerializer',
'NestedFrontPortTemplateSerializer',
'NestedInterfaceSerializer',
'NestedManufacturerSerializer',
'NestedPlatformSerializer',
'NestedPowerOutletSerializer',
'NestedPowerPortSerializer',
'NestedRackGroupSerializer',
'NestedRackRoleSerializer',
'NestedRackSerializer',
'NestedRearPortSerializer',
'NestedRearPortTemplateSerializer',
'NestedRegionSerializer',
'NestedSiteSerializer',
'NestedVirtualChassisSerializer',
]
#
# Regions/sites
#
class NestedRegionSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:region-detail')
class Meta:
model = Region
fields = ['id', 'url', 'name', 'slug']
class NestedSiteSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:site-detail')
class Meta:
model = Site
fields = ['id', 'url', 'name', 'slug']
#
# Racks
#
class NestedRackGroupSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rackgroup-detail')
class Meta:
model = RackGroup
fields = ['id', 'url', 'name', 'slug']
class NestedRackRoleSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rackrole-detail')
class Meta:
model = RackRole
fields = ['id', 'url', 'name', 'slug']
class NestedRackSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rack-detail')
class Meta:
model = Rack
fields = ['id', 'url', 'name', 'display_name']
#
# Device types
#
class NestedManufacturerSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:manufacturer-detail')
class Meta:
model = Manufacturer
fields = ['id', 'url', 'name', 'slug']
class NestedDeviceTypeSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicetype-detail')
manufacturer = NestedManufacturerSerializer(read_only=True)
class Meta:
model = DeviceType
fields = ['id', 'url', 'manufacturer', 'model', 'slug']
class NestedRearPortTemplateSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rearporttemplate-detail')
class Meta:
model = RearPortTemplate
fields = ['id', 'url', 'name']
class NestedFrontPortTemplateSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:frontporttemplate-detail')
class Meta:
model = FrontPortTemplate
fields = ['id', 'url', 'name']
#
# Devices
#
class NestedDeviceRoleSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicerole-detail')
class Meta:
model = DeviceRole
fields = ['id', 'url', 'name', 'slug']
class NestedPlatformSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:platform-detail')
class Meta:
model = Platform
fields = ['id', 'url', 'name', 'slug']
class NestedDeviceSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:device-detail')
class Meta:
model = Device
fields = ['id', 'url', 'name', 'display_name']
class NestedConsoleServerPortSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:consoleserverport-detail')
device = NestedDeviceSerializer(read_only=True)
connection_status = ChoiceField(choices=CONNECTION_STATUS_CHOICES, read_only=True)
class Meta:
model = ConsoleServerPort
fields = ['id', 'url', 'device', 'name', 'cable', 'connection_status']
class NestedConsolePortSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:consoleport-detail')
device = NestedDeviceSerializer(read_only=True)
connection_status = ChoiceField(choices=CONNECTION_STATUS_CHOICES, read_only=True)
class Meta:
model = ConsolePort
fields = ['id', 'url', 'device', 'name', 'cable', 'connection_status']
class NestedPowerOutletSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:poweroutlet-detail')
device = NestedDeviceSerializer(read_only=True)
connection_status = ChoiceField(choices=CONNECTION_STATUS_CHOICES, read_only=True)
class Meta:
model = PowerOutlet
fields = ['id', 'url', 'device', 'name', 'cable', 'connection_status']
class NestedPowerPortSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:powerport-detail')
device = NestedDeviceSerializer(read_only=True)
connection_status = ChoiceField(choices=CONNECTION_STATUS_CHOICES, read_only=True)
class Meta:
model = PowerPort
fields = ['id', 'url', 'device', 'name', 'cable', 'connection_status']
class NestedInterfaceSerializer(WritableNestedSerializer):
device = NestedDeviceSerializer(read_only=True)
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:interface-detail')
connection_status = ChoiceField(choices=CONNECTION_STATUS_CHOICES, read_only=True)
class Meta:
model = Interface
fields = ['id', 'url', 'device', 'name', 'cable', 'connection_status']
class NestedRearPortSerializer(WritableNestedSerializer):
device = NestedDeviceSerializer(read_only=True)
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rearport-detail')
class Meta:
model = RearPort
fields = ['id', 'url', 'device', 'name', 'cable']
class NestedFrontPortSerializer(WritableNestedSerializer):
device = NestedDeviceSerializer(read_only=True)
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:frontport-detail')
class Meta:
model = FrontPort
fields = ['id', 'url', 'device', 'name', 'cable']
class NestedDeviceBaySerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rearport-detail')
device = NestedDeviceSerializer(read_only=True)
class Meta:
model = DeviceBay
fields = ['id', 'url', 'device', 'name']
#
# Cables
#
class NestedCableSerializer(serializers.ModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:cable-detail')
class Meta:
model = Cable
fields = ['id', 'url', 'label']
#
# Virtual chassis
#
class NestedVirtualChassisSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:virtualchassis-detail')
master = NestedDeviceSerializer()
class Meta:
model = VirtualChassis
fields = ['id', 'url', 'master']

View File

@@ -1,43 +1,58 @@
from __future__ import unicode_literals
from rest_framework import serializers
from rest_framework.validators import UniqueTogetherValidator
from taggit_serializer.serializers import TaggitSerializer, TagListSerializerField
from circuits.models import Circuit, CircuitTermination
from dcim.constants import (
CONNECTION_STATUS_CHOICES, DEVICE_STATUS_CHOICES, IFACE_FF_CHOICES, IFACE_MODE_CHOICES, IFACE_ORDERING_CHOICES,
RACK_FACE_CHOICES, RACK_TYPE_CHOICES, RACK_WIDTH_CHOICES, SITE_STATUS_CHOICES, SUBDEVICE_ROLE_CHOICES,
)
from dcim.constants import *
from dcim.models import (
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
DeviceBayTemplate, DeviceType, DeviceRole, Interface, InterfaceConnection, InterfaceTemplate, Manufacturer,
InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup,
RackReservation, RackRole, Region, Site, VirtualChassis,
Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
DeviceBayTemplate, DeviceType, DeviceRole, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate,
Manufacturer, InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack,
RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site, VirtualChassis,
)
from extras.api.customfields import CustomFieldModelSerializer
from ipam.models import IPAddress, VLAN
from tenancy.api.serializers import NestedTenantSerializer
from users.api.serializers import NestedUserSerializer
from ipam.api.nested_serializers import NestedIPAddressSerializer, NestedVLANSerializer
from ipam.models import VLAN
from tenancy.api.nested_serializers import NestedTenantSerializer
from users.api.nested_serializers import NestedUserSerializer
from utilities.api import (
ChoiceField, SerializedPKRelatedField, TimeZoneField, ValidatedModelSerializer,
WritableNestedSerializer,
ChoiceField, ContentTypeField, SerializedPKRelatedField, TimeZoneField, ValidatedModelSerializer,
WritableNestedSerializer, get_serializer_for_model,
)
from virtualization.models import Cluster
from virtualization.api.nested_serializers import NestedClusterSerializer
from .nested_serializers import *
class ConnectedEndpointSerializer(ValidatedModelSerializer):
connected_endpoint_type = serializers.SerializerMethodField(read_only=True)
connected_endpoint = serializers.SerializerMethodField(read_only=True)
connection_status = ChoiceField(choices=CONNECTION_STATUS_CHOICES, read_only=True)
def get_connected_endpoint_type(self, obj):
if hasattr(obj, 'connected_endpoint') and obj.connected_endpoint is not None:
return '{}.{}'.format(
obj.connected_endpoint._meta.app_label,
obj.connected_endpoint._meta.model_name
)
return None
def get_connected_endpoint(self, obj):
"""
Return the appropriate serializer for the type of connected object.
"""
if getattr(obj, 'connected_endpoint', None) is None:
return None
serializer = get_serializer_for_model(obj.connected_endpoint, prefix='Nested')
context = {'request': self.context['request']}
data = serializer(obj.connected_endpoint, context=context).data
return data
#
# Regions
# Regions/sites
#
class NestedRegionSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:region-detail')
class Meta:
model = Region
fields = ['id', 'url', 'name', 'slug']
class RegionSerializer(serializers.ModelSerializer):
parent = NestedRegionSerializer(required=False, allow_null=True)
@@ -46,10 +61,6 @@ class RegionSerializer(serializers.ModelSerializer):
fields = ['id', 'name', 'slug', 'parent']
#
# Sites
#
class SiteSerializer(TaggitSerializer, CustomFieldModelSerializer):
status = ChoiceField(choices=SITE_STATUS_CHOICES, required=False)
region = NestedRegionSerializer(required=False, allow_null=True)
@@ -72,16 +83,8 @@ class SiteSerializer(TaggitSerializer, CustomFieldModelSerializer):
]
class NestedSiteSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:site-detail')
class Meta:
model = Site
fields = ['id', 'url', 'name', 'slug']
#
# Rack groups
# Racks
#
class RackGroupSerializer(ValidatedModelSerializer):
@@ -92,18 +95,6 @@ class RackGroupSerializer(ValidatedModelSerializer):
fields = ['id', 'name', 'slug', 'site']
class NestedRackGroupSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rackgroup-detail')
class Meta:
model = RackGroup
fields = ['id', 'url', 'name', 'slug']
#
# Rack roles
#
class RackRoleSerializer(ValidatedModelSerializer):
class Meta:
@@ -111,32 +102,23 @@ class RackRoleSerializer(ValidatedModelSerializer):
fields = ['id', 'name', 'slug', 'color']
class NestedRackRoleSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rackrole-detail')
class Meta:
model = RackRole
fields = ['id', 'url', 'name', 'slug']
#
# Racks
#
class RackSerializer(TaggitSerializer, CustomFieldModelSerializer):
site = NestedSiteSerializer()
group = NestedRackGroupSerializer(required=False, allow_null=True, default=None)
tenant = NestedTenantSerializer(required=False, allow_null=True)
status = ChoiceField(choices=RACK_STATUS_CHOICES, required=False)
role = NestedRackRoleSerializer(required=False, allow_null=True)
type = ChoiceField(choices=RACK_TYPE_CHOICES, required=False, allow_null=True)
width = ChoiceField(choices=RACK_WIDTH_CHOICES, required=False)
outer_unit = ChoiceField(choices=RACK_DIMENSION_UNIT_CHOICES, required=False)
tags = TagListSerializerField(required=False)
class Meta:
model = Rack
fields = [
'id', 'name', 'facility_id', 'display_name', 'site', 'group', 'tenant', 'role', 'serial', 'type', 'width',
'u_height', 'desc_units', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
'id', 'name', 'facility_id', 'display_name', 'site', 'group', 'tenant', 'status', 'role', 'serial',
'asset_tag', 'type', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit',
'comments', 'tags', 'custom_fields', 'created', 'last_updated',
]
# Omit the UniqueTogetherValidator that would be automatically added to validate (group, facility_id). This
# prevents facility_id from being interpreted as a required field.
@@ -153,31 +135,11 @@ class RackSerializer(TaggitSerializer, CustomFieldModelSerializer):
validator(data)
# Enforce model validation
super(RackSerializer, self).validate(data)
super().validate(data)
return data
class NestedRackSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rack-detail')
class Meta:
model = Rack
fields = ['id', 'url', 'name', 'display_name']
#
# Rack units
#
class NestedDeviceSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:device-detail')
class Meta:
model = Device
fields = ['id', 'url', 'name', 'display_name']
class RackUnitSerializer(serializers.Serializer):
"""
A rack unit is an abstraction formed by the set (rack, position, face); it does not exist as a row in the database.
@@ -188,10 +150,6 @@ class RackUnitSerializer(serializers.Serializer):
device = NestedDeviceSerializer(read_only=True)
#
# Rack reservations
#
class RackReservationSerializer(ValidatedModelSerializer):
rack = NestedRackSerializer()
user = NestedUserSerializer()
@@ -203,7 +161,7 @@ class RackReservationSerializer(ValidatedModelSerializer):
#
# Manufacturers
# Device types
#
class ManufacturerSerializer(ValidatedModelSerializer):
@@ -213,21 +171,8 @@ class ManufacturerSerializer(ValidatedModelSerializer):
fields = ['id', 'name', 'slug']
class NestedManufacturerSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:manufacturer-detail')
class Meta:
model = Manufacturer
fields = ['id', 'url', 'name', 'slug']
#
# Device types
#
class DeviceTypeSerializer(TaggitSerializer, CustomFieldModelSerializer):
manufacturer = NestedManufacturerSerializer()
interface_ordering = ChoiceField(choices=IFACE_ORDERING_CHOICES, required=False)
subdevice_role = ChoiceField(choices=SUBDEVICE_ROLE_CHOICES, required=False, allow_null=True)
instance_count = serializers.IntegerField(source='instances.count', read_only=True)
tags = TagListSerializerField(required=False)
@@ -235,25 +180,11 @@ class DeviceTypeSerializer(TaggitSerializer, CustomFieldModelSerializer):
class Meta:
model = DeviceType
fields = [
'id', 'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'interface_ordering',
'is_console_server', 'is_pdu', 'is_network_device', 'subdevice_role', 'comments', 'tags', 'custom_fields',
'created', 'last_updated', 'instance_count',
'id', 'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role',
'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'instance_count',
]
class NestedDeviceTypeSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicetype-detail')
manufacturer = NestedManufacturerSerializer(read_only=True)
class Meta:
model = DeviceType
fields = ['id', 'url', 'manufacturer', 'model', 'slug']
#
# Console port templates
#
class ConsolePortTemplateSerializer(ValidatedModelSerializer):
device_type = NestedDeviceTypeSerializer()
@@ -262,10 +193,6 @@ class ConsolePortTemplateSerializer(ValidatedModelSerializer):
fields = ['id', 'device_type', 'name']
#
# Console server port templates
#
class ConsoleServerPortTemplateSerializer(ValidatedModelSerializer):
device_type = NestedDeviceTypeSerializer()
@@ -274,10 +201,6 @@ class ConsoleServerPortTemplateSerializer(ValidatedModelSerializer):
fields = ['id', 'device_type', 'name']
#
# Power port templates
#
class PowerPortTemplateSerializer(ValidatedModelSerializer):
device_type = NestedDeviceTypeSerializer()
@@ -286,10 +209,6 @@ class PowerPortTemplateSerializer(ValidatedModelSerializer):
fields = ['id', 'device_type', 'name']
#
# Power outlet templates
#
class PowerOutletTemplateSerializer(ValidatedModelSerializer):
device_type = NestedDeviceTypeSerializer()
@@ -298,10 +217,6 @@ class PowerOutletTemplateSerializer(ValidatedModelSerializer):
fields = ['id', 'device_type', 'name']
#
# Interface templates
#
class InterfaceTemplateSerializer(ValidatedModelSerializer):
device_type = NestedDeviceTypeSerializer()
form_factor = ChoiceField(choices=IFACE_FF_CHOICES, required=False)
@@ -311,9 +226,24 @@ class InterfaceTemplateSerializer(ValidatedModelSerializer):
fields = ['id', 'device_type', 'name', 'form_factor', 'mgmt_only']
#
# Device bay templates
#
class RearPortTemplateSerializer(ValidatedModelSerializer):
device_type = NestedDeviceTypeSerializer()
type = ChoiceField(choices=PORT_TYPE_CHOICES)
class Meta:
model = RearPortTemplate
fields = ['id', 'device_type', 'name', 'type', 'positions']
class FrontPortTemplateSerializer(ValidatedModelSerializer):
device_type = NestedDeviceTypeSerializer()
type = ChoiceField(choices=PORT_TYPE_CHOICES)
rear_port = NestedRearPortTemplateSerializer()
class Meta:
model = FrontPortTemplate
fields = ['id', 'device_type', 'name', 'type', 'rear_port', 'rear_port_position']
class DeviceBayTemplateSerializer(ValidatedModelSerializer):
device_type = NestedDeviceTypeSerializer()
@@ -324,7 +254,7 @@ class DeviceBayTemplateSerializer(ValidatedModelSerializer):
#
# Device roles
# Devices
#
class DeviceRoleSerializer(ValidatedModelSerializer):
@@ -334,64 +264,12 @@ class DeviceRoleSerializer(ValidatedModelSerializer):
fields = ['id', 'name', 'slug', 'color', 'vm_role']
class NestedDeviceRoleSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicerole-detail')
class Meta:
model = DeviceRole
fields = ['id', 'url', 'name', 'slug']
#
# Platforms
#
class PlatformSerializer(ValidatedModelSerializer):
manufacturer = NestedManufacturerSerializer(required=False, allow_null=True)
class Meta:
model = Platform
fields = ['id', 'name', 'slug', 'manufacturer', 'napalm_driver', 'napalm_args', 'rpc_client']
class NestedPlatformSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:platform-detail')
class Meta:
model = Platform
fields = ['id', 'url', 'name', 'slug']
#
# Devices
#
# Cannot import ipam.api.NestedIPAddressSerializer due to circular dependency
class DeviceIPAddressSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='ipam-api:ipaddress-detail')
class Meta:
model = IPAddress
fields = ['id', 'url', 'family', 'address']
# Cannot import virtualization.api.NestedClusterSerializer due to circular dependency
class NestedClusterSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='virtualization-api:cluster-detail')
class Meta:
model = Cluster
fields = ['id', 'url', 'name']
# Cannot import NestedVirtualChassisSerializer due to circular dependency
class DeviceVirtualChassisSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:virtualchassis-detail')
master = NestedDeviceSerializer()
class Meta:
model = VirtualChassis
fields = ['id', 'url', 'master']
fields = ['id', 'name', 'slug', 'manufacturer', 'napalm_driver', 'napalm_args']
class DeviceSerializer(TaggitSerializer, CustomFieldModelSerializer):
@@ -403,12 +281,12 @@ class DeviceSerializer(TaggitSerializer, CustomFieldModelSerializer):
rack = NestedRackSerializer(required=False, allow_null=True)
face = ChoiceField(choices=RACK_FACE_CHOICES, required=False, allow_null=True)
status = ChoiceField(choices=DEVICE_STATUS_CHOICES, required=False)
primary_ip = DeviceIPAddressSerializer(read_only=True)
primary_ip4 = DeviceIPAddressSerializer(required=False, allow_null=True)
primary_ip6 = DeviceIPAddressSerializer(required=False, allow_null=True)
primary_ip = NestedIPAddressSerializer(read_only=True)
primary_ip4 = NestedIPAddressSerializer(required=False, allow_null=True)
primary_ip6 = NestedIPAddressSerializer(required=False, allow_null=True)
parent_device = serializers.SerializerMethodField()
cluster = NestedClusterSerializer(required=False, allow_null=True)
virtual_chassis = DeviceVirtualChassisSerializer(required=False, allow_null=True)
virtual_chassis = NestedVirtualChassisSerializer(required=False, allow_null=True)
tags = TagListSerializerField(required=False)
class Meta:
@@ -416,8 +294,8 @@ class DeviceSerializer(TaggitSerializer, CustomFieldModelSerializer):
fields = [
'id', 'name', 'display_name', 'device_type', 'device_role', 'tenant', 'platform', 'serial', 'asset_tag',
'site', 'rack', 'position', 'face', 'parent_device', 'status', 'primary_ip', 'primary_ip4', 'primary_ip6',
'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'comments', 'tags', 'custom_fields', 'created',
'last_updated', 'local_context_data',
'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'comments', 'local_context_data', 'tags',
'custom_fields', 'created', 'last_updated',
]
validators = []
@@ -430,7 +308,7 @@ class DeviceSerializer(TaggitSerializer, CustomFieldModelSerializer):
validator(data)
# Enforce model validation
super(DeviceSerializer, self).validate(data)
super().validate(data)
return data
@@ -452,203 +330,90 @@ class DeviceWithConfigContextSerializer(DeviceSerializer):
fields = [
'id', 'name', 'display_name', 'device_type', 'device_role', 'tenant', 'platform', 'serial', 'asset_tag',
'site', 'rack', 'position', 'face', 'parent_device', 'status', 'primary_ip', 'primary_ip4', 'primary_ip6',
'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'comments', 'tags', 'custom_fields',
'config_context', 'created', 'last_updated', 'local_context_data',
'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'comments', 'local_context_data', 'tags',
'custom_fields', 'config_context', 'created', 'last_updated',
]
def get_config_context(self, obj):
return obj.get_config_context()
#
# Console server ports
#
class ConsoleServerPortSerializer(TaggitSerializer, ValidatedModelSerializer):
class ConsoleServerPortSerializer(TaggitSerializer, ConnectedEndpointSerializer):
device = NestedDeviceSerializer()
cable = NestedCableSerializer(read_only=True)
tags = TagListSerializerField(required=False)
class Meta:
model = ConsoleServerPort
fields = ['id', 'device', 'name', 'connected_console', 'tags']
read_only_fields = ['connected_console']
class NestedConsoleServerPortSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:consoleserverport-detail')
device = NestedDeviceSerializer(read_only=True)
is_connected = serializers.SerializerMethodField(read_only=True)
class Meta:
model = ConsoleServerPort
fields = ['id', 'url', 'device', 'name', 'is_connected']
def get_is_connected(self, obj):
return hasattr(obj, 'connected_console') and obj.connected_console is not None
#
# Console ports
#
class ConsolePortSerializer(TaggitSerializer, ValidatedModelSerializer):
device = NestedDeviceSerializer()
cs_port = NestedConsoleServerPortSerializer(required=False, allow_null=True)
tags = TagListSerializerField(required=False)
class Meta:
model = ConsolePort
fields = ['id', 'device', 'name', 'cs_port', 'connection_status', 'tags']
class NestedConsolePortSerializer(TaggitSerializer, ValidatedModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:consoleport-detail')
device = NestedDeviceSerializer(read_only=True)
is_connected = serializers.SerializerMethodField(read_only=True)
class Meta:
model = ConsolePort
fields = ['id', 'url', 'device', 'name', 'is_connected']
def get_is_connected(self, obj):
return obj.cs_port is not None
#
# Power outlets
#
class PowerOutletSerializer(TaggitSerializer, ValidatedModelSerializer):
device = NestedDeviceSerializer()
tags = TagListSerializerField(required=False)
class Meta:
model = PowerOutlet
fields = ['id', 'device', 'name', 'connected_port', 'tags']
read_only_fields = ['connected_port']
class NestedPowerOutletSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:poweroutlet-detail')
device = NestedDeviceSerializer(read_only=True)
is_connected = serializers.SerializerMethodField(read_only=True)
class Meta:
model = PowerOutlet
fields = ['id', 'url', 'device', 'name', 'is_connected']
def get_is_connected(self, obj):
return hasattr(obj, 'connected_port') and obj.connected_port is not None
#
# Power ports
#
class PowerPortSerializer(TaggitSerializer, ValidatedModelSerializer):
device = NestedDeviceSerializer()
power_outlet = NestedPowerOutletSerializer(required=False, allow_null=True)
tags = TagListSerializerField(required=False)
class Meta:
model = PowerPort
fields = ['id', 'device', 'name', 'power_outlet', 'connection_status', 'tags']
class NestedPowerPortSerializer(TaggitSerializer, ValidatedModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:powerport-detail')
device = NestedDeviceSerializer(read_only=True)
is_connected = serializers.SerializerMethodField(read_only=True)
class Meta:
model = PowerPort
fields = ['id', 'url', 'device', 'name', 'is_connected']
def get_is_connected(self, obj):
return obj.power_outlet is not None
#
# Interfaces
#
class IsConnectedMixin(object):
"""
Provide a method for setting is_connected on Interface serializers.
"""
def get_is_connected(self, obj):
"""
Return True if the interface has a connected interface or circuit.
"""
if obj.connection:
return True
if hasattr(obj, 'circuit_termination') and obj.circuit_termination is not None:
return True
return False
class NestedInterfaceSerializer(IsConnectedMixin, WritableNestedSerializer):
device = NestedDeviceSerializer(read_only=True)
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:interface-detail')
is_connected = serializers.SerializerMethodField(read_only=True)
class Meta:
model = Interface
fields = ['id', 'url', 'device', 'name', 'is_connected']
class InterfaceNestedCircuitSerializer(serializers.ModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='circuits-api:circuit-detail')
class Meta:
model = Circuit
fields = ['id', 'url', 'cid']
class InterfaceCircuitTerminationSerializer(WritableNestedSerializer):
circuit = InterfaceNestedCircuitSerializer(read_only=True)
class Meta:
model = CircuitTermination
fields = [
'id', 'circuit', 'term_side', 'port_speed', 'upstream_speed', 'xconnect_id', 'pp_info',
'id', 'device', 'name', 'connected_endpoint_type', 'connected_endpoint', 'connection_status', 'cable',
'tags',
]
# Cannot import ipam.api.NestedVLANSerializer due to circular dependency
class InterfaceVLANSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='ipam-api:vlan-detail')
class ConsolePortSerializer(TaggitSerializer, ConnectedEndpointSerializer):
device = NestedDeviceSerializer()
cable = NestedCableSerializer(read_only=True)
tags = TagListSerializerField(required=False)
class Meta:
model = VLAN
fields = ['id', 'url', 'vid', 'name', 'display_name']
model = ConsolePort
fields = [
'id', 'device', 'name', 'connected_endpoint_type', 'connected_endpoint', 'connection_status', 'cable',
'tags',
]
class InterfaceSerializer(TaggitSerializer, IsConnectedMixin, ValidatedModelSerializer):
class PowerOutletSerializer(TaggitSerializer, ConnectedEndpointSerializer):
device = NestedDeviceSerializer()
cable = NestedCableSerializer(read_only=True)
tags = TagListSerializerField(required=False)
class Meta:
model = PowerOutlet
fields = [
'id', 'device', 'name', 'connected_endpoint_type', 'connected_endpoint', 'connection_status', 'cable',
'tags',
]
class PowerPortSerializer(TaggitSerializer, ConnectedEndpointSerializer):
device = NestedDeviceSerializer()
cable = NestedCableSerializer(read_only=True)
tags = TagListSerializerField(required=False)
class Meta:
model = PowerPort
fields = [
'id', 'device', 'name', 'connected_endpoint_type', 'connected_endpoint', 'connection_status', 'cable',
'tags',
]
class InterfaceSerializer(TaggitSerializer, ConnectedEndpointSerializer):
device = NestedDeviceSerializer()
form_factor = ChoiceField(choices=IFACE_FF_CHOICES, required=False)
lag = NestedInterfaceSerializer(required=False, allow_null=True)
is_connected = serializers.SerializerMethodField(read_only=True)
interface_connection = serializers.SerializerMethodField(read_only=True)
circuit_termination = InterfaceCircuitTerminationSerializer(read_only=True)
mode = ChoiceField(choices=IFACE_MODE_CHOICES, required=False, allow_null=True)
untagged_vlan = InterfaceVLANSerializer(required=False, allow_null=True)
untagged_vlan = NestedVLANSerializer(required=False, allow_null=True)
tagged_vlans = SerializedPKRelatedField(
queryset=VLAN.objects.all(),
serializer=InterfaceVLANSerializer,
serializer=NestedVLANSerializer,
required=False,
many=True
)
cable = NestedCableSerializer(read_only=True)
tags = TagListSerializerField(required=False)
class Meta:
model = Interface
fields = [
'id', 'device', 'name', 'form_factor', 'enabled', 'lag', 'mtu', 'mac_address', 'mgmt_only', 'description',
'is_connected', 'interface_connection', 'circuit_termination', 'mode', 'untagged_vlan', 'tagged_vlans',
'tags',
'connected_endpoint_type', 'connected_endpoint', 'connection_status', 'cable', 'mode', 'untagged_vlan',
'tagged_vlans', 'tags', 'count_ipaddresses',
]
# TODO: This validation should be handled by Interface.clean()
def validate(self, data):
# All associated VLANs be global or assigned to the parent device's site.
@@ -666,21 +431,42 @@ class InterfaceSerializer(TaggitSerializer, IsConnectedMixin, ValidatedModelSeri
"be global.".format(vlan)
})
return super(InterfaceSerializer, self).validate(data)
def get_interface_connection(self, obj):
if obj.connection:
context = {
'request': self.context['request'],
'interface': obj.connected_interface,
}
return ContextualInterfaceConnectionSerializer(obj.connection, context=context).data
return None
return super().validate(data)
#
# Device bays
#
class RearPortSerializer(ValidatedModelSerializer):
device = NestedDeviceSerializer()
type = ChoiceField(choices=PORT_TYPE_CHOICES)
cable = NestedCableSerializer(read_only=True)
tags = TagListSerializerField(required=False)
class Meta:
model = RearPort
fields = ['id', 'device', 'name', 'type', 'positions', 'description', 'cable', 'tags']
class FrontPortRearPortSerializer(WritableNestedSerializer):
"""
NestedRearPortSerializer but with parent device omitted (since front and rear ports must belong to same device)
"""
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rearport-detail')
class Meta:
model = RearPort
fields = ['id', 'url', 'name']
class FrontPortSerializer(ValidatedModelSerializer):
device = NestedDeviceSerializer()
type = ChoiceField(choices=PORT_TYPE_CHOICES)
rear_port = FrontPortRearPortSerializer()
cable = NestedCableSerializer(read_only=True)
tags = TagListSerializerField(required=False)
class Meta:
model = FrontPort
fields = ['id', 'device', 'name', 'type', 'rear_port', 'rear_port_position', 'description', 'cable', 'tags']
class DeviceBaySerializer(TaggitSerializer, ValidatedModelSerializer):
device = NestedDeviceSerializer()
@@ -692,15 +478,6 @@ class DeviceBaySerializer(TaggitSerializer, ValidatedModelSerializer):
fields = ['id', 'device', 'name', 'installed_device', 'tags']
class NestedDeviceBaySerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicebay-detail')
device = NestedDeviceSerializer(read_only=True)
class Meta:
model = DeviceBay
fields = ['id', 'url', 'device', 'name']
#
# Inventory items
#
@@ -720,41 +497,76 @@ class InventoryItemSerializer(TaggitSerializer, ValidatedModelSerializer):
]
#
# Cables
#
class CableSerializer(ValidatedModelSerializer):
termination_a_type = ContentTypeField()
termination_b_type = ContentTypeField()
termination_a = serializers.SerializerMethodField(read_only=True)
termination_b = serializers.SerializerMethodField(read_only=True)
status = ChoiceField(choices=CONNECTION_STATUS_CHOICES, required=False)
length_unit = ChoiceField(choices=CABLE_LENGTH_UNIT_CHOICES, required=False)
class Meta:
model = Cable
fields = [
'id', 'termination_a_type', 'termination_a_id', 'termination_a', 'termination_b_type', 'termination_b_id',
'termination_b', 'type', 'status', 'label', 'color', 'length', 'length_unit',
]
def _get_termination(self, obj, side):
"""
Serialize a nested representation of a termination.
"""
if side.lower() not in ['a', 'b']:
raise ValueError("Termination side must be either A or B.")
termination = getattr(obj, 'termination_{}'.format(side.lower()))
if termination is None:
return None
serializer = get_serializer_for_model(termination, prefix='Nested')
context = {'request': self.context['request']}
data = serializer(termination, context=context).data
return data
def get_termination_a(self, obj):
return self._get_termination(obj, 'a')
def get_termination_b(self, obj):
return self._get_termination(obj, 'b')
class TracedCableSerializer(serializers.ModelSerializer):
"""
Used only while tracing a cable path.
"""
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:cable-detail')
class Meta:
model = Cable
fields = [
'id', 'url', 'type', 'status', 'label', 'color', 'length', 'length_unit',
]
#
# Interface connections
#
class InterfaceConnectionSerializer(ValidatedModelSerializer):
interface_a = NestedInterfaceSerializer()
interface_b = NestedInterfaceSerializer()
interface_a = serializers.SerializerMethodField()
interface_b = NestedInterfaceSerializer(source='connected_endpoint')
connection_status = ChoiceField(choices=CONNECTION_STATUS_CHOICES, required=False)
class Meta:
model = InterfaceConnection
fields = ['id', 'interface_a', 'interface_b', 'connection_status']
model = Interface
fields = ['interface_a', 'interface_b', 'connection_status']
class NestedInterfaceConnectionSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:interfaceconnection-detail')
class Meta:
model = InterfaceConnection
fields = ['id', 'url', 'connection_status']
class ContextualInterfaceConnectionSerializer(serializers.ModelSerializer):
"""
A read-only representation of an InterfaceConnection from the perspective of either of its two connected Interfaces.
"""
interface = serializers.SerializerMethodField(read_only=True)
connection_status = ChoiceField(choices=CONNECTION_STATUS_CHOICES, read_only=True)
class Meta:
model = InterfaceConnection
fields = ['id', 'interface', 'connection_status']
def get_interface(self, obj):
return NestedInterfaceSerializer(self.context['interface'], context=self.context).data
def get_interface_a(self, obj):
context = {'request': self.context['request']}
return NestedInterfaceSerializer(instance=obj, context=context).data
#
@@ -768,11 +580,3 @@ class VirtualChassisSerializer(TaggitSerializer, ValidatedModelSerializer):
class Meta:
model = VirtualChassis
fields = ['id', 'master', 'domain', 'tags']
class NestedVirtualChassisSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:virtualchassis-detail')
class Meta:
model = VirtualChassis
fields = ['id', 'url']

View File

@@ -1,5 +1,3 @@
from __future__ import unicode_literals
from rest_framework import routers
from . import views
@@ -17,7 +15,7 @@ router = routers.DefaultRouter()
router.APIRootView = DCIMRootView
# Field choices
router.register(r'_choices', views.DCIMFieldChoicesViewSet, base_name='field-choice')
router.register(r'_choices', views.DCIMFieldChoicesViewSet, basename='field-choice')
# Sites
router.register(r'regions', views.RegionViewSet)
@@ -39,6 +37,8 @@ router.register(r'console-server-port-templates', views.ConsoleServerPortTemplat
router.register(r'power-port-templates', views.PowerPortTemplateViewSet)
router.register(r'power-outlet-templates', views.PowerOutletTemplateViewSet)
router.register(r'interface-templates', views.InterfaceTemplateViewSet)
router.register(r'front-port-templates', views.FrontPortTemplateViewSet)
router.register(r'rear-port-templates', views.RearPortTemplateViewSet)
router.register(r'device-bay-templates', views.DeviceBayTemplateViewSet)
# Devices
@@ -52,19 +52,24 @@ router.register(r'console-server-ports', views.ConsoleServerPortViewSet)
router.register(r'power-ports', views.PowerPortViewSet)
router.register(r'power-outlets', views.PowerOutletViewSet)
router.register(r'interfaces', views.InterfaceViewSet)
router.register(r'front-ports', views.FrontPortViewSet)
router.register(r'rear-ports', views.RearPortViewSet)
router.register(r'device-bays', views.DeviceBayViewSet)
router.register(r'inventory-items', views.InventoryItemViewSet)
# Connections
router.register(r'console-connections', views.ConsoleConnectionViewSet, base_name='consoleconnections')
router.register(r'power-connections', views.PowerConnectionViewSet, base_name='powerconnections')
router.register(r'interface-connections', views.InterfaceConnectionViewSet)
router.register(r'console-connections', views.ConsoleConnectionViewSet, basename='consoleconnections')
router.register(r'power-connections', views.PowerConnectionViewSet, basename='powerconnections')
router.register(r'interface-connections', views.InterfaceConnectionViewSet, basename='interfaceconnections')
# Cables
router.register(r'cables', views.CableViewSet)
# Virtual chassis
router.register(r'virtual-chassis', views.VirtualChassisViewSet)
# Miscellaneous
router.register(r'connected-device', views.ConnectedDeviceViewSet, base_name='connected-device')
router.register(r'connected-device', views.ConnectedDeviceViewSet, basename='connected-device')
app_name = 'dcim-api'
urlpatterns = router.urls

View File

@@ -1,8 +1,7 @@
from __future__ import unicode_literals
from collections import OrderedDict
from django.conf import settings
from django.db.models import F, Q
from django.http import HttpResponseForbidden
from django.shortcuts import get_object_or_404
from drf_yasg import openapi
@@ -15,15 +14,17 @@ from rest_framework.viewsets import GenericViewSet, ViewSet
from dcim import filters
from dcim.models import (
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
DeviceBayTemplate, DeviceRole, DeviceType, Interface, InterfaceConnection, InterfaceTemplate, Manufacturer,
InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup,
RackReservation, RackRole, Region, Site, VirtualChassis,
Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate,
Manufacturer, InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack,
RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site, VirtualChassis,
)
from extras.api.serializers import RenderedGraphSerializer
from extras.api.views import CustomFieldModelViewSet
from extras.models import Graph, GRAPH_TYPE_INTERFACE, GRAPH_TYPE_SITE
from utilities.api import IsAuthenticatedOrLoginNotRequired, FieldChoicesViewSet, ModelViewSet, ServiceUnavailable
from utilities.api import (
get_serializer_for_model, IsAuthenticatedOrLoginNotRequired, FieldChoicesViewSet, ModelViewSet, ServiceUnavailable,
)
from . import serializers
from .exceptions import MissingFilterException
@@ -34,17 +35,51 @@ from .exceptions import MissingFilterException
class DCIMFieldChoicesViewSet(FieldChoicesViewSet):
fields = (
(Cable, ['length_unit']),
(Device, ['face', 'status']),
(ConsolePort, ['connection_status']),
(Interface, ['form_factor', 'mode']),
(InterfaceConnection, ['connection_status']),
(Interface, ['connection_status', 'form_factor', 'mode']),
(InterfaceTemplate, ['form_factor']),
(PowerPort, ['connection_status']),
(Rack, ['type', 'width']),
(Rack, ['outer_unit', 'status', 'type', 'width']),
(Site, ['status']),
)
# Mixins
class CableTraceMixin(object):
@action(detail=True, url_path='trace')
def trace(self, request, pk):
"""
Trace a complete cable path and return each segment as a three-tuple of (termination, cable, termination).
"""
obj = get_object_or_404(self.queryset.model, pk=pk)
# Initialize the path array
path = []
for near_end, cable, far_end in obj.trace():
# Serialize each object
serializer_a = get_serializer_for_model(near_end, prefix='Nested')
x = serializer_a(near_end, context={'request': request}).data
if cable is not None:
y = serializers.TracedCableSerializer(cable, context={'request': request}).data
else:
y = None
if far_end is not None:
serializer_b = get_serializer_for_model(far_end, prefix='Nested')
z = serializer_b(far_end, context={'request': request}).data
else:
z = None
path.append((x, y, z))
return Response(path)
#
# Regions
#
@@ -52,7 +87,7 @@ class DCIMFieldChoicesViewSet(FieldChoicesViewSet):
class RegionViewSet(ModelViewSet):
queryset = Region.objects.all()
serializer_class = serializers.RegionSerializer
filter_class = filters.RegionFilter
filterset_class = filters.RegionFilter
#
@@ -62,7 +97,7 @@ class RegionViewSet(ModelViewSet):
class SiteViewSet(CustomFieldModelViewSet):
queryset = Site.objects.select_related('region', 'tenant').prefetch_related('tags')
serializer_class = serializers.SiteSerializer
filter_class = filters.SiteFilter
filterset_class = filters.SiteFilter
@action(detail=True)
def graphs(self, request, pk=None):
@@ -82,7 +117,7 @@ class SiteViewSet(CustomFieldModelViewSet):
class RackGroupViewSet(ModelViewSet):
queryset = RackGroup.objects.select_related('site')
serializer_class = serializers.RackGroupSerializer
filter_class = filters.RackGroupFilter
filterset_class = filters.RackGroupFilter
#
@@ -92,7 +127,7 @@ class RackGroupViewSet(ModelViewSet):
class RackRoleViewSet(ModelViewSet):
queryset = RackRole.objects.all()
serializer_class = serializers.RackRoleSerializer
filter_class = filters.RackRoleFilter
filterset_class = filters.RackRoleFilter
#
@@ -102,7 +137,7 @@ class RackRoleViewSet(ModelViewSet):
class RackViewSet(CustomFieldModelViewSet):
queryset = Rack.objects.select_related('site', 'group__site', 'tenant').prefetch_related('tags')
serializer_class = serializers.RackSerializer
filter_class = filters.RackFilter
filterset_class = filters.RackFilter
@action(detail=True)
def units(self, request, pk=None):
@@ -132,7 +167,7 @@ class RackViewSet(CustomFieldModelViewSet):
class RackReservationViewSet(ModelViewSet):
queryset = RackReservation.objects.select_related('rack', 'user', 'tenant')
serializer_class = serializers.RackReservationSerializer
filter_class = filters.RackReservationFilter
filterset_class = filters.RackReservationFilter
# Assign user from request
def perform_create(self, serializer):
@@ -146,7 +181,7 @@ class RackReservationViewSet(ModelViewSet):
class ManufacturerViewSet(ModelViewSet):
queryset = Manufacturer.objects.all()
serializer_class = serializers.ManufacturerSerializer
filter_class = filters.ManufacturerFilter
filterset_class = filters.ManufacturerFilter
#
@@ -156,7 +191,7 @@ class ManufacturerViewSet(ModelViewSet):
class DeviceTypeViewSet(CustomFieldModelViewSet):
queryset = DeviceType.objects.select_related('manufacturer').prefetch_related('tags')
serializer_class = serializers.DeviceTypeSerializer
filter_class = filters.DeviceTypeFilter
filterset_class = filters.DeviceTypeFilter
#
@@ -166,37 +201,49 @@ class DeviceTypeViewSet(CustomFieldModelViewSet):
class ConsolePortTemplateViewSet(ModelViewSet):
queryset = ConsolePortTemplate.objects.select_related('device_type__manufacturer')
serializer_class = serializers.ConsolePortTemplateSerializer
filter_class = filters.ConsolePortTemplateFilter
filterset_class = filters.ConsolePortTemplateFilter
class ConsoleServerPortTemplateViewSet(ModelViewSet):
queryset = ConsoleServerPortTemplate.objects.select_related('device_type__manufacturer')
serializer_class = serializers.ConsoleServerPortTemplateSerializer
filter_class = filters.ConsoleServerPortTemplateFilter
filterset_class = filters.ConsoleServerPortTemplateFilter
class PowerPortTemplateViewSet(ModelViewSet):
queryset = PowerPortTemplate.objects.select_related('device_type__manufacturer')
serializer_class = serializers.PowerPortTemplateSerializer
filter_class = filters.PowerPortTemplateFilter
filterset_class = filters.PowerPortTemplateFilter
class PowerOutletTemplateViewSet(ModelViewSet):
queryset = PowerOutletTemplate.objects.select_related('device_type__manufacturer')
serializer_class = serializers.PowerOutletTemplateSerializer
filter_class = filters.PowerOutletTemplateFilter
filterset_class = filters.PowerOutletTemplateFilter
class InterfaceTemplateViewSet(ModelViewSet):
queryset = InterfaceTemplate.objects.select_related('device_type__manufacturer')
serializer_class = serializers.InterfaceTemplateSerializer
filter_class = filters.InterfaceTemplateFilter
filterset_class = filters.InterfaceTemplateFilter
class FrontPortTemplateViewSet(ModelViewSet):
queryset = FrontPortTemplate.objects.select_related('device_type__manufacturer')
serializer_class = serializers.FrontPortTemplateSerializer
filterset_class = filters.FrontPortTemplateFilter
class RearPortTemplateViewSet(ModelViewSet):
queryset = RearPortTemplate.objects.select_related('device_type__manufacturer')
serializer_class = serializers.RearPortTemplateSerializer
filterset_class = filters.RearPortTemplateFilter
class DeviceBayTemplateViewSet(ModelViewSet):
queryset = DeviceBayTemplate.objects.select_related('device_type__manufacturer')
serializer_class = serializers.DeviceBayTemplateSerializer
filter_class = filters.DeviceBayTemplateFilter
filterset_class = filters.DeviceBayTemplateFilter
#
@@ -206,7 +253,7 @@ class DeviceBayTemplateViewSet(ModelViewSet):
class DeviceRoleViewSet(ModelViewSet):
queryset = DeviceRole.objects.all()
serializer_class = serializers.DeviceRoleSerializer
filter_class = filters.DeviceRoleFilter
filterset_class = filters.DeviceRoleFilter
#
@@ -216,7 +263,7 @@ class DeviceRoleViewSet(ModelViewSet):
class PlatformViewSet(ModelViewSet):
queryset = Platform.objects.all()
serializer_class = serializers.PlatformSerializer
filter_class = filters.PlatformFilter
filterset_class = filters.PlatformFilter
#
@@ -230,7 +277,7 @@ class DeviceViewSet(CustomFieldModelViewSet):
).prefetch_related(
'primary_ip4__nat_outside', 'primary_ip6__nat_outside', 'tags',
)
filter_class = filters.DeviceFilter
filterset_class = filters.DeviceFilter
def get_serializer_class(self):
"""
@@ -321,34 +368,54 @@ class DeviceViewSet(CustomFieldModelViewSet):
# Device components
#
class ConsolePortViewSet(ModelViewSet):
queryset = ConsolePort.objects.select_related('device', 'cs_port__device').prefetch_related('tags')
class ConsolePortViewSet(CableTraceMixin, ModelViewSet):
queryset = ConsolePort.objects.select_related(
'device', 'connected_endpoint__device', 'cable'
).prefetch_related(
'tags'
)
serializer_class = serializers.ConsolePortSerializer
filter_class = filters.ConsolePortFilter
filterset_class = filters.ConsolePortFilter
class ConsoleServerPortViewSet(ModelViewSet):
queryset = ConsoleServerPort.objects.select_related('device', 'connected_console__device').prefetch_related('tags')
class ConsoleServerPortViewSet(CableTraceMixin, ModelViewSet):
queryset = ConsoleServerPort.objects.select_related(
'device', 'connected_endpoint__device', 'cable'
).prefetch_related(
'tags'
)
serializer_class = serializers.ConsoleServerPortSerializer
filter_class = filters.ConsoleServerPortFilter
filterset_class = filters.ConsoleServerPortFilter
class PowerPortViewSet(ModelViewSet):
queryset = PowerPort.objects.select_related('device', 'power_outlet__device').prefetch_related('tags')
class PowerPortViewSet(CableTraceMixin, ModelViewSet):
queryset = PowerPort.objects.select_related(
'device', 'connected_endpoint__device', 'cable'
).prefetch_related(
'tags'
)
serializer_class = serializers.PowerPortSerializer
filter_class = filters.PowerPortFilter
filterset_class = filters.PowerPortFilter
class PowerOutletViewSet(ModelViewSet):
queryset = PowerOutlet.objects.select_related('device', 'connected_port__device').prefetch_related('tags')
class PowerOutletViewSet(CableTraceMixin, ModelViewSet):
queryset = PowerOutlet.objects.select_related(
'device', 'connected_endpoint__device', 'cable'
).prefetch_related(
'tags'
)
serializer_class = serializers.PowerOutletSerializer
filter_class = filters.PowerOutletFilter
filterset_class = filters.PowerOutletFilter
class InterfaceViewSet(ModelViewSet):
queryset = Interface.objects.select_related('device').prefetch_related('tags')
class InterfaceViewSet(CableTraceMixin, ModelViewSet):
queryset = Interface.objects.select_related(
'device', '_connected_interface', '_connected_circuittermination', 'cable'
).prefetch_related(
'ip_addresses', 'tags'
)
serializer_class = serializers.InterfaceSerializer
filter_class = filters.InterfaceFilter
filterset_class = filters.InterfaceFilter
@action(detail=True)
def graphs(self, request, pk=None):
@@ -361,16 +428,36 @@ class InterfaceViewSet(ModelViewSet):
return Response(serializer.data)
class FrontPortViewSet(ModelViewSet):
queryset = FrontPort.objects.select_related(
'device__device_type__manufacturer', 'rear_port', 'cable'
).prefetch_related(
'tags'
)
serializer_class = serializers.FrontPortSerializer
filterset_class = filters.FrontPortFilter
class RearPortViewSet(ModelViewSet):
queryset = RearPort.objects.select_related(
'device__device_type__manufacturer', 'cable'
).prefetch_related(
'tags'
)
serializer_class = serializers.RearPortSerializer
filterset_class = filters.RearPortFilter
class DeviceBayViewSet(ModelViewSet):
queryset = DeviceBay.objects.select_related('installed_device').prefetch_related('tags')
serializer_class = serializers.DeviceBaySerializer
filter_class = filters.DeviceBayFilter
filterset_class = filters.DeviceBayFilter
class InventoryItemViewSet(ModelViewSet):
queryset = InventoryItem.objects.select_related('device', 'manufacturer').prefetch_related('tags')
serializer_class = serializers.InventoryItemSerializer
filter_class = filters.InventoryItemFilter
filterset_class = filters.InventoryItemFilter
#
@@ -378,21 +465,47 @@ class InventoryItemViewSet(ModelViewSet):
#
class ConsoleConnectionViewSet(ListModelMixin, GenericViewSet):
queryset = ConsolePort.objects.select_related('device', 'cs_port__device').filter(cs_port__isnull=False)
queryset = ConsolePort.objects.select_related(
'device', 'connected_endpoint__device'
).filter(
connected_endpoint__isnull=False
)
serializer_class = serializers.ConsolePortSerializer
filter_class = filters.ConsoleConnectionFilter
filterset_class = filters.ConsoleConnectionFilter
class PowerConnectionViewSet(ListModelMixin, GenericViewSet):
queryset = PowerPort.objects.select_related('device', 'power_outlet__device').filter(power_outlet__isnull=False)
queryset = PowerPort.objects.select_related(
'device', 'connected_endpoint__device'
).filter(
connected_endpoint__isnull=False
)
serializer_class = serializers.PowerPortSerializer
filter_class = filters.PowerConnectionFilter
filterset_class = filters.PowerConnectionFilter
class InterfaceConnectionViewSet(ModelViewSet):
queryset = InterfaceConnection.objects.select_related('interface_a__device', 'interface_b__device')
queryset = Interface.objects.select_related(
'device', '_connected_interface', '_connected_circuittermination'
).filter(
# Avoid duplicate connections by only selecting the lower PK in a connected pair
Q(_connected_interface__isnull=False, pk__lt=F('_connected_interface')) |
Q(_connected_circuittermination__isnull=False)
)
serializer_class = serializers.InterfaceConnectionSerializer
filter_class = filters.InterfaceConnectionFilter
filterset_class = filters.InterfaceConnectionFilter
#
# Cables
#
class CableViewSet(ModelViewSet):
queryset = Cable.objects.prefetch_related(
'termination_a', 'termination_b'
)
serializer_class = serializers.CableSerializer
filterset_class = filters.CableFilter
#
@@ -418,32 +531,39 @@ class ConnectedDeviceViewSet(ViewSet):
* `peer_interface`: The name of the peer interface
"""
permission_classes = [IsAuthenticatedOrLoginNotRequired]
_device_param = Parameter('peer_device', 'query',
description='The name of the peer device', required=True, type=openapi.TYPE_STRING)
_interface_param = Parameter('peer_interface', 'query',
description='The name of the peer interface', required=True, type=openapi.TYPE_STRING)
_device_param = Parameter(
name='peer_device',
in_='query',
description='The name of the peer device',
required=True,
type=openapi.TYPE_STRING
)
_interface_param = Parameter(
name='peer_interface',
in_='query',
description='The name of the peer interface',
required=True,
type=openapi.TYPE_STRING
)
def get_view_name(self):
return "Connected Device Locator"
@swagger_auto_schema(
manual_parameters=[_device_param, _interface_param], responses={'200': serializers.DeviceSerializer})
manual_parameters=[_device_param, _interface_param],
responses={'200': serializers.DeviceSerializer}
)
def list(self, request):
peer_device_name = request.query_params.get(self._device_param.name)
if not peer_device_name:
# TODO: remove this after 2.4 as the switch to using underscores is a breaking change
peer_device_name = request.query_params.get('peer-device')
peer_interface_name = request.query_params.get(self._interface_param.name)
if not peer_interface_name:
# TODO: remove this after 2.4 as the switch to using underscores is a breaking change
peer_interface_name = request.query_params.get('peer-interface')
if not peer_device_name or not peer_interface_name:
raise MissingFilterException(detail='Request must include "peer_device" and "peer_interface" filters.')
# Determine local interface from peer interface's connection
peer_interface = get_object_or_404(Interface, device__name=peer_device_name, name=peer_interface_name)
local_interface = peer_interface.connected_interface
local_interface = peer_interface._connected_interface
if local_interface is None:
return Response()

View File

@@ -1,5 +1,3 @@
from __future__ import unicode_literals
from django.apps import AppConfig

View File

@@ -1,5 +1,3 @@
from __future__ import unicode_literals
# Rack types
RACK_TYPE_2POST = 100
@@ -31,6 +29,20 @@ RACK_FACE_CHOICES = [
[RACK_FACE_REAR, 'Rear'],
]
# Rack statuses
RACK_STATUS_RESERVED = 0
RACK_STATUS_AVAILABLE = 1
RACK_STATUS_PLANNED = 2
RACK_STATUS_ACTIVE = 3
RACK_STATUS_DEPRECATED = 4
RACK_STATUS_CHOICES = [
[RACK_STATUS_ACTIVE, 'Active'],
[RACK_STATUS_PLANNED, 'Planned'],
[RACK_STATUS_RESERVED, 'Reserved'],
[RACK_STATUS_AVAILABLE, 'Available'],
[RACK_STATUS_DEPRECATED, 'Deprecated'],
]
# Parent/child device roles
SUBDEVICE_ROLE_PARENT = True
SUBDEVICE_ROLE_CHILD = False
@@ -233,6 +245,36 @@ IFACE_MODE_CHOICES = [
[IFACE_MODE_TAGGED_ALL, 'Tagged All'],
]
# Pass-through port types
PORT_TYPE_8P8C = 1000
PORT_TYPE_ST = 2000
PORT_TYPE_SC = 2100
PORT_TYPE_FC = 2200
PORT_TYPE_LC = 2300
PORT_TYPE_MTRJ = 2400
PORT_TYPE_MPO = 2500
PORT_TYPE_LSH = 2600
PORT_TYPE_CHOICES = [
[
'Copper',
[
[PORT_TYPE_8P8C, '8P8C'],
],
],
[
'Fiber Optic',
[
[PORT_TYPE_FC, 'FC'],
[PORT_TYPE_LC, 'LC'],
[PORT_TYPE_LSH, 'LSH'],
[PORT_TYPE_MPO, 'MPO'],
[PORT_TYPE_MTRJ, 'MTRJ'],
[PORT_TYPE_SC, 'SC'],
[PORT_TYPE_ST, 'ST'],
]
]
]
# Device statuses
DEVICE_STATUS_OFFLINE = 0
DEVICE_STATUS_ACTIVE = 1
@@ -259,7 +301,7 @@ SITE_STATUS_CHOICES = [
[SITE_STATUS_RETIRED, 'Retired'],
]
# Bootstrap CSS classes for device statuses
# Bootstrap CSS classes for device/rack statuses
STATUS_CLASSES = {
0: 'warning',
1: 'success',
@@ -277,12 +319,81 @@ CONNECTION_STATUS_CHOICES = [
[CONNECTION_STATUS_CONNECTED, 'Connected'],
]
# Platform -> RPC client mappings
RPC_CLIENT_JUNIPER_JUNOS = 'juniper-junos'
RPC_CLIENT_CISCO_IOS = 'cisco-ios'
RPC_CLIENT_OPENGEAR = 'opengear'
RPC_CLIENT_CHOICES = [
[RPC_CLIENT_JUNIPER_JUNOS, 'Juniper Junos (NETCONF)'],
[RPC_CLIENT_CISCO_IOS, 'Cisco IOS (SSH)'],
[RPC_CLIENT_OPENGEAR, 'Opengear (SSH)'],
# Cable endpoint types
CABLE_TERMINATION_TYPES = [
'consoleport', 'consoleserverport', 'interface', 'poweroutlet', 'powerport', 'frontport', 'rearport',
]
# Cable types
CABLE_TYPE_CAT3 = 1300
CABLE_TYPE_CAT5 = 1500
CABLE_TYPE_CAT5E = 1510
CABLE_TYPE_CAT6 = 1600
CABLE_TYPE_CAT6A = 1610
CABLE_TYPE_CAT7 = 1700
CABLE_TYPE_MMF_OM1 = 3010
CABLE_TYPE_MMF_OM2 = 3020
CABLE_TYPE_MMF_OM3 = 3030
CABLE_TYPE_MMF_OM4 = 3040
CABLE_TYPE_SMF = 3500
CABLE_TYPE_POWER = 5000
CABLE_TYPE_CHOICES = (
(
'Copper', (
(CABLE_TYPE_CAT3, 'CAT3'),
(CABLE_TYPE_CAT5, 'CAT5'),
(CABLE_TYPE_CAT5E, 'CAT5e'),
(CABLE_TYPE_CAT6, 'CAT6'),
(CABLE_TYPE_CAT6A, 'CAT6a'),
(CABLE_TYPE_CAT7, 'CAT7'),
),
),
(
'Fiber', (
(CABLE_TYPE_MMF_OM1, 'Multimode Fiber (OM1)'),
(CABLE_TYPE_MMF_OM2, 'Multimode Fiber (OM2)'),
(CABLE_TYPE_MMF_OM3, 'Multimode Fiber (OM3)'),
(CABLE_TYPE_MMF_OM4, 'Multimode Fiber (OM4)'),
(CABLE_TYPE_SMF, 'Singlemode Fiber'),
),
),
(CABLE_TYPE_POWER, 'Power'),
)
CABLE_TERMINATION_TYPE_CHOICES = {
# (API endpoint, human-friendly name)
'consoleport': ('console-ports', 'Console port'),
'consoleserverport': ('console-server-ports', 'Console server port'),
'powerport': ('power-ports', 'Power port'),
'poweroutlet': ('power-outlets', 'Power outlet'),
'interface': ('interfaces', 'Interface'),
'frontport': ('front-ports', 'Front panel port'),
'rearport': ('rear-ports', 'Rear panel port'),
}
COMPATIBLE_TERMINATION_TYPES = {
'consoleport': ['consoleserverport', 'frontport', 'rearport'],
'consoleserverport': ['consoleport', 'frontport', 'rearport'],
'powerport': ['poweroutlet'],
'poweroutlet': ['powerport'],
'interface': ['interface', 'circuittermination', 'frontport', 'rearport'],
'frontport': ['consoleport', 'consoleserverport', 'interface', 'frontport', 'rearport', 'circuittermination'],
'rearport': ['consoleport', 'consoleserverport', 'interface', 'frontport', 'rearport', 'circuittermination'],
'circuittermination': ['interface', 'frontport', 'rearport'],
}
LENGTH_UNIT_METER = 1200
LENGTH_UNIT_CENTIMETER = 1100
LENGTH_UNIT_MILLIMETER = 1000
LENGTH_UNIT_FOOT = 2100
LENGTH_UNIT_INCH = 2000
CABLE_LENGTH_UNIT_CHOICES = (
(LENGTH_UNIT_METER, 'Meters'),
(LENGTH_UNIT_CENTIMETER, 'Centimeters'),
(LENGTH_UNIT_FOOT, 'Feet'),
(LENGTH_UNIT_INCH, 'Inches'),
)
RACK_DIMENSION_UNIT_CHOICES = (
(LENGTH_UNIT_MILLIMETER, 'Millimeters'),
(LENGTH_UNIT_INCH, 'Inches'),
)

View File

@@ -1,10 +1,7 @@
from __future__ import unicode_literals
from netaddr import AddrFormatError, EUI, mac_unix_expanded
from django.core.exceptions import ValidationError
from django.core.validators import MinValueValidator, MaxValueValidator
from django.db import models
from netaddr import AddrFormatError, EUI, mac_unix_expanded
class ASNField(models.BigIntegerField):

View File

@@ -1,5 +1,3 @@
from __future__ import unicode_literals
import django_filters
from django.contrib.auth.models import User
from django.core.exceptions import ObjectDoesNotExist
@@ -9,17 +7,15 @@ from netaddr.core import AddrFormatError
from extras.filters import CustomFieldFilterSet
from tenancy.models import Tenant
from utilities.constants import COLOR_CHOICES
from utilities.filters import NullableCharFieldFilter, NumericInFilter, TagFilter
from virtualization.models import Cluster
from .constants import (
DEVICE_STATUS_CHOICES, IFACE_FF_LAG, NONCONNECTABLE_IFACE_TYPES, SITE_STATUS_CHOICES, VIRTUAL_IFACE_TYPES,
WIRELESS_IFACE_TYPES, IFACE_FF_CHOICES,
)
from .constants import *
from .models import (
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
DeviceBayTemplate, DeviceRole, DeviceType, Interface, InterfaceConnection, InterfaceTemplate, Manufacturer,
InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup,
RackReservation, RackRole, Region, Site, VirtualChassis,
Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate,
InventoryItem, Manufacturer, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack,
RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site, VirtualChassis,
)
@@ -33,7 +29,7 @@ class RegionFilter(django_filters.FilterSet):
label='Parent region (ID)',
)
parent = django_filters.ModelMultipleChoiceFilter(
name='parent__slug',
field_name='parent__slug',
queryset=Region.objects.all(),
to_field_name='slug',
label='Parent region (slug)',
@@ -54,7 +50,10 @@ class RegionFilter(django_filters.FilterSet):
class SiteFilter(CustomFieldFilterSet, django_filters.FilterSet):
id__in = NumericInFilter(name='id', lookup_expr='in')
id__in = NumericInFilter(
field_name='id',
lookup_expr='in'
)
q = django_filters.CharFilter(
method='search',
label='Search',
@@ -68,7 +67,7 @@ class SiteFilter(CustomFieldFilterSet, django_filters.FilterSet):
label='Region (ID)',
)
region = django_filters.ModelMultipleChoiceFilter(
name='region__slug',
field_name='region__slug',
queryset=Region.objects.all(),
to_field_name='slug',
label='Region (slug)',
@@ -78,7 +77,7 @@ class SiteFilter(CustomFieldFilterSet, django_filters.FilterSet):
label='Tenant (ID)',
)
tenant = django_filters.ModelMultipleChoiceFilter(
name='tenant__slug',
field_name='tenant__slug',
queryset=Tenant.objects.all(),
to_field_name='slug',
label='Tenant (slug)',
@@ -120,7 +119,7 @@ class RackGroupFilter(django_filters.FilterSet):
label='Site (ID)',
)
site = django_filters.ModelMultipleChoiceFilter(
name='site__slug',
field_name='site__slug',
queryset=Site.objects.all(),
to_field_name='slug',
label='Site (slug)',
@@ -148,7 +147,10 @@ class RackRoleFilter(django_filters.FilterSet):
class RackFilter(CustomFieldFilterSet, django_filters.FilterSet):
id__in = NumericInFilter(name='id', lookup_expr='in')
id__in = NumericInFilter(
field_name='id',
lookup_expr='in'
)
q = django_filters.CharFilter(
method='search',
label='Search',
@@ -159,7 +161,7 @@ class RackFilter(CustomFieldFilterSet, django_filters.FilterSet):
label='Site (ID)',
)
site = django_filters.ModelMultipleChoiceFilter(
name='site__slug',
field_name='site__slug',
queryset=Site.objects.all(),
to_field_name='slug',
label='Site (slug)',
@@ -169,7 +171,7 @@ class RackFilter(CustomFieldFilterSet, django_filters.FilterSet):
label='Group (ID)',
)
group = django_filters.ModelMultipleChoiceFilter(
name='group__slug',
field_name='group__slug',
queryset=RackGroup.objects.all(),
to_field_name='slug',
label='Group',
@@ -179,26 +181,34 @@ class RackFilter(CustomFieldFilterSet, django_filters.FilterSet):
label='Tenant (ID)',
)
tenant = django_filters.ModelMultipleChoiceFilter(
name='tenant__slug',
field_name='tenant__slug',
queryset=Tenant.objects.all(),
to_field_name='slug',
label='Tenant (slug)',
)
status = django_filters.MultipleChoiceFilter(
choices=RACK_STATUS_CHOICES,
null_value=None
)
role_id = django_filters.ModelMultipleChoiceFilter(
queryset=RackRole.objects.all(),
label='Role (ID)',
)
role = django_filters.ModelMultipleChoiceFilter(
name='role__slug',
field_name='role__slug',
queryset=RackRole.objects.all(),
to_field_name='slug',
label='Role (slug)',
)
asset_tag = NullableCharFieldFilter()
tag = TagFilter()
class Meta:
model = Rack
fields = ['name', 'serial', 'type', 'width', 'u_height', 'desc_units']
fields = [
'name', 'serial', 'asset_tag', 'type', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth',
'outer_unit',
]
def search(self, queryset, name, value):
if not value.strip():
@@ -207,12 +217,16 @@ class RackFilter(CustomFieldFilterSet, django_filters.FilterSet):
Q(name__icontains=value) |
Q(facility_id__icontains=value) |
Q(serial__icontains=value.strip()) |
Q(asset_tag__icontains=value.strip()) |
Q(comments__icontains=value)
)
class RackReservationFilter(django_filters.FilterSet):
id__in = NumericInFilter(name='id', lookup_expr='in')
id__in = NumericInFilter(
field_name='id',
lookup_expr='in'
)
q = django_filters.CharFilter(
method='search',
label='Search',
@@ -222,23 +236,23 @@ class RackReservationFilter(django_filters.FilterSet):
label='Rack (ID)',
)
site_id = django_filters.ModelMultipleChoiceFilter(
name='rack__site',
field_name='rack__site',
queryset=Site.objects.all(),
label='Site (ID)',
)
site = django_filters.ModelMultipleChoiceFilter(
name='rack__site__slug',
field_name='rack__site__slug',
queryset=Site.objects.all(),
to_field_name='slug',
label='Site (slug)',
)
group_id = django_filters.ModelMultipleChoiceFilter(
name='rack__group',
field_name='rack__group',
queryset=RackGroup.objects.all(),
label='Group (ID)',
)
group = django_filters.ModelMultipleChoiceFilter(
name='rack__group__slug',
field_name='rack__group__slug',
queryset=RackGroup.objects.all(),
to_field_name='slug',
label='Group',
@@ -248,7 +262,7 @@ class RackReservationFilter(django_filters.FilterSet):
label='Tenant (ID)',
)
tenant = django_filters.ModelMultipleChoiceFilter(
name='tenant__slug',
field_name='tenant__slug',
queryset=Tenant.objects.all(),
to_field_name='slug',
label='Tenant (slug)',
@@ -258,7 +272,7 @@ class RackReservationFilter(django_filters.FilterSet):
label='User (ID)',
)
user = django_filters.ModelMultipleChoiceFilter(
name='user',
field_name='user',
queryset=User.objects.all(),
to_field_name='username',
label='User (name)',
@@ -286,8 +300,11 @@ class ManufacturerFilter(django_filters.FilterSet):
fields = ['name', 'slug']
class DeviceTypeFilter(CustomFieldFilterSet, django_filters.FilterSet):
id__in = NumericInFilter(name='id', lookup_expr='in')
class DeviceTypeFilter(CustomFieldFilterSet):
id__in = NumericInFilter(
field_name='id',
lookup_expr='in'
)
q = django_filters.CharFilter(
method='search',
label='Search',
@@ -297,18 +314,41 @@ class DeviceTypeFilter(CustomFieldFilterSet, django_filters.FilterSet):
label='Manufacturer (ID)',
)
manufacturer = django_filters.ModelMultipleChoiceFilter(
name='manufacturer__slug',
field_name='manufacturer__slug',
queryset=Manufacturer.objects.all(),
to_field_name='slug',
label='Manufacturer (slug)',
)
console_ports = django_filters.BooleanFilter(
method='_console_ports',
label='Has console ports',
)
console_server_ports = django_filters.BooleanFilter(
method='_console_server_ports',
label='Has console server ports',
)
power_ports = django_filters.BooleanFilter(
method='_power_ports',
label='Has power ports',
)
power_outlets = django_filters.BooleanFilter(
method='_power_outlets',
label='Has power outlets',
)
interfaces = django_filters.BooleanFilter(
method='_interfaces',
label='Has interfaces',
)
pass_through_ports = django_filters.BooleanFilter(
method='_pass_through_ports',
label='Has pass-through ports',
)
tag = TagFilter()
class Meta:
model = DeviceType
fields = [
'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'is_console_server', 'is_pdu',
'is_network_device', 'subdevice_role',
'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role',
]
def search(self, queryset, name, value):
@@ -321,11 +361,32 @@ class DeviceTypeFilter(CustomFieldFilterSet, django_filters.FilterSet):
Q(comments__icontains=value)
)
def _console_ports(self, queryset, name, value):
return queryset.exclude(consoleport_templates__isnull=value)
def _console_server_ports(self, queryset, name, value):
return queryset.exclude(consoleserverport_templates__isnull=value)
def _power_ports(self, queryset, name, value):
return queryset.exclude(powerport_templates__isnull=value)
def _power_outlets(self, queryset, name, value):
return queryset.exclude(poweroutlet_templates__isnull=value)
def _interfaces(self, queryset, name, value):
return queryset.exclude(interface_templates__isnull=value)
def _pass_through_ports(self, queryset, name, value):
return queryset.exclude(
frontport_templates__isnull=value,
rearport_templates__isnull=value
)
class DeviceTypeComponentFilterSet(django_filters.FilterSet):
devicetype_id = django_filters.ModelMultipleChoiceFilter(
queryset=DeviceType.objects.all(),
name='device_type_id',
field_name='device_type_id',
label='Device type (ID)',
)
@@ -365,6 +426,20 @@ class InterfaceTemplateFilter(DeviceTypeComponentFilterSet):
fields = ['name', 'form_factor', 'mgmt_only']
class FrontPortTemplateFilter(DeviceTypeComponentFilterSet):
class Meta:
model = FrontPortTemplate
fields = ['name', 'type']
class RearPortTemplateFilter(DeviceTypeComponentFilterSet):
class Meta:
model = RearPortTemplate
fields = ['name', 'type']
class DeviceBayTemplateFilter(DeviceTypeComponentFilterSet):
class Meta:
@@ -381,12 +456,12 @@ class DeviceRoleFilter(django_filters.FilterSet):
class PlatformFilter(django_filters.FilterSet):
manufacturer_id = django_filters.ModelMultipleChoiceFilter(
name='manufacturer',
field_name='manufacturer',
queryset=Manufacturer.objects.all(),
label='Manufacturer (ID)',
)
manufacturer = django_filters.ModelMultipleChoiceFilter(
name='manufacturer__slug',
field_name='manufacturer__slug',
queryset=Manufacturer.objects.all(),
to_field_name='slug',
label='Manufacturer (slug)',
@@ -397,19 +472,22 @@ class PlatformFilter(django_filters.FilterSet):
fields = ['name', 'slug']
class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet):
id__in = NumericInFilter(name='id', lookup_expr='in')
class DeviceFilter(CustomFieldFilterSet):
id__in = NumericInFilter(
field_name='id',
lookup_expr='in'
)
q = django_filters.CharFilter(
method='search',
label='Search',
)
manufacturer_id = django_filters.ModelMultipleChoiceFilter(
name='device_type__manufacturer',
field_name='device_type__manufacturer',
queryset=Manufacturer.objects.all(),
label='Manufacturer (ID)',
)
manufacturer = django_filters.ModelMultipleChoiceFilter(
name='device_type__manufacturer__slug',
field_name='device_type__manufacturer__slug',
queryset=Manufacturer.objects.all(),
to_field_name='slug',
label='Manufacturer (slug)',
@@ -419,12 +497,12 @@ class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet):
label='Device type (ID)',
)
role_id = django_filters.ModelMultipleChoiceFilter(
name='device_role_id',
field_name='device_role_id',
queryset=DeviceRole.objects.all(),
label='Role (ID)',
)
role = django_filters.ModelMultipleChoiceFilter(
name='device_role__slug',
field_name='device_role__slug',
queryset=DeviceRole.objects.all(),
to_field_name='slug',
label='Role (slug)',
@@ -434,7 +512,7 @@ class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet):
label='Tenant (ID)',
)
tenant = django_filters.ModelMultipleChoiceFilter(
name='tenant__slug',
field_name='tenant__slug',
queryset=Tenant.objects.all(),
to_field_name='slug',
label='Tenant (slug)',
@@ -444,7 +522,7 @@ class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet):
label='Platform (ID)',
)
platform = django_filters.ModelMultipleChoiceFilter(
name='platform__slug',
field_name='platform__slug',
queryset=Platform.objects.all(),
to_field_name='slug',
label='Platform (slug)',
@@ -453,12 +531,12 @@ class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet):
asset_tag = NullableCharFieldFilter()
region_id = django_filters.NumberFilter(
method='filter_region',
name='pk',
field_name='pk',
label='Region (ID)',
)
region = django_filters.CharFilter(
method='filter_region',
name='slug',
field_name='slug',
label='Region (slug)',
)
site_id = django_filters.ModelMultipleChoiceFilter(
@@ -466,18 +544,18 @@ class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet):
label='Site (ID)',
)
site = django_filters.ModelMultipleChoiceFilter(
name='site__slug',
field_name='site__slug',
queryset=Site.objects.all(),
to_field_name='slug',
label='Site name (slug)',
)
rack_group_id = django_filters.ModelMultipleChoiceFilter(
name='rack__group',
field_name='rack__group',
queryset=RackGroup.objects.all(),
label='Rack group (ID)',
)
rack_id = django_filters.ModelMultipleChoiceFilter(
name='rack',
field_name='rack',
queryset=Rack.objects.all(),
label='Rack (ID)',
)
@@ -486,7 +564,7 @@ class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet):
label='VM cluster (ID)',
)
model = django_filters.ModelMultipleChoiceFilter(
name='device_type__slug',
field_name='device_type__slug',
queryset=DeviceType.objects.all(),
to_field_name='slug',
label='Device model (slug)',
@@ -496,21 +574,9 @@ class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet):
null_value=None
)
is_full_depth = django_filters.BooleanFilter(
name='device_type__is_full_depth',
field_name='device_type__is_full_depth',
label='Is full depth',
)
is_console_server = django_filters.BooleanFilter(
name='device_type__is_console_server',
label='Is a console server',
)
is_pdu = django_filters.BooleanFilter(
name='device_type__is_pdu',
label='Is a PDU',
)
is_network_device = django_filters.BooleanFilter(
name='device_type__is_network_device',
label='Is a network device',
)
mac_address = django_filters.CharFilter(
method='_mac_address',
label='MAC address',
@@ -520,10 +586,34 @@ class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet):
label='Has a primary IP',
)
virtual_chassis_id = django_filters.ModelMultipleChoiceFilter(
name='virtual_chassis',
field_name='virtual_chassis',
queryset=VirtualChassis.objects.all(),
label='Virtual chassis (ID)',
)
console_ports = django_filters.BooleanFilter(
method='_console_ports',
label='Has console ports',
)
console_server_ports = django_filters.BooleanFilter(
method='_console_server_ports',
label='Has console server ports',
)
power_ports = django_filters.BooleanFilter(
method='_power_ports',
label='Has power ports',
)
power_outlets = django_filters.BooleanFilter(
method='_power_outlets',
label='Has power outlets',
)
interfaces = django_filters.BooleanFilter(
method='_interfaces',
label='Has interfaces',
)
pass_through_ports = django_filters.BooleanFilter(
method='_pass_through_ports',
label='Has pass-through ports',
)
tag = TagFilter()
class Meta:
@@ -573,6 +663,27 @@ class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet):
Q(primary_ip6__isnull=False)
)
def _console_ports(self, queryset, name, value):
return queryset.exclude(consoleports__isnull=value)
def _console_server_ports(self, queryset, name, value):
return queryset.exclude(consoleserverports__isnull=value)
def _power_ports(self, queryset, name, value):
return queryset.exclude(powerports__isnull=value)
def _power_outlets(self, queryset, name, value):
return queryset.exclude(poweroutlets_isnull=value)
def _interfaces(self, queryset, name, value):
return queryset.exclude(interfaces__isnull=value)
def _pass_through_ports(self, queryset, name, value):
return queryset.exclude(
frontports__isnull=value,
rearports__isnull=value
)
class DeviceComponentFilterSet(django_filters.FilterSet):
device_id = django_filters.ModelChoiceFilter(
@@ -588,54 +699,78 @@ class DeviceComponentFilterSet(django_filters.FilterSet):
class ConsolePortFilter(DeviceComponentFilterSet):
cabled = django_filters.BooleanFilter(
field_name='cable',
lookup_expr='isnull',
exclude=True
)
class Meta:
model = ConsolePort
fields = ['name']
fields = ['name', 'connection_status']
class ConsoleServerPortFilter(DeviceComponentFilterSet):
cabled = django_filters.BooleanFilter(
field_name='cable',
lookup_expr='isnull',
exclude=True
)
class Meta:
model = ConsoleServerPort
fields = ['name']
fields = ['name', 'connection_status']
class PowerPortFilter(DeviceComponentFilterSet):
cabled = django_filters.BooleanFilter(
field_name='cable',
lookup_expr='isnull',
exclude=True
)
class Meta:
model = PowerPort
fields = ['name']
fields = ['name', 'connection_status']
class PowerOutletFilter(DeviceComponentFilterSet):
cabled = django_filters.BooleanFilter(
field_name='cable',
lookup_expr='isnull',
exclude=True
)
class Meta:
model = PowerOutlet
fields = ['name']
fields = ['name', 'connection_status']
class InterfaceFilter(django_filters.FilterSet):
"""
Not using DeviceComponentFilterSet for Interfaces because we need to glean the ordering logic from the parent
Device's DeviceType.
Not using DeviceComponentFilterSet for Interfaces because we need to check for VirtualChassis membership.
"""
device = django_filters.CharFilter(
method='filter_device',
name='name',
field_name='name',
label='Device',
)
device_id = django_filters.NumberFilter(
method='filter_device',
name='pk',
field_name='pk',
label='Device (ID)',
)
cabled = django_filters.BooleanFilter(
field_name='cable',
lookup_expr='isnull',
exclude=True
)
type = django_filters.CharFilter(
method='filter_type',
label='Interface type',
)
lag_id = django_filters.ModelMultipleChoiceFilter(
name='lag',
field_name='lag',
queryset=Interface.objects.all(),
label='LAG interface (ID)',
)
@@ -659,14 +794,13 @@ class InterfaceFilter(django_filters.FilterSet):
class Meta:
model = Interface
fields = ['name', 'enabled', 'mtu', 'mgmt_only']
fields = ['name', 'connection_status', 'form_factor', 'enabled', 'mtu', 'mgmt_only']
def filter_device(self, queryset, name, value):
try:
device = Device.objects.select_related('device_type').get(**{name: value})
vc_interface_ids = [i['id'] for i in device.vc_interfaces.values('id')]
ordering = device.device_type.interface_ordering
return queryset.filter(pk__in=vc_interface_ids).order_naturally(ordering)
device = Device.objects.get(**{name: value})
vc_interface_ids = device.vc_interfaces.values_list('id', flat=True)
return queryset.filter(pk__in=vc_interface_ids)
except Device.DoesNotExist:
return queryset.none()
@@ -708,6 +842,30 @@ class InterfaceFilter(django_filters.FilterSet):
return queryset.none()
class FrontPortFilter(DeviceComponentFilterSet):
cabled = django_filters.BooleanFilter(
field_name='cable',
lookup_expr='isnull',
exclude=True
)
class Meta:
model = FrontPort
fields = ['name', 'type']
class RearPortFilter(DeviceComponentFilterSet):
cabled = django_filters.BooleanFilter(
field_name='cable',
lookup_expr='isnull',
exclude=True
)
class Meta:
model = RearPort
fields = ['name', 'type']
class DeviceBayFilter(DeviceComponentFilterSet):
class Meta:
@@ -738,7 +896,7 @@ class InventoryItemFilter(DeviceComponentFilterSet):
label='Manufacturer (ID)',
)
manufacturer = django_filters.ModelMultipleChoiceFilter(
name='manufacturer__slug',
field_name='manufacturer__slug',
queryset=Manufacturer.objects.all(),
to_field_name='slug',
label='Manufacturer (slug)',
@@ -768,23 +926,23 @@ class VirtualChassisFilter(django_filters.FilterSet):
label='Search',
)
site_id = django_filters.ModelMultipleChoiceFilter(
name='master__site',
field_name='master__site',
queryset=Site.objects.all(),
label='Site (ID)',
)
site = django_filters.ModelMultipleChoiceFilter(
name='master__site__slug',
field_name='master__site__slug',
queryset=Site.objects.all(),
to_field_name='slug',
label='Site name (slug)',
)
tenant_id = django_filters.ModelMultipleChoiceFilter(
name='master__tenant',
field_name='master__tenant',
queryset=Tenant.objects.all(),
label='Tenant (ID)',
)
tenant = django_filters.ModelMultipleChoiceFilter(
name='master__tenant__slug',
field_name='master__tenant__slug',
queryset=Tenant.objects.all(),
to_field_name='slug',
label='Tenant (slug)',
@@ -805,6 +963,28 @@ class VirtualChassisFilter(django_filters.FilterSet):
return queryset.filter(qs_filter)
class CableFilter(django_filters.FilterSet):
q = django_filters.CharFilter(
method='search',
label='Search',
)
type = django_filters.MultipleChoiceFilter(
choices=CABLE_TYPE_CHOICES
)
color = django_filters.MultipleChoiceFilter(
choices=COLOR_CHOICES
)
class Meta:
model = Cable
fields = ['type', 'status', 'color', 'length', 'length_unit']
def search(self, queryset, name, value):
if not value.strip():
return queryset
return queryset.filter(label__icontains=value)
class ConsoleConnectionFilter(django_filters.FilterSet):
site = django_filters.CharFilter(
method='filter_site',
@@ -822,14 +1002,14 @@ class ConsoleConnectionFilter(django_filters.FilterSet):
def filter_site(self, queryset, name, value):
if not value.strip():
return queryset
return queryset.filter(cs_port__device__site__slug=value)
return queryset.filter(connected_endpoint__device__site__slug=value)
def filter_device(self, queryset, name, value):
if not value.strip():
return queryset
return queryset.filter(
Q(device__name__icontains=value) |
Q(cs_port__device__name__icontains=value)
Q(connected_endpoint__device__name__icontains=value)
)
@@ -850,14 +1030,14 @@ class PowerConnectionFilter(django_filters.FilterSet):
def filter_site(self, queryset, name, value):
if not value.strip():
return queryset
return queryset.filter(power_outlet__device__site__slug=value)
return queryset.filter(connected_endpoint__device__site__slug=value)
def filter_device(self, queryset, name, value):
if not value.strip():
return queryset
return queryset.filter(
Q(device__name__icontains=value) |
Q(power_outlet__device__name__icontains=value)
Q(connected_endpoint__device__name__icontains=value)
)
@@ -872,21 +1052,21 @@ class InterfaceConnectionFilter(django_filters.FilterSet):
)
class Meta:
model = InterfaceConnection
model = Interface
fields = ['connection_status']
def filter_site(self, queryset, name, value):
if not value.strip():
return queryset
return queryset.filter(
Q(interface_a__device__site__slug=value) |
Q(interface_b__device__site__slug=value)
Q(device__site__slug=value) |
Q(_connected_interface__device__site__slug=value)
)
def filter_device(self, queryset, name, value):
if not value.strip():
return queryset
return queryset.filter(
Q(interface_a__device__name__icontains=value) |
Q(interface_b__device__name__icontains=value)
Q(device__name__icontains=value) |
Q(_connected_interface__device__name__icontains=value)
)

View File

@@ -76,10 +76,7 @@
"model": "MX960",
"slug": "mx960",
"u_height": 16,
"is_full_depth": true,
"is_console_server": false,
"is_pdu": false,
"is_network_device": true
"is_full_depth": true
}
},
{
@@ -92,10 +89,7 @@
"model": "EX9214",
"slug": "ex9214",
"u_height": 16,
"is_full_depth": true,
"is_console_server": false,
"is_pdu": false,
"is_network_device": true
"is_full_depth": true
}
},
{
@@ -108,10 +102,7 @@
"model": "QFX5100-24Q",
"slug": "qfx5100-24q",
"u_height": 1,
"is_full_depth": true,
"is_console_server": false,
"is_pdu": false,
"is_network_device": true
"is_full_depth": true
}
},
{
@@ -124,10 +115,7 @@
"model": "QFX5100-48S",
"slug": "qfx5100-48s",
"u_height": 1,
"is_full_depth": true,
"is_console_server": false,
"is_pdu": false,
"is_network_device": true
"is_full_depth": true
}
},
{
@@ -140,10 +128,7 @@
"model": "CM4148",
"slug": "cm4148",
"u_height": 1,
"is_full_depth": true,
"is_console_server": true,
"is_pdu": false,
"is_network_device": false
"is_full_depth": true
}
},
{
@@ -156,10 +141,7 @@
"model": "CWG-24VYM415C9",
"slug": "cwg-24vym415c9",
"u_height": 0,
"is_full_depth": false,
"is_console_server": false,
"is_pdu": true,
"is_network_device": false
"is_full_depth": false
}
},
{
@@ -1903,8 +1885,7 @@
"pk": 1,
"fields": {
"name": "Juniper Junos",
"slug": "juniper-junos",
"rpc_client": "juniper-junos"
"slug": "juniper-junos"
}
},
{
@@ -1912,8 +1893,7 @@
"pk": 2,
"fields": {
"name": "Opengear",
"slug": "opengear",
"rpc_client": "opengear"
"slug": "opengear"
}
},
{
@@ -2153,7 +2133,7 @@
"fields": {
"device": 1,
"name": "Console (RE0)",
"cs_port": 27,
"connected_endpoint": 27,
"connection_status": true
}
},
@@ -2163,7 +2143,7 @@
"fields": {
"device": 1,
"name": "Console (RE1)",
"cs_port": 38,
"connected_endpoint": 38,
"connection_status": true
}
},
@@ -2173,7 +2153,7 @@
"fields": {
"device": 2,
"name": "Console (RE0)",
"cs_port": 5,
"connected_endpoint": 5,
"connection_status": true
}
},
@@ -2183,7 +2163,7 @@
"fields": {
"device": 2,
"name": "Console (RE1)",
"cs_port": 16,
"connected_endpoint": 16,
"connection_status": true
}
},
@@ -2193,7 +2173,7 @@
"fields": {
"device": 3,
"name": "Console",
"cs_port": 49,
"connected_endpoint": 49,
"connection_status": true
}
},
@@ -2203,7 +2183,7 @@
"fields": {
"device": 4,
"name": "Console",
"cs_port": 48,
"connected_endpoint": 48,
"connection_status": true
}
},
@@ -2213,7 +2193,7 @@
"fields": {
"device": 5,
"name": "Console",
"cs_port": null,
"connected_endpoint": null,
"connection_status": true
}
},
@@ -2223,7 +2203,7 @@
"fields": {
"device": 6,
"name": "Console",
"cs_port": null,
"connected_endpoint": null,
"connection_status": true
}
},
@@ -2233,7 +2213,7 @@
"fields": {
"device": 7,
"name": "Console (RE0)",
"cs_port": null,
"connected_endpoint": null,
"connection_status": true
}
},
@@ -2243,7 +2223,7 @@
"fields": {
"device": 7,
"name": "Console (RE1)",
"cs_port": null,
"connected_endpoint": null,
"connection_status": true
}
},
@@ -2253,7 +2233,7 @@
"fields": {
"device": 8,
"name": "Console (RE0)",
"cs_port": null,
"connected_endpoint": null,
"connection_status": true
}
},
@@ -2263,7 +2243,7 @@
"fields": {
"device": 8,
"name": "Console (RE1)",
"cs_port": null,
"connected_endpoint": null,
"connection_status": true
}
},
@@ -2273,7 +2253,7 @@
"fields": {
"device": 9,
"name": "Console",
"cs_port": null,
"connected_endpoint": null,
"connection_status": true
}
},
@@ -2283,7 +2263,7 @@
"fields": {
"device": 11,
"name": "Serial",
"cs_port": null,
"connected_endpoint": null,
"connection_status": true
}
},
@@ -2293,7 +2273,7 @@
"fields": {
"device": 12,
"name": "Serial",
"cs_port": null,
"connected_endpoint": null,
"connection_status": true
}
},
@@ -2687,7 +2667,7 @@
"fields": {
"device": 1,
"name": "PEM0",
"power_outlet": 25,
"connected_endpoint": 25,
"connection_status": true
}
},
@@ -2697,7 +2677,7 @@
"fields": {
"device": 1,
"name": "PEM1",
"power_outlet": 49,
"connected_endpoint": 49,
"connection_status": true
}
},
@@ -2707,7 +2687,7 @@
"fields": {
"device": 1,
"name": "PEM2",
"power_outlet": null,
"connected_endpoint": null,
"connection_status": true
}
},
@@ -2717,7 +2697,7 @@
"fields": {
"device": 1,
"name": "PEM3",
"power_outlet": null,
"connected_endpoint": null,
"connection_status": true
}
},
@@ -2727,7 +2707,7 @@
"fields": {
"device": 2,
"name": "PEM0",
"power_outlet": 26,
"connected_endpoint": 26,
"connection_status": true
}
},
@@ -2737,7 +2717,7 @@
"fields": {
"device": 2,
"name": "PEM1",
"power_outlet": 50,
"connected_endpoint": 50,
"connection_status": true
}
},
@@ -2747,7 +2727,7 @@
"fields": {
"device": 2,
"name": "PEM2",
"power_outlet": null,
"connected_endpoint": null,
"connection_status": true
}
},
@@ -2757,7 +2737,7 @@
"fields": {
"device": 2,
"name": "PEM3",
"power_outlet": null,
"connected_endpoint": null,
"connection_status": true
}
},
@@ -2767,7 +2747,7 @@
"fields": {
"device": 4,
"name": "PSU0",
"power_outlet": 28,
"connected_endpoint": 28,
"connection_status": true
}
},
@@ -2777,7 +2757,7 @@
"fields": {
"device": 4,
"name": "PSU1",
"power_outlet": 52,
"connected_endpoint": 52,
"connection_status": true
}
},
@@ -2787,7 +2767,7 @@
"fields": {
"device": 5,
"name": "PSU0",
"power_outlet": 56,
"connected_endpoint": 56,
"connection_status": true
}
},
@@ -2797,7 +2777,7 @@
"fields": {
"device": 5,
"name": "PSU1",
"power_outlet": 32,
"connected_endpoint": 32,
"connection_status": true
}
},
@@ -2807,7 +2787,7 @@
"fields": {
"device": 3,
"name": "PSU0",
"power_outlet": 27,
"connected_endpoint": 27,
"connection_status": true
}
},
@@ -2817,7 +2797,7 @@
"fields": {
"device": 3,
"name": "PSU1",
"power_outlet": 51,
"connected_endpoint": 51,
"connection_status": true
}
},
@@ -2827,7 +2807,7 @@
"fields": {
"device": 7,
"name": "PEM0",
"power_outlet": 53,
"connected_endpoint": 53,
"connection_status": true
}
},
@@ -2837,7 +2817,7 @@
"fields": {
"device": 7,
"name": "PEM1",
"power_outlet": 29,
"connected_endpoint": 29,
"connection_status": true
}
},
@@ -2847,7 +2827,7 @@
"fields": {
"device": 7,
"name": "PEM2",
"power_outlet": null,
"connected_endpoint": null,
"connection_status": true
}
},
@@ -2857,7 +2837,7 @@
"fields": {
"device": 7,
"name": "PEM3",
"power_outlet": null,
"connected_endpoint": null,
"connection_status": true
}
},
@@ -2867,7 +2847,7 @@
"fields": {
"device": 8,
"name": "PEM0",
"power_outlet": 54,
"connected_endpoint": 54,
"connection_status": true
}
},
@@ -2877,7 +2857,7 @@
"fields": {
"device": 8,
"name": "PEM1",
"power_outlet": 30,
"connected_endpoint": 30,
"connection_status": true
}
},
@@ -2887,7 +2867,7 @@
"fields": {
"device": 8,
"name": "PEM2",
"power_outlet": null,
"connected_endpoint": null,
"connection_status": true
}
},
@@ -2897,7 +2877,7 @@
"fields": {
"device": 8,
"name": "PEM3",
"power_outlet": null,
"connected_endpoint": null,
"connection_status": true
}
},
@@ -2907,7 +2887,7 @@
"fields": {
"device": 6,
"name": "PSU0",
"power_outlet": 55,
"connected_endpoint": 55,
"connection_status": true
}
},
@@ -2917,7 +2897,7 @@
"fields": {
"device": 6,
"name": "PSU1",
"power_outlet": 31,
"connected_endpoint": 31,
"connection_status": true
}
},
@@ -2927,7 +2907,7 @@
"fields": {
"device": 9,
"name": "PSU",
"power_outlet": null,
"connected_endpoint": null,
"connection_status": true
}
},
@@ -5748,158 +5728,5 @@
"mgmt_only": true,
"description": ""
}
},
{
"model": "dcim.interfaceconnection",
"pk": 3,
"fields": {
"interface_a": 99,
"interface_b": 15,
"connection_status": true
}
},
{
"model": "dcim.interfaceconnection",
"pk": 4,
"fields": {
"interface_a": 100,
"interface_b": 153,
"connection_status": true
}
},
{
"model": "dcim.interfaceconnection",
"pk": 5,
"fields": {
"interface_a": 46,
"interface_b": 14,
"connection_status": true
}
},
{
"model": "dcim.interfaceconnection",
"pk": 6,
"fields": {
"interface_a": 47,
"interface_b": 152,
"connection_status": true
}
},
{
"model": "dcim.interfaceconnection",
"pk": 7,
"fields": {
"interface_a": 91,
"interface_b": 144,
"connection_status": true
}
},
{
"model": "dcim.interfaceconnection",
"pk": 8,
"fields": {
"interface_a": 92,
"interface_b": 145,
"connection_status": true
}
},
{
"model": "dcim.interfaceconnection",
"pk": 16,
"fields": {
"interface_a": 189,
"interface_b": 37,
"connection_status": true
}
},
{
"model": "dcim.interfaceconnection",
"pk": 17,
"fields": {
"interface_a": 192,
"interface_b": 175,
"connection_status": true
}
},
{
"model": "dcim.interfaceconnection",
"pk": 18,
"fields": {
"interface_a": 195,
"interface_b": 41,
"connection_status": true
}
},
{
"model": "dcim.interfaceconnection",
"pk": 19,
"fields": {
"interface_a": 198,
"interface_b": 179,
"connection_status": true
}
},
{
"model": "dcim.interfaceconnection",
"pk": 20,
"fields": {
"interface_a": 191,
"interface_b": 197,
"connection_status": true
}
},
{
"model": "dcim.interfaceconnection",
"pk": 21,
"fields": {
"interface_a": 194,
"interface_b": 200,
"connection_status": true
}
},
{
"model": "dcim.interfaceconnection",
"pk": 22,
"fields": {
"interface_a": 9,
"interface_b": 218,
"connection_status": true
}
},
{
"model": "dcim.interfaceconnection",
"pk": 23,
"fields": {
"interface_a": 8,
"interface_b": 206,
"connection_status": true
}
},
{
"model": "dcim.interfaceconnection",
"pk": 24,
"fields": {
"interface_a": 7,
"interface_b": 212,
"connection_status": true
}
},
{
"model": "dcim.interfaceconnection",
"pk": 25,
"fields": {
"interface_a": 217,
"interface_b": 205,
"connection_status": true
}
},
{
"model": "dcim.interfaceconnection",
"pk": 26,
"fields": {
"interface_a": 216,
"interface_b": 211,
"connection_status": true
}
}
]

View File

@@ -149,8 +149,7 @@
"pk": 1,
"fields": {
"name": "Cisco IOS",
"slug": "cisco-ios",
"rpc_client": "cisco-ios"
"slug": "cisco-ios"
}
},
{
@@ -158,8 +157,7 @@
"pk": 2,
"fields": {
"name": "Cisco NX-OS",
"slug": "cisco-nx-os",
"rpc_client": ""
"slug": "cisco-nx-os"
}
},
{
@@ -167,8 +165,7 @@
"pk": 3,
"fields": {
"name": "Juniper Junos",
"slug": "juniper-junos",
"rpc_client": "juniper-junos"
"slug": "juniper-junos"
}
},
{
@@ -176,8 +173,7 @@
"pk": 4,
"fields": {
"name": "Arista EOS",
"slug": "arista-eos",
"rpc_client": ""
"slug": "arista-eos"
}
},
{
@@ -185,8 +181,7 @@
"pk": 5,
"fields": {
"name": "Linux",
"slug": "linux",
"rpc_client": ""
"slug": "linux"
}
},
{
@@ -194,8 +189,7 @@
"pk": 6,
"fields": {
"name": "Opengear",
"slug": "opengear",
"rpc_client": "opengear"
"slug": "opengear"
}
}
]

File diff suppressed because it is too large Load Diff

85
netbox/dcim/managers.py Normal file
View File

@@ -0,0 +1,85 @@
from django.db.models import Manager, QuerySet
from django.db.models.expressions import RawSQL
from .constants import NONCONNECTABLE_IFACE_TYPES
# Regular expressions for parsing Interface names
TYPE_RE = r"SUBSTRING({} FROM '^([^0-9\.:]+)')"
SLOT_RE = r"COALESCE(CAST(SUBSTRING({} FROM '^(?:[^0-9]+)?(\d{{1,9}})/') AS integer), NULL)"
SUBSLOT_RE = r"COALESCE(CAST(SUBSTRING({} FROM '^(?:[^0-9\.:]+)?\d{{1,9}}/(\d{{1,9}})') AS integer), NULL)"
POSITION_RE = r"COALESCE(CAST(SUBSTRING({} FROM '^(?:[^0-9]+)?(?:\d{{1,9}}/){{2}}(\d{{1,9}})') AS integer), NULL)"
SUBPOSITION_RE = r"COALESCE(CAST(SUBSTRING({} FROM '^(?:[^0-9]+)?(?:\d{{1,9}}/){{3}}(\d{{1,9}})') AS integer), NULL)"
ID_RE = r"CAST(SUBSTRING({} FROM '^(?:[^0-9\.:]+)?(\d{{1,9}})([^/]|$)') AS integer)"
CHANNEL_RE = r"COALESCE(CAST(SUBSTRING({} FROM '^.*:(\d{{1,9}})(\.\d{{1,9}})?$') AS integer), 0)"
VC_RE = r"COALESCE(CAST(SUBSTRING({} FROM '^.*\.(\d{{1,9}})$') AS integer), 0)"
class DeviceComponentManager(Manager):
def get_queryset(self):
queryset = super().get_queryset()
table_name = self.model._meta.db_table
sql = r"CONCAT(REGEXP_REPLACE({}.name, '\d+$', ''), LPAD(SUBSTRING({}.name FROM '\d+$'), 8, '0'))"
# Pad any trailing digits to effect natural sorting
return queryset.extra(
select={
'name_padded': sql.format(table_name, table_name),
}
).order_by('name_padded')
class InterfaceQuerySet(QuerySet):
def connectable(self):
"""
Return only physical interfaces which are capable of being connected to other interfaces (i.e. not virtual or
wireless).
"""
return self.exclude(form_factor__in=NONCONNECTABLE_IFACE_TYPES)
class InterfaceManager(Manager):
def get_queryset(self):
"""
Naturally order interfaces by their type and numeric position. To order interfaces naturally, the `name` field
is split into eight distinct components: leading text (type), slot, subslot, position, subposition, ID, channel,
and virtual circuit:
{type}{slot or ID}/{subslot}/{position}/{subposition}:{channel}.{vc}
Components absent from the interface name are coalesced to zero or null. For example, an interface named
GigabitEthernet1/2/3 would be parsed as follows:
type = 'GigabitEthernet'
slot = 1
subslot = 2
position = 3
subposition = None
id = None
channel = 0
vc = 0
The original `name` field is considered in its entirety to serve as a fallback in the event interfaces do not
match any of the prescribed fields.
"""
sql_col = '{}.name'.format(self.model._meta.db_table)
ordering = [
'_slot', '_subslot', '_position', '_subposition', '_type', '_id', '_channel', '_vc', 'name',
]
fields = {
'_type': RawSQL(TYPE_RE.format(sql_col), []),
'_id': RawSQL(ID_RE.format(sql_col), []),
'_slot': RawSQL(SLOT_RE.format(sql_col), []),
'_subslot': RawSQL(SUBSLOT_RE.format(sql_col), []),
'_position': RawSQL(POSITION_RE.format(sql_col), []),
'_subposition': RawSQL(SUBPOSITION_RE.format(sql_col), []),
'_channel': RawSQL(CHANNEL_RE.format(sql_col), []),
'_vc': RawSQL(VC_RE.format(sql_col), []),
}
return InterfaceQuerySet(self.model, using=self._db).annotate(**fields).order_by(*ordering)

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-06-22 18:21
from __future__ import unicode_literals
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-06-22 18:21
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.14 on 2018-07-31 02:06
from __future__ import unicode_literals
import dcim.fields
import django.core.validators
from django.db import migrations, models

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-06-28 17:21
from __future__ import unicode_literals
from django.db import migrations, models

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-07-01 20:49
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-07-06 17:22
from __future__ import unicode_literals
import dcim.fields
from django.db import migrations, models

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-07-11 18:40
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-07-11 18:40
from __future__ import unicode_literals
from django.db import migrations

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-07-11 19:01
from __future__ import unicode_literals
from django.db import migrations

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-07-13 19:24
from __future__ import unicode_literals
import dcim.fields
from django.db import migrations

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-07-14 21:38
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.8 on 2016-07-26 15:05
from __future__ import unicode_literals
from django.db import migrations, models

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.8 on 2016-07-26 21:59
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.8 on 2016-08-06 20:24
from __future__ import unicode_literals
from django.db import migrations, models

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.8 on 2016-08-08 21:11
from __future__ import unicode_literals
from django.db import migrations, models

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.8 on 2016-08-09 21:18
from __future__ import unicode_literals
import django.core.validators
from django.db import migrations, models

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.8 on 2016-08-10 13:45
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.8 on 2016-08-10 14:58
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10 on 2016-08-11 15:42
from __future__ import unicode_literals
from django.db import migrations
import utilities.fields

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10 on 2016-09-13 15:20
from __future__ import unicode_literals
from django.db import migrations, models

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10 on 2016-10-28 15:01
from __future__ import unicode_literals
from django.db import migrations, models

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10 on 2016-10-31 18:47
from __future__ import unicode_literals
import django.core.validators
from django.db import migrations, models

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10 on 2016-12-06 16:35
from __future__ import unicode_literals
from django.db import migrations
import utilities.fields

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10 on 2016-12-16 16:08
from __future__ import unicode_literals
from django.db import migrations, models

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.14 on 2018-07-31 02:13
from __future__ import unicode_literals
import dcim.fields
from django.conf import settings
import django.contrib.postgres.fields

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.4 on 2016-12-29 16:23
from __future__ import unicode_literals
from django.db import migrations, models

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.4 on 2017-01-06 16:56
from __future__ import unicode_literals
from django.db import migrations, models

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.4 on 2017-02-16 18:43
from __future__ import unicode_literals
from django.conf import settings
import django.contrib.postgres.fields
from django.db import migrations, models

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.4 on 2017-02-16 21:21
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.4 on 2017-02-16 21:23
from __future__ import unicode_literals
from django.db import migrations

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.4 on 2017-02-16 21:25
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.4 on 2017-02-27 19:55
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.4 on 2017-02-28 17:14
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
import mptt.fields

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.4 on 2017-03-02 15:09
from __future__ import unicode_literals
from django.db import migrations
import utilities.fields

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.6 on 2017-03-17 18:39
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.6 on 2017-03-21 14:55
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2017-05-08 15:57
from __future__ import unicode_literals
from django.db import migrations, models

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.1 on 2017-05-09 16:00
from __future__ import unicode_literals
from django.db import migrations, models

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2017-05-24 15:34
from __future__ import unicode_literals
import dcim.fields
import django.core.validators
from django.db import migrations, models

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.1 on 2017-06-16 21:38
from __future__ import unicode_literals
from django.db import migrations, models

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.1 on 2017-06-23 17:05
from __future__ import unicode_literals
from django.db import migrations, models

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2017-06-23 20:44
from __future__ import unicode_literals
from django.db import migrations, models
import utilities.fields

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.3 on 2017-07-14 17:26
from __future__ import unicode_literals
from django.db import migrations, models

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2017-08-29 21:00
from __future__ import unicode_literals
from django.db import migrations, models

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2017-08-29 21:26
from __future__ import unicode_literals
from django.db import migrations, models

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2017-08-31 14:15
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.14 on 2018-07-31 02:17
from __future__ import unicode_literals
from django.conf import settings
import django.core.validators
from django.db import migrations, models

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2017-09-29 16:09
from __future__ import unicode_literals
from django.db import migrations, models

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2017-10-09 17:43
from __future__ import unicode_literals
from django.db import migrations
import utilities.fields

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2017-10-09 18:43
from __future__ import unicode_literals
from django.db import migrations, models

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2017-10-09 18:50
from __future__ import unicode_literals
from django.db import migrations, models

View File

@@ -1,7 +1,5 @@
# -*- 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

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.6 on 2017-11-10 20:10
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.6 on 2017-11-15 18:56
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.6 on 2017-11-27 17:27
from __future__ import unicode_literals
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.6 on 2017-12-19 20:56
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.6 on 2018-01-25 18:21
from __future__ import unicode_literals
from django.db import migrations, models
import timezone_field.fields

View File

@@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.9 on 2018-02-21 14:41
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion

Some files were not shown because too many files have changed in this diff Show More