Merge v2.5 work

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

View File

@ -5,7 +5,6 @@ addons:
postgresql: "9.4"
language: python
python:
- "2.7"
- "3.5"
install:
- pip install -r requirements.txt

View File

@ -1,3 +1,82 @@
v2.5.0 (FUTURE)
## Notes
### Python 3 Required
As promised, Python 2 support has been completed removed. Python 3.5 or higher is now required to run NetBox. Please see [our Python 3 migration guide](https://netbox.readthedocs.io/en/stable/installation/migrating-to-python3/) for assistance with upgrading.
### Removed Deprecated User Activity Log
The UserAction model, which was deprecated by the new change logging feature in NetBox v2.4, has been removed. If you need to archive legacy user activity, do so prior to upgrading to NetBox v2.5, as the database migration will remove all data associated with this model.
### View Permissions in Django 2.1
Django 2.1 introduces view permissions for object types (not to be confused with object-level permissions). Implementation of [#323](https://github.com/digitalocean/netbox/issues/323) is planned for NetBox v2.6. Users are encourage to begin assigning view permissions as desired in preparation for their eventual enforcement.
### upgrade.sh No Longer Invokes sudo
The `upgrade.sh` script has been tweaked so that it no longer invokes `sudo` internally. This was done to ensure compatibility when running NetBox inside a Python virtual environment. If you need elevated permissions when upgrading NetBox, call the upgrade script with `sudo upgrade.sh`.
## New Features
### Patch Panels and Cables ([#20](https://github.com/digitalocean/netbox/issues/20))
NetBox now supports modeling physical cables for console, power, and interface connections. The new pass-through port component type has also been introduced to model patch panels and similar devices.
## Enhancements
* [#450](https://github.com/digitalocean/netbox/issues/450) - Added `outer_width` and `outer_depth` fields to rack model
* [#867](https://github.com/digitalocean/netbox/issues/867) - Added `description` field to circuit terminations
* [#1444](https://github.com/digitalocean/netbox/issues/1444) - Added an `asset_tag` field for racks
* [#1931](https://github.com/digitalocean/netbox/issues/1931) - Added a count of assigned IP addresses to the interface API serializer
* [#2000](https://github.com/digitalocean/netbox/issues/2000) - Dropped support for Python 2
* [#2053](https://github.com/digitalocean/netbox/issues/2053) - Introduced the `LOGIN_TIMEOUT` configuration setting
* [#2057](https://github.com/digitalocean/netbox/issues/2057) - Added description columns to interface connections list
* [#2104](https://github.com/digitalocean/netbox/issues/2104) - Added a `status` field for racks
* [#2165](https://github.com/digitalocean/netbox/issues/2165) - Improved natural ordering of Interfaces
* [#2292](https://github.com/digitalocean/netbox/issues/2292) - Removed the deprecated UserAction model
* [#2367](https://github.com/digitalocean/netbox/issues/2367) - Removed deprecated RPCClient functionality
* [#2426](https://github.com/digitalocean/netbox/issues/2426) - Introduced `SESSION_FILE_PATH` configuration setting for authentication without write access to database
* [#2594](https://github.com/digitalocean/netbox/issues/2594) - `upgrade.sh` no longer invokes sudo
## Changes From v2.5-beta2
* [#2474](https://github.com/digitalocean/netbox/issues/2474) - Add `cabled` and `connection_status` filters for device components
* [#2616](https://github.com/digitalocean/netbox/issues/2616) - Convert Rack `outer_unit` and Cable `length_unit` to integer-based choice fields
* [#2622](https://github.com/digitalocean/netbox/issues/2622) - Enable filtering cables by multiple types/colors
* [#2624](https://github.com/digitalocean/netbox/issues/2624) - Delete associated content type and permissions when removing InterfaceConnection model
* [#2626](https://github.com/digitalocean/netbox/issues/2626) - Remove extraneous permissions generated from proxy models
* [#2632](https://github.com/digitalocean/netbox/issues/2632) - Change representation of null values from `0` to `null`
* [#2639](https://github.com/digitalocean/netbox/issues/2639) - Fix preservation of length/dimensions unit for racks and cables
* [#2648](https://github.com/digitalocean/netbox/issues/2648) - Include the `connection_status` field in nested represenations of connectable device components
* [#2649](https://github.com/digitalocean/netbox/issues/2649) - Add `connected_endpoint_type` to connectable device component API representations
## API Changes
* The `/extras/recent-activity/` endpoint (replaced by change logging in v2.4) has been removed
* The `rpc_client` field has been removed from dcim.Platform (see #2367)
* Introduced a new API endpoint for cables at `/dcim/cables/`
* New endpoints for front and rear pass-through ports (and their templates) in parallel with existing device components
* The fields `interface_connection` on Interface and `interface` on CircuitTermination have been replaced with `connected_endpoint` and `connection_status`
* A new `cable` field has been added to console, power, and interface components and to circuit terminations
* New fields for dcim.Rack: `status`, `asset_tag`, `outer_width`, `outer_depth`, `outer_unit`
* The following boolean filters on dcim.Device and dcim.DeviceType have been renamed:
* `is_console_server`: `console_server_ports`
* `is_pdu`: `power_outlets`
* `is_network_device`: `interfaces`
* The following new boolean filters have been introduced for dcim.Device and dcim.DeviceType:
* `console_ports`
* `power_ports`
* `pass_through_ports`
* The field `interface_ordering` has been removed from the DeviceType serializer
* Added a `description` field to the CircuitTermination serializer
* Added `ipaddress_count` to InterfaceSerializer to show the count of assigned IP addresses for each interface
* The `available-prefixes` and `available-ips` IPAM endpoints now return an HTTP 204 response instead of HTTP 400 when no new objects can be created
* Filtering on null values now uses the string `null` instead of zero
---
v2.4.9 (2018-12-07)
## Enhancements

View File

@ -16,8 +16,6 @@ or join us in the #netbox Slack channel on [NetworkToCode](https://networktocode
### Build Status
NetBox is built against both Python 2.7 and 3.5. Python 3.5 or higher is strongly recommended.
| | status |
|-------------|------------|
| **master** | [![Build Status](https://travis-ci.org/digitalocean/netbox.svg?branch=master)](https://travis-ci.org/digitalocean/netbox) |

View File

@ -1,25 +1,72 @@
# django-filter-1.1.0 breaks with Django-2.1
Django>=1.11,<2.1
# The Python web framework on which NetBox is built
# https://github.com/django/django
Django
# Django middleware which permits cross-domain API requests
# https://github.com/OttoYiu/django-cors-headers
django-cors-headers
# Runtime UI tool for debugging Django
# https://github.com/jazzband/django-debug-toolbar
django-debug-toolbar
# django-filter-2.0.0 drops Python 2 support (blocked by #2000)
django-filter==1.1.0
# Library for writing reusable URL query filters
# https://github.com/carltongibson/django-filter
django-filter
# Modified Preorder Tree Traversal (recursive nesting of objects)
# https://github.com/django-mptt/django-mptt
django-mptt
# Abstraction models for rendering and paginating HTML tables
# https://github.com/jieter/django-tables2
django-tables2
# User-defined tags for objects
# https://github.com/alex/django-taggit
django-taggit
# A Django REST Framework serializer which represents tags
# https://github.com/glemmaPaul/django-taggit-serializer
django-taggit-serializer
# A Django field for representing time zones
# https://github.com/mfogel/django-timezone-field/
django-timezone-field
# https://github.com/encode/django-rest-framework/issues/6053
djangorestframework==3.8.1
# A REST API framework for Django projects
# https://github.com/encode/django-rest-framework
djangorestframework
# Swagger/OpenAPI schema generation for REST APIs
# https://github.com/axnsan12/drf-yasg
drf-yasg[validation]
# Python interface to the graphviz graph rendering utility
# https://github.com/xflr6/graphviz
graphviz
Markdown
natsort
ncclient
# Simple markup language for rendering HTML
# https://github.com/Python-Markdown/markdown
# py-gfm requires Markdown<3.0
Markdown<3.0
# Library for manipulating IP prefixes and addresses
# https://github.com/drkjam/netaddr
netaddr
paramiko
# Fork of PIL (Python Imaging Library) for image processing
# https://github.com/python-pillow/Pillow
Pillow
# PostgreSQL database adapter for Python
# https://github.com/psycopg/psycopg2
psycopg2-binary
# GitHub-flavored Markdown extensions
# https://github.com/zopieux/py-gfm
py-gfm
# Extensive cryptographic library (fork of pycrypto)
# https://github.com/Legrandin/pycryptodome
pycryptodome
xmltodict

View File

@ -9,7 +9,7 @@ This will launch a customized version of [the built-in Django shell](https://doc
```
$ ./manage.py nbshell
### NetBox interactive shell (jstretch-laptop)
### Python 2.7.6 | Django 1.11.3 | NetBox 2.1.0-dev
### Python 3.5.2 | Django 2.0.8 | NetBox 2.4.3
### lsmodels() will show available models. Use help(<model>) for more info.
```

View File

@ -133,6 +133,14 @@ Setting this to True will permit only authenticated users to access any part of
---
## LOGIN_TIMEOUT
Default: 1209600 seconds (14 days)
The liftetime (in seconds) of the authentication cookie issued to a NetBox user upon login.
---
## MAINTENANCE_MODE
Default: False
@ -223,6 +231,14 @@ The file path to the location where custom reports will be kept. By default, thi
---
## SESSION_FILE_PATH
Default: None
Session data is used to track authenticated users when they access NetBox. By default, NetBox stores session data in the PostgreSQL database. However, this inhibits authentication to a standby instance of NetBox without write access to the database. Alternatively, a local file path may be specified here and NetBox will store session data as files instead of using the database. Note that the user as which NetBox runs must have read and write permissions to this path.
---
## TIME_ZONE
Default: UTC

View File

@ -25,7 +25,7 @@ Circuit types are fully customizable.
A circuit may have one or two terminations, annotated as the "A" and "Z" sides of the circuit. A single-termination circuit can be used when you don't know (or care) about the far end of a circuit (for example, an Internet access circuit which connects to a transit provider). A dual-termination circuit is useful for tracking circuits which connect two sites.
Each circuit termination is tied to a site, and optionally to a specific device and interface within that site. Each termination can be assigned a separate downstream and upstream speed independent from one another. Fields are also available to track cross-connect and patch panel details.
Each circuit termination is tied to a site, and may optionally be connected via a cable to a specific device interface or pass-through port. Each termination can be assigned a separate downstream and upstream speed independent from one another. Fields are also available to track cross-connect and patch panel details.
!!! note
A circuit represents a physical link, and cannot have more than two endpoints. When modeling a multi-point topology, each leg of the topology must be defined as a discrete circuit.

View File

@ -4,12 +4,6 @@ A device type represents a particular make and model of hardware that exists in
Device types are instantiated as devices installed within racks. For example, you might define a device type to represent a Juniper EX4300-48T network switch with 48 Ethernet interfaces. You can then create multiple devices of this type named "switch1," "switch2," and so on. Each device will inherit the components (such as interfaces) of its device type at the time of creation. (However, changes made to a device type will **not** apply to instances of that device type retroactively.)
The device type model includes three flags which inform what type of components may be added to it:
* `is_console_server`: This device type has console server ports
* `is_pdu`: This device type has power outlets
* `is_network_device`: This device type has network interfaces
Some devices house child devices which share physical resources, like space and power, but which functional independently from one another. A common example of this is blade server chassis. Each device type is designated as one of the following:
* A parent device (which has device bays)
@ -32,6 +26,8 @@ Each device type is assigned a number of component templates which define the ph
* Power ports
* Power outlets
* Network interfaces
* Front ports
* Rear ports
* Device bays (which house child devices)
Whenever a new device is created, its components are automatically created per the templates assigned to its device type. For example, a Juniper EX4300-48T device type might have the following component templates defined:
@ -56,32 +52,28 @@ When assigning a multi-U device to a rack, it is considered to be mounted in the
A device is said to be full depth if its installation on one rack face prevents the installation of any other device on the opposite face within the same rack unit(s). This could be either because the device is physically too deep to allow a device behind it, or because the installation of an opposing device would impede airflow.
## Device Roles
## Device Components
Devices can be organized by functional roles. These roles are fully customizable. For example, you might create roles for core switches, distribution switches, and access switches.
---
# Device Components
There are six types of device components which comprise all of the interconnection logic with NetBox:
There are eight types of device components which comprise all of the interconnection logic with NetBox:
* Console ports
* Console server ports
* Power ports
* Power outlets
* Network interfaces
* Front ports
* Rear ports
* Device bays
## Console
### Console
Console ports connect only to console server ports. Console connections can be marked as either *planned* or *connected*.
## Power
### Power
Power ports connect only to power outlets. Power connections can be marked as either *planned* or *connected*.
## Interfaces
### Interfaces
Interfaces connect to one another in a symmetric manner: If interface A connects to interface B, interface B therefore connects to interface A. Each type of connection can be classified as either *planned* or *connected*.
@ -91,10 +83,20 @@ Each interface can also be enabled or disabled, and optionally designated as man
VLANs can be assigned to each interface as either tagged or untagged. (An interface may have only one untagged VLAN.)
## Device Bays
### Pass-through Ports
Pass-through ports are used to model physical terminations which comprise part of a longer path, such as a cable terminated to a patch panel. Each front port maps to a position on a rear port. A 24-port UTP patch panel, for instance, would have 24 front ports and 24 rear ports. Although this relationship is typically one-to-one, a rear port may have multiple front ports mapped to it. This can be useful for modeling instances where multiple paths share a common cable (for example, six different fiber connections sharing a 12-strand MPO cable).
Pass-through ports can also be used to model "bump in the wire" devices, such as a media convertor or passive tap.
### Device Bays
Device bays represent the ability of a device to house child devices. For example, you might install four blade servers into a 2U chassis. The chassis would appear in the rack elevation as a 2U device with four device bays. Each server within it would be defined as a 0U device installed in one of the device bays. Child devices do not appear within rack elevations, but they are included in the "Non-Racked Devices" list within the rack view.
## Device Roles
Devices can be organized by functional roles. These roles are fully customizable. For example, you might create roles for core switches, distribution switches, and access switches.
---
# Platforms
@ -118,3 +120,25 @@ Inventory items represent hardware components installed within a device, such as
A virtual chassis represents a set of devices which share a single control plane: a stack of switches which are managed as a single device, for example. Each device in the virtual chassis is assigned a position and (optionally) a priority. Exactly one device is designated the virtual chassis master: This device will typically be assigned a name, secrets, services, and other attributes related to its management.
It's important to recognize the distinction between a virtual chassis and a chassis-based device. For instance, a virtual chassis is not used to model a chassis switch with removable line cards such as the Juniper EX9208, as its line cards are _not_ physically separate devices capable of operating independently.
---
# Cables
A cable represents a physical connection between two termination points, such as between a console port and a patch panel port, or between two network interfaces. Cables can be traced through pass-through ports to form a complete path between two endpoints. In the example below, three individual cables comprise a path between the two connected endpoints.
```
|<------------------------------------------ Cable Path ------------------------------------------->|
Device A Patch Panel A Patch Panel B Device B
+-----------+ +-------------+ +-------------+ +-----------+
| Interface | --- Cable --- | Front Port | | Front Port | --- Cable --- | Interface |
+-----------+ +-------------+ +-------------+ +-----------+
+-------------+ +-------------+
| Rear Port | --- Cable --- | Rear Port |
+-------------+ +-------------+
```
All connections between device components in NetBox are represented using cables. However, defining the actual cable plant is optional: Components can be be directly connected using cables with no type or other attributes assigned.
Cables are also used to associated ports and interfaces with circuit terminations. To do this, first create the circuit termination, then navigate the desired component and connect a cable between the two.

View File

@ -64,13 +64,6 @@ Once the new code is in place, run the upgrade script (which may need to be run
# ./upgrade.sh
```
!!! warning
The upgrade script will prefer Python3 and pip3 if both executables are available. To force it to use Python2 and pip, use the `-2` argument as below. Note that Python 2 will no longer be supported in NetBox v2.5.
```no-highlight
# ./upgrade.sh -2
```
This script:
* Installs or upgrades any new required Python packages

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

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