mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-14 01:41:22 -06:00
Merge v2.5 work
This commit is contained in:
commit
65edffea63
@ -5,7 +5,6 @@ addons:
|
|||||||
postgresql: "9.4"
|
postgresql: "9.4"
|
||||||
language: python
|
language: python
|
||||||
python:
|
python:
|
||||||
- "2.7"
|
|
||||||
- "3.5"
|
- "3.5"
|
||||||
install:
|
install:
|
||||||
- pip install -r requirements.txt
|
- pip install -r requirements.txt
|
||||||
|
79
CHANGELOG.md
79
CHANGELOG.md
@ -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)
|
v2.4.9 (2018-12-07)
|
||||||
|
|
||||||
## Enhancements
|
## Enhancements
|
||||||
|
@ -16,8 +16,6 @@ or join us in the #netbox Slack channel on [NetworkToCode](https://networktocode
|
|||||||
|
|
||||||
### Build Status
|
### Build Status
|
||||||
|
|
||||||
NetBox is built against both Python 2.7 and 3.5. Python 3.5 or higher is strongly recommended.
|
|
||||||
|
|
||||||
| | status |
|
| | status |
|
||||||
|-------------|------------|
|
|-------------|------------|
|
||||||
| **master** | [](https://travis-ci.org/digitalocean/netbox) |
|
| **master** | [](https://travis-ci.org/digitalocean/netbox) |
|
||||||
|
@ -1,25 +1,72 @@
|
|||||||
# django-filter-1.1.0 breaks with Django-2.1
|
# The Python web framework on which NetBox is built
|
||||||
Django>=1.11,<2.1
|
# https://github.com/django/django
|
||||||
|
Django
|
||||||
|
|
||||||
|
# Django middleware which permits cross-domain API requests
|
||||||
|
# https://github.com/OttoYiu/django-cors-headers
|
||||||
django-cors-headers
|
django-cors-headers
|
||||||
|
|
||||||
|
# Runtime UI tool for debugging Django
|
||||||
|
# https://github.com/jazzband/django-debug-toolbar
|
||||||
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
|
django-mptt
|
||||||
|
|
||||||
|
# Abstraction models for rendering and paginating HTML tables
|
||||||
|
# https://github.com/jieter/django-tables2
|
||||||
django-tables2
|
django-tables2
|
||||||
|
|
||||||
|
# User-defined tags for objects
|
||||||
|
# https://github.com/alex/django-taggit
|
||||||
django-taggit
|
django-taggit
|
||||||
|
|
||||||
|
# A Django REST Framework serializer which represents tags
|
||||||
|
# https://github.com/glemmaPaul/django-taggit-serializer
|
||||||
django-taggit-serializer
|
django-taggit-serializer
|
||||||
|
|
||||||
|
# A Django field for representing time zones
|
||||||
|
# https://github.com/mfogel/django-timezone-field/
|
||||||
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]
|
drf-yasg[validation]
|
||||||
|
|
||||||
|
# Python interface to the graphviz graph rendering utility
|
||||||
|
# https://github.com/xflr6/graphviz
|
||||||
graphviz
|
graphviz
|
||||||
Markdown
|
|
||||||
natsort
|
# Simple markup language for rendering HTML
|
||||||
ncclient
|
# 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
|
netaddr
|
||||||
paramiko
|
|
||||||
|
# Fork of PIL (Python Imaging Library) for image processing
|
||||||
|
# https://github.com/python-pillow/Pillow
|
||||||
Pillow
|
Pillow
|
||||||
|
|
||||||
|
# PostgreSQL database adapter for Python
|
||||||
|
# https://github.com/psycopg/psycopg2
|
||||||
psycopg2-binary
|
psycopg2-binary
|
||||||
|
|
||||||
|
# GitHub-flavored Markdown extensions
|
||||||
|
# https://github.com/zopieux/py-gfm
|
||||||
py-gfm
|
py-gfm
|
||||||
|
|
||||||
|
# Extensive cryptographic library (fork of pycrypto)
|
||||||
|
# https://github.com/Legrandin/pycryptodome
|
||||||
pycryptodome
|
pycryptodome
|
||||||
xmltodict
|
|
||||||
|
@ -9,7 +9,7 @@ This will launch a customized version of [the built-in Django shell](https://doc
|
|||||||
```
|
```
|
||||||
$ ./manage.py nbshell
|
$ ./manage.py nbshell
|
||||||
### NetBox interactive shell (jstretch-laptop)
|
### 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.
|
### lsmodels() will show available models. Use help(<model>) for more info.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -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
|
## MAINTENANCE_MODE
|
||||||
|
|
||||||
Default: False
|
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
|
## TIME_ZONE
|
||||||
|
|
||||||
Default: UTC
|
Default: UTC
|
||||||
|
@ -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.
|
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
|
!!! 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.
|
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.
|
||||||
|
@ -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.)
|
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:
|
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)
|
* 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 ports
|
||||||
* Power outlets
|
* Power outlets
|
||||||
* Network interfaces
|
* Network interfaces
|
||||||
|
* Front ports
|
||||||
|
* Rear ports
|
||||||
* Device bays (which house child devices)
|
* 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:
|
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.
|
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.
|
There are eight types of device components which comprise all of the interconnection logic with NetBox:
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# Device Components
|
|
||||||
|
|
||||||
There are six types of device components which comprise all of the interconnection logic with NetBox:
|
|
||||||
|
|
||||||
* Console ports
|
* Console ports
|
||||||
* Console server ports
|
* Console server ports
|
||||||
* Power ports
|
* Power ports
|
||||||
* Power outlets
|
* Power outlets
|
||||||
* Network interfaces
|
* Network interfaces
|
||||||
|
* Front ports
|
||||||
|
* Rear ports
|
||||||
* Device bays
|
* Device bays
|
||||||
|
|
||||||
## Console
|
### Console
|
||||||
|
|
||||||
Console ports connect only to console server ports. Console connections can be marked as either *planned* or *connected*.
|
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*.
|
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*.
|
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.)
|
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 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
|
# 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.
|
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.
|
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.
|
||||||
|
@ -64,13 +64,6 @@ Once the new code is in place, run the upgrade script (which may need to be run
|
|||||||
# ./upgrade.sh
|
# ./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:
|
This script:
|
||||||
|
|
||||||
* Installs or upgrades any new required Python packages
|
* Installs or upgrades any new required Python packages
|
||||||
|
52
netbox/circuits/api/nested_serializers.py
Normal file
52
netbox/circuits/api/nested_serializers.py
Normal 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']
|
@ -1,14 +1,13 @@
|
|||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from rest_framework import serializers
|
|
||||||
from taggit_serializer.serializers import TaggitSerializer, TagListSerializerField
|
from taggit_serializer.serializers import TaggitSerializer, TagListSerializerField
|
||||||
|
|
||||||
from circuits.constants import CIRCUIT_STATUS_CHOICES
|
from circuits.constants import CIRCUIT_STATUS_CHOICES
|
||||||
from circuits.models import Circuit, CircuitTermination, CircuitType, Provider
|
from circuits.models import Provider, Circuit, CircuitTermination, CircuitType
|
||||||
from dcim.api.serializers import NestedInterfaceSerializer, NestedSiteSerializer
|
from dcim.api.nested_serializers import NestedCableSerializer, NestedSiteSerializer
|
||||||
|
from dcim.api.serializers import ConnectedEndpointSerializer
|
||||||
from extras.api.customfields import CustomFieldModelSerializer
|
from extras.api.customfields import CustomFieldModelSerializer
|
||||||
from tenancy.api.serializers import NestedTenantSerializer
|
from tenancy.api.nested_serializers import NestedTenantSerializer
|
||||||
from utilities.api import ChoiceField, ValidatedModelSerializer, WritableNestedSerializer
|
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):
|
class CircuitTypeSerializer(ValidatedModelSerializer):
|
||||||
@ -45,18 +36,6 @@ class CircuitTypeSerializer(ValidatedModelSerializer):
|
|||||||
fields = ['id', 'name', 'slug']
|
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):
|
class CircuitSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
||||||
provider = NestedProviderSerializer()
|
provider = NestedProviderSerializer()
|
||||||
status = ChoiceField(choices=CIRCUIT_STATUS_CHOICES, required=False)
|
status = ChoiceField(choices=CIRCUIT_STATUS_CHOICES, required=False)
|
||||||
@ -72,25 +51,14 @@ class CircuitSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class NestedCircuitSerializer(WritableNestedSerializer):
|
class CircuitTerminationSerializer(ConnectedEndpointSerializer):
|
||||||
url = serializers.HyperlinkedIdentityField(view_name='circuits-api:circuit-detail')
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Circuit
|
|
||||||
fields = ['id', 'url', 'cid']
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Circuit Terminations
|
|
||||||
#
|
|
||||||
|
|
||||||
class CircuitTerminationSerializer(ValidatedModelSerializer):
|
|
||||||
circuit = NestedCircuitSerializer()
|
circuit = NestedCircuitSerializer()
|
||||||
site = NestedSiteSerializer()
|
site = NestedSiteSerializer()
|
||||||
interface = NestedInterfaceSerializer(required=False, allow_null=True)
|
cable = NestedCableSerializer(read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CircuitTermination
|
model = CircuitTermination
|
||||||
fields = [
|
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',
|
||||||
]
|
]
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from rest_framework import routers
|
from rest_framework import routers
|
||||||
|
|
||||||
from . import views
|
from . import views
|
||||||
@ -17,7 +15,7 @@ router = routers.DefaultRouter()
|
|||||||
router.APIRootView = CircuitsRootView
|
router.APIRootView = CircuitsRootView
|
||||||
|
|
||||||
# Field choices
|
# Field choices
|
||||||
router.register(r'_choices', views.CircuitsFieldChoicesViewSet, base_name='field-choice')
|
router.register(r'_choices', views.CircuitsFieldChoicesViewSet, basename='field-choice')
|
||||||
|
|
||||||
# Providers
|
# Providers
|
||||||
router.register(r'providers', views.ProviderViewSet)
|
router.register(r'providers', views.ProviderViewSet)
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
@ -31,7 +29,7 @@ class CircuitsFieldChoicesViewSet(FieldChoicesViewSet):
|
|||||||
class ProviderViewSet(CustomFieldModelViewSet):
|
class ProviderViewSet(CustomFieldModelViewSet):
|
||||||
queryset = Provider.objects.prefetch_related('tags')
|
queryset = Provider.objects.prefetch_related('tags')
|
||||||
serializer_class = serializers.ProviderSerializer
|
serializer_class = serializers.ProviderSerializer
|
||||||
filter_class = filters.ProviderFilter
|
filterset_class = filters.ProviderFilter
|
||||||
|
|
||||||
@action(detail=True)
|
@action(detail=True)
|
||||||
def graphs(self, request, pk=None):
|
def graphs(self, request, pk=None):
|
||||||
@ -51,7 +49,7 @@ class ProviderViewSet(CustomFieldModelViewSet):
|
|||||||
class CircuitTypeViewSet(ModelViewSet):
|
class CircuitTypeViewSet(ModelViewSet):
|
||||||
queryset = CircuitType.objects.all()
|
queryset = CircuitType.objects.all()
|
||||||
serializer_class = serializers.CircuitTypeSerializer
|
serializer_class = serializers.CircuitTypeSerializer
|
||||||
filter_class = filters.CircuitTypeFilter
|
filterset_class = filters.CircuitTypeFilter
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -61,7 +59,7 @@ class CircuitTypeViewSet(ModelViewSet):
|
|||||||
class CircuitViewSet(CustomFieldModelViewSet):
|
class CircuitViewSet(CustomFieldModelViewSet):
|
||||||
queryset = Circuit.objects.select_related('type', 'tenant', 'provider').prefetch_related('tags')
|
queryset = Circuit.objects.select_related('type', 'tenant', 'provider').prefetch_related('tags')
|
||||||
serializer_class = serializers.CircuitSerializer
|
serializer_class = serializers.CircuitSerializer
|
||||||
filter_class = filters.CircuitFilter
|
filterset_class = filters.CircuitFilter
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -69,6 +67,8 @@ class CircuitViewSet(CustomFieldModelViewSet):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class CircuitTerminationViewSet(ModelViewSet):
|
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
|
serializer_class = serializers.CircuitTerminationSerializer
|
||||||
filter_class = filters.CircuitTerminationFilter
|
filterset_class = filters.CircuitTerminationFilter
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
|
|
||||||
# Circuit statuses
|
# Circuit statuses
|
||||||
CIRCUIT_STATUS_DEPROVISIONING = 0
|
CIRCUIT_STATUS_DEPROVISIONING = 0
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
import django_filters
|
import django_filters
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
|
||||||
@ -12,18 +10,21 @@ from .models import Provider, Circuit, CircuitTermination, CircuitType
|
|||||||
|
|
||||||
|
|
||||||
class ProviderFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
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(
|
q = django_filters.CharFilter(
|
||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
||||||
)
|
)
|
||||||
site_id = django_filters.ModelMultipleChoiceFilter(
|
site_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
name='circuits__terminations__site',
|
field_name='circuits__terminations__site',
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
label='Site',
|
label='Site',
|
||||||
)
|
)
|
||||||
site = django_filters.ModelMultipleChoiceFilter(
|
site = django_filters.ModelMultipleChoiceFilter(
|
||||||
name='circuits__terminations__site__slug',
|
field_name='circuits__terminations__site__slug',
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label='Site (slug)',
|
label='Site (slug)',
|
||||||
@ -54,7 +55,10 @@ class CircuitTypeFilter(django_filters.FilterSet):
|
|||||||
|
|
||||||
|
|
||||||
class CircuitFilter(CustomFieldFilterSet, 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(
|
q = django_filters.CharFilter(
|
||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
||||||
@ -64,7 +68,7 @@ class CircuitFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
label='Provider (ID)',
|
label='Provider (ID)',
|
||||||
)
|
)
|
||||||
provider = django_filters.ModelMultipleChoiceFilter(
|
provider = django_filters.ModelMultipleChoiceFilter(
|
||||||
name='provider__slug',
|
field_name='provider__slug',
|
||||||
queryset=Provider.objects.all(),
|
queryset=Provider.objects.all(),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label='Provider (slug)',
|
label='Provider (slug)',
|
||||||
@ -74,7 +78,7 @@ class CircuitFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
label='Circuit type (ID)',
|
label='Circuit type (ID)',
|
||||||
)
|
)
|
||||||
type = django_filters.ModelMultipleChoiceFilter(
|
type = django_filters.ModelMultipleChoiceFilter(
|
||||||
name='type__slug',
|
field_name='type__slug',
|
||||||
queryset=CircuitType.objects.all(),
|
queryset=CircuitType.objects.all(),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label='Circuit type (slug)',
|
label='Circuit type (slug)',
|
||||||
@ -88,18 +92,18 @@ class CircuitFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
label='Tenant (ID)',
|
label='Tenant (ID)',
|
||||||
)
|
)
|
||||||
tenant = django_filters.ModelMultipleChoiceFilter(
|
tenant = django_filters.ModelMultipleChoiceFilter(
|
||||||
name='tenant__slug',
|
field_name='tenant__slug',
|
||||||
queryset=Tenant.objects.all(),
|
queryset=Tenant.objects.all(),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label='Tenant (slug)',
|
label='Tenant (slug)',
|
||||||
)
|
)
|
||||||
site_id = django_filters.ModelMultipleChoiceFilter(
|
site_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
name='terminations__site',
|
field_name='terminations__site',
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
label='Site (ID)',
|
label='Site (ID)',
|
||||||
)
|
)
|
||||||
site = django_filters.ModelMultipleChoiceFilter(
|
site = django_filters.ModelMultipleChoiceFilter(
|
||||||
name='terminations__site__slug',
|
field_name='terminations__site__slug',
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label='Site (slug)',
|
label='Site (slug)',
|
||||||
@ -117,6 +121,7 @@ class CircuitFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
Q(cid__icontains=value) |
|
Q(cid__icontains=value) |
|
||||||
Q(terminations__xconnect_id__icontains=value) |
|
Q(terminations__xconnect_id__icontains=value) |
|
||||||
Q(terminations__pp_info__icontains=value) |
|
Q(terminations__pp_info__icontains=value) |
|
||||||
|
Q(terminations__description__icontains=value) |
|
||||||
Q(description__icontains=value) |
|
Q(description__icontains=value) |
|
||||||
Q(comments__icontains=value)
|
Q(comments__icontains=value)
|
||||||
).distinct()
|
).distinct()
|
||||||
@ -136,7 +141,7 @@ class CircuitTerminationFilter(django_filters.FilterSet):
|
|||||||
label='Site (ID)',
|
label='Site (ID)',
|
||||||
)
|
)
|
||||||
site = django_filters.ModelMultipleChoiceFilter(
|
site = django_filters.ModelMultipleChoiceFilter(
|
||||||
name='site__slug',
|
field_name='site__slug',
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label='Site (slug)',
|
label='Site (slug)',
|
||||||
@ -152,5 +157,6 @@ class CircuitTerminationFilter(django_filters.FilterSet):
|
|||||||
return queryset.filter(
|
return queryset.filter(
|
||||||
Q(circuit__cid__icontains=value) |
|
Q(circuit__cid__icontains=value) |
|
||||||
Q(xconnect_id__icontains=value) |
|
Q(xconnect_id__icontains=value) |
|
||||||
Q(pp_info__icontains=value)
|
Q(pp_info__icontains=value) |
|
||||||
|
Q(description__icontains=value)
|
||||||
).distinct()
|
).distinct()
|
||||||
|
@ -1,16 +1,14 @@
|
|||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.db.models import Count
|
from django.db.models import Count
|
||||||
from taggit.forms import TagField
|
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 extras.forms import AddRemoveTagsForm, CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm
|
||||||
from tenancy.forms import TenancyForm
|
from tenancy.forms import TenancyForm
|
||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
from utilities.forms import (
|
from utilities.forms import (
|
||||||
AnnotatedMultipleChoiceField, APISelect, add_blank_choice, BootstrapMixin, ChainedFieldsMixin,
|
AnnotatedMultipleChoiceField, add_blank_choice, BootstrapMixin, CommentField, CSVChoiceField, FilterChoiceField,
|
||||||
ChainedModelChoiceField, CommentField, CSVChoiceField, FilterChoiceField, SmallTextarea, SlugField,
|
SmallTextarea, SlugField,
|
||||||
)
|
)
|
||||||
from .constants import CIRCUIT_STATUS_CHOICES
|
from .constants import CIRCUIT_STATUS_CHOICES
|
||||||
from .models import Circuit, CircuitTermination, CircuitType, Provider
|
from .models import Circuit, CircuitTermination, CircuitType, Provider
|
||||||
@ -23,14 +21,22 @@ from .models import Circuit, CircuitTermination, CircuitType, Provider
|
|||||||
class ProviderForm(BootstrapMixin, CustomFieldForm):
|
class ProviderForm(BootstrapMixin, CustomFieldForm):
|
||||||
slug = SlugField()
|
slug = SlugField()
|
||||||
comments = CommentField()
|
comments = CommentField()
|
||||||
tags = TagField(required=False)
|
tags = TagField(
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Provider
|
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 = {
|
widgets = {
|
||||||
'noc_contact': SmallTextarea(attrs={'rows': 5}),
|
'noc_contact': SmallTextarea(
|
||||||
'admin_contact': SmallTextarea(attrs={'rows': 5}),
|
attrs={'rows': 5}
|
||||||
|
),
|
||||||
|
'admin_contact': SmallTextarea(
|
||||||
|
attrs={'rows': 5}
|
||||||
|
),
|
||||||
}
|
}
|
||||||
help_texts = {
|
help_texts = {
|
||||||
'name': "Full name of the provider",
|
'name': "Full name of the provider",
|
||||||
@ -56,23 +62,57 @@ class ProviderCSVForm(forms.ModelForm):
|
|||||||
|
|
||||||
|
|
||||||
class ProviderBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm):
|
class ProviderBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm):
|
||||||
pk = forms.ModelMultipleChoiceField(queryset=Provider.objects.all(), widget=forms.MultipleHiddenInput)
|
pk = forms.ModelMultipleChoiceField(
|
||||||
asn = forms.IntegerField(required=False, label='ASN')
|
queryset=Provider.objects.all(),
|
||||||
account = forms.CharField(max_length=30, required=False, label='Account number')
|
widget=forms.MultipleHiddenInput
|
||||||
portal_url = forms.URLField(required=False, label='Portal')
|
)
|
||||||
noc_contact = forms.CharField(required=False, widget=SmallTextarea, label='NOC contact')
|
asn = forms.IntegerField(
|
||||||
admin_contact = forms.CharField(required=False, widget=SmallTextarea, label='Admin contact')
|
required=False,
|
||||||
comments = CommentField(widget=SmallTextarea)
|
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:
|
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):
|
class ProviderFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
||||||
model = Provider
|
model = Provider
|
||||||
q = forms.CharField(required=False, label='Search')
|
q = forms.CharField(
|
||||||
site = FilterChoiceField(queryset=Site.objects.all(), to_field_name='slug')
|
required=False,
|
||||||
asn = forms.IntegerField(required=False, label='ASN')
|
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:
|
class Meta:
|
||||||
model = CircuitType
|
model = CircuitType
|
||||||
fields = ['name', 'slug']
|
fields = [
|
||||||
|
'name', 'slug',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class CircuitTypeCSVForm(forms.ModelForm):
|
class CircuitTypeCSVForm(forms.ModelForm):
|
||||||
@ -104,7 +146,9 @@ class CircuitTypeCSVForm(forms.ModelForm):
|
|||||||
|
|
||||||
class CircuitForm(BootstrapMixin, TenancyForm, CustomFieldForm):
|
class CircuitForm(BootstrapMixin, TenancyForm, CustomFieldForm):
|
||||||
comments = CommentField()
|
comments = CommentField()
|
||||||
tags = TagField(required=False)
|
tags = TagField(
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Circuit
|
model = Circuit
|
||||||
@ -159,28 +203,61 @@ class CircuitCSVForm(forms.ModelForm):
|
|||||||
|
|
||||||
|
|
||||||
class CircuitBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm):
|
class CircuitBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditForm):
|
||||||
pk = forms.ModelMultipleChoiceField(queryset=Circuit.objects.all(), widget=forms.MultipleHiddenInput)
|
pk = forms.ModelMultipleChoiceField(
|
||||||
type = forms.ModelChoiceField(queryset=CircuitType.objects.all(), required=False)
|
queryset=Circuit.objects.all(),
|
||||||
provider = forms.ModelChoiceField(queryset=Provider.objects.all(), required=False)
|
widget=forms.MultipleHiddenInput
|
||||||
status = forms.ChoiceField(choices=add_blank_choice(CIRCUIT_STATUS_CHOICES), required=False, initial='')
|
)
|
||||||
tenant = forms.ModelChoiceField(queryset=Tenant.objects.all(), required=False)
|
type = forms.ModelChoiceField(
|
||||||
commit_rate = forms.IntegerField(required=False, label='Commit rate (Kbps)')
|
queryset=CircuitType.objects.all(),
|
||||||
description = forms.CharField(max_length=100, required=False)
|
required=False
|
||||||
comments = CommentField(widget=SmallTextarea)
|
)
|
||||||
|
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:
|
class Meta:
|
||||||
nullable_fields = ['tenant', 'commit_rate', 'description', 'comments']
|
nullable_fields = [
|
||||||
|
'tenant', 'commit_rate', 'description', 'comments',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class CircuitFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
class CircuitFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
||||||
model = Circuit
|
model = Circuit
|
||||||
q = forms.CharField(required=False, label='Search')
|
q = forms.CharField(
|
||||||
|
required=False,
|
||||||
|
label='Search'
|
||||||
|
)
|
||||||
type = FilterChoiceField(
|
type = FilterChoiceField(
|
||||||
queryset=CircuitType.objects.annotate(filter_count=Count('circuits')),
|
queryset=CircuitType.objects.annotate(
|
||||||
|
filter_count=Count('circuits')
|
||||||
|
),
|
||||||
to_field_name='slug'
|
to_field_name='slug'
|
||||||
)
|
)
|
||||||
provider = FilterChoiceField(
|
provider = FilterChoiceField(
|
||||||
queryset=Provider.objects.annotate(filter_count=Count('circuits')),
|
queryset=Provider.objects.annotate(
|
||||||
|
filter_count=Count('circuits')
|
||||||
|
),
|
||||||
to_field_name='slug'
|
to_field_name='slug'
|
||||||
)
|
)
|
||||||
status = AnnotatedMultipleChoiceField(
|
status = AnnotatedMultipleChoiceField(
|
||||||
@ -190,74 +267,35 @@ class CircuitFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
tenant = FilterChoiceField(
|
tenant = FilterChoiceField(
|
||||||
queryset=Tenant.objects.annotate(filter_count=Count('circuits')),
|
queryset=Tenant.objects.annotate(
|
||||||
|
filter_count=Count('circuits')
|
||||||
|
),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
null_label='-- None --'
|
null_label='-- None --'
|
||||||
)
|
)
|
||||||
site = FilterChoiceField(
|
site = FilterChoiceField(
|
||||||
queryset=Site.objects.annotate(filter_count=Count('circuit_terminations')),
|
queryset=Site.objects.annotate(
|
||||||
|
filter_count=Count('circuit_terminations')
|
||||||
|
),
|
||||||
to_field_name='slug'
|
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
|
# Circuit terminations
|
||||||
#
|
#
|
||||||
|
|
||||||
class CircuitTerminationForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelForm):
|
class CircuitTerminationForm(BootstrapMixin, 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 Meta:
|
class Meta:
|
||||||
model = CircuitTermination
|
model = CircuitTermination
|
||||||
fields = [
|
fields = [
|
||||||
'term_side', 'site', 'rack', 'device', 'interface', 'port_speed', 'upstream_speed', 'xconnect_id',
|
'term_side', 'site', 'port_speed', 'upstream_speed', 'xconnect_id', 'pp_info', 'description',
|
||||||
'pp_info',
|
|
||||||
]
|
]
|
||||||
help_texts = {
|
help_texts = {
|
||||||
'port_speed': "Physical circuit speed",
|
'port_speed': "Physical circuit speed",
|
||||||
@ -267,25 +305,3 @@ class CircuitTerminationForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelForm
|
|||||||
widgets = {
|
widgets = {
|
||||||
'term_side': forms.HiddenInput(),
|
'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'),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.9.7 on 2016-06-22 18:21
|
# Generated by Django 1.9.7 on 2016-06-22 18:21
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.11.14 on 2018-07-31 02:25
|
# Generated by Django 1.11.14 on 2018-07-31 02:25
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
import dcim.fields
|
import dcim.fields
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.9.7 on 2016-06-22 18:21
|
# Generated by Django 1.9.7 on 2016-06-22 18:21
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.9.7 on 2016-07-13 19:24
|
# Generated by Django 1.9.7 on 2016-07-13 19:24
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
import dcim.fields
|
import dcim.fields
|
||||||
from django.db import migrations
|
from django.db import migrations
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.9.8 on 2016-07-26 21:59
|
# Generated by Django 1.9.8 on 2016-07-26 21:59
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.9.8 on 2016-08-08 20:24
|
# Generated by Django 1.9.8 on 2016-08-08 20:24
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.10 on 2016-12-13 16:30
|
# Generated by Django 1.10 on 2016-12-13 16:30
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.10.4 on 2017-01-17 20:08
|
# Generated by Django 1.10.4 on 2017-01-17 20:08
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.11 on 2017-04-19 17:17
|
# Generated by Django 1.11 on 2017-04-19 17:17
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.11 on 2017-05-24 15:34
|
# Generated by Django 1.11 on 2017-05-24 15:34
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
import dcim.fields
|
import dcim.fields
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.11.9 on 2018-02-06 18:48
|
# Generated by Django 1.11.9 on 2018-02-06 18:48
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.11.12 on 2018-05-22 19:04
|
# Generated by Django 1.11.12 on 2018-05-22 19:04
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations
|
from django.db import migrations
|
||||||
import taggit.managers
|
import taggit.managers
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.11.12 on 2018-06-13 17:14
|
# Generated by Django 1.11.12 on 2018-06-13 17:14
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
89
netbox/circuits/migrations/0013_cables.py
Normal file
89
netbox/circuits/migrations/0013_cables.py
Normal 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',
|
||||||
|
),
|
||||||
|
]
|
@ -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),
|
||||||
|
),
|
||||||
|
]
|
@ -1,20 +1,17 @@
|
|||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.contrib.contenttypes.fields import GenericRelation
|
from django.contrib.contenttypes.fields import GenericRelation
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.encoding import python_2_unicode_compatible
|
|
||||||
from taggit.managers import TaggableManager
|
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.fields import ASNField
|
||||||
|
from dcim.models import CableTermination
|
||||||
from extras.models import CustomFieldModel, ObjectChange
|
from extras.models import CustomFieldModel, ObjectChange
|
||||||
from utilities.models import ChangeLoggedModel
|
from utilities.models import ChangeLoggedModel
|
||||||
from utilities.utils import serialize_object
|
from utilities.utils import serialize_object
|
||||||
from .constants import CIRCUIT_STATUS_ACTIVE, CIRCUIT_STATUS_CHOICES, TERM_SIDE_CHOICES
|
from .constants import CIRCUIT_STATUS_ACTIVE, CIRCUIT_STATUS_CHOICES, TERM_SIDE_CHOICES
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
|
||||||
class Provider(ChangeLoggedModel, CustomFieldModel):
|
class Provider(ChangeLoggedModel, CustomFieldModel):
|
||||||
"""
|
"""
|
||||||
Each Circuit belongs to a Provider. This is usually a telecommunications company or similar organization. This model
|
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):
|
class CircuitType(ChangeLoggedModel):
|
||||||
"""
|
"""
|
||||||
Circuits can be organized by their functional role. For example, a user might wish to define CircuitTypes named
|
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):
|
class Circuit(ChangeLoggedModel, CustomFieldModel):
|
||||||
"""
|
"""
|
||||||
A communications circuit connects two points. Each Circuit belongs to a Provider; Providers may have multiple
|
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
|
circuits. Each circuit is also assigned a CircuitType and a Site. Circuit port speed and commit rate are measured
|
||||||
interface, but this is not required. Circuit port speed and commit rate are measured in Kbps.
|
in Kbps.
|
||||||
"""
|
"""
|
||||||
cid = models.CharField(
|
cid = models.CharField(
|
||||||
max_length=50,
|
max_length=50,
|
||||||
@ -217,8 +212,7 @@ class Circuit(ChangeLoggedModel, CustomFieldModel):
|
|||||||
return self._get_termination('Z')
|
return self._get_termination('Z')
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
class CircuitTermination(CableTermination):
|
||||||
class CircuitTermination(models.Model):
|
|
||||||
circuit = models.ForeignKey(
|
circuit = models.ForeignKey(
|
||||||
to='circuits.Circuit',
|
to='circuits.Circuit',
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
@ -234,13 +228,17 @@ class CircuitTermination(models.Model):
|
|||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
related_name='circuit_terminations'
|
related_name='circuit_terminations'
|
||||||
)
|
)
|
||||||
interface = models.OneToOneField(
|
connected_endpoint = models.OneToOneField(
|
||||||
to='dcim.Interface',
|
to='dcim.Interface',
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.SET_NULL,
|
||||||
related_name='circuit_termination',
|
related_name='+',
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
|
connection_status = models.NullBooleanField(
|
||||||
|
choices=CONNECTION_STATUS_CHOICES,
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
port_speed = models.PositiveIntegerField(
|
port_speed = models.PositiveIntegerField(
|
||||||
verbose_name='Port speed (Kbps)'
|
verbose_name='Port speed (Kbps)'
|
||||||
)
|
)
|
||||||
@ -260,13 +258,17 @@ class CircuitTermination(models.Model):
|
|||||||
blank=True,
|
blank=True,
|
||||||
verbose_name='Patch panel/port(s)'
|
verbose_name='Patch panel/port(s)'
|
||||||
)
|
)
|
||||||
|
description = models.CharField(
|
||||||
|
max_length=100,
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['circuit', 'term_side']
|
ordering = ['circuit', 'term_side']
|
||||||
unique_together = ['circuit', 'term_side']
|
unique_together = ['circuit', 'term_side']
|
||||||
|
|
||||||
def __str__(self):
|
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):
|
def log_change(self, user, request_id, action):
|
||||||
"""
|
"""
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db.models.signals import post_delete, post_save
|
from django.db.models.signals import post_delete, post_save
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
import django_tables2 as tables
|
import django_tables2 as tables
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django_tables2.utils import Accessor
|
from django_tables2.utils import Accessor
|
||||||
@ -25,12 +23,6 @@ STATUS_LABEL = """
|
|||||||
class CircuitTerminationColumn(tables.Column):
|
class CircuitTerminationColumn(tables.Column):
|
||||||
|
|
||||||
def render(self, value):
|
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(
|
return mark_safe('<a href="{}">{}</a>'.format(
|
||||||
value.site.get_absolute_url(),
|
value.site.get_absolute_url(),
|
||||||
value.site
|
value.site
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
|
|
||||||
@ -15,7 +13,7 @@ class ProviderTest(APITestCase):
|
|||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
||||||
super(ProviderTest, self).setUp()
|
super().setUp()
|
||||||
|
|
||||||
self.provider1 = Provider.objects.create(name='Test Provider 1', slug='test-provider-1')
|
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')
|
self.provider2 = Provider.objects.create(name='Test Provider 2', slug='test-provider-2')
|
||||||
@ -137,7 +135,7 @@ class CircuitTypeTest(APITestCase):
|
|||||||
|
|
||||||
def setUp(self):
|
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.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')
|
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):
|
def setUp(self):
|
||||||
|
|
||||||
super(CircuitTest, self).setUp()
|
super().setUp()
|
||||||
|
|
||||||
self.provider1 = Provider.objects.create(name='Test Provider 1', slug='test-provider-1')
|
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')
|
self.provider2 = Provider.objects.create(name='Test Provider 2', slug='test-provider-2')
|
||||||
@ -328,46 +326,26 @@ class CircuitTerminationTest(APITestCase):
|
|||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
||||||
super(CircuitTerminationTest, self).setUp()
|
super().setUp()
|
||||||
|
|
||||||
self.site1 = Site.objects.create(name='Test Site 1', slug='test-site-1')
|
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')
|
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')
|
provider = Provider.objects.create(name='Test Provider', slug='test-provider')
|
||||||
circuittype = CircuitType.objects.create(name='Test Circuit Type', slug='test-circuit-type')
|
circuittype = CircuitType.objects.create(name='Test Circuit Type', slug='test-circuit-type')
|
||||||
self.circuit1 = Circuit.objects.create(cid='TEST0001', provider=provider, type=circuittype)
|
self.circuit1 = Circuit.objects.create(cid='TEST0001', provider=provider, type=circuittype)
|
||||||
self.circuit2 = Circuit.objects.create(cid='TEST0002', 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.circuit3 = Circuit.objects.create(cid='TEST0003', provider=provider, type=circuittype)
|
||||||
self.circuittermination1 = CircuitTermination.objects.create(
|
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(
|
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(
|
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(
|
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):
|
def test_get_circuittermination(self):
|
||||||
@ -390,7 +368,6 @@ class CircuitTerminationTest(APITestCase):
|
|||||||
'circuit': self.circuit3.pk,
|
'circuit': self.circuit3.pk,
|
||||||
'term_side': TERM_SIDE_A,
|
'term_side': TERM_SIDE_A,
|
||||||
'site': self.site1.pk,
|
'site': self.site1.pk,
|
||||||
'interface': self.interface5.pk,
|
|
||||||
'port_speed': 1000000,
|
'port_speed': 1000000,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -403,20 +380,18 @@ class CircuitTerminationTest(APITestCase):
|
|||||||
self.assertEqual(circuittermination4.circuit_id, data['circuit'])
|
self.assertEqual(circuittermination4.circuit_id, data['circuit'])
|
||||||
self.assertEqual(circuittermination4.term_side, data['term_side'])
|
self.assertEqual(circuittermination4.term_side, data['term_side'])
|
||||||
self.assertEqual(circuittermination4.site_id, data['site'])
|
self.assertEqual(circuittermination4.site_id, data['site'])
|
||||||
self.assertEqual(circuittermination4.interface_id, data['interface'])
|
|
||||||
self.assertEqual(circuittermination4.port_speed, data['port_speed'])
|
self.assertEqual(circuittermination4.port_speed, data['port_speed'])
|
||||||
|
|
||||||
def test_update_circuittermination(self):
|
def test_update_circuittermination(self):
|
||||||
|
|
||||||
circuittermination5 = CircuitTermination.objects.create(
|
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 = {
|
data = {
|
||||||
'circuit': self.circuit3.pk,
|
'circuit': self.circuit3.pk,
|
||||||
'term_side': TERM_SIDE_Z,
|
'term_side': TERM_SIDE_Z,
|
||||||
'site': self.site2.pk,
|
'site': self.site2.pk,
|
||||||
'interface': self.interface6.pk,
|
|
||||||
'port_speed': 1000000,
|
'port_speed': 1000000,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -428,7 +403,6 @@ class CircuitTerminationTest(APITestCase):
|
|||||||
circuittermination1 = CircuitTermination.objects.get(pk=response.data['id'])
|
circuittermination1 = CircuitTermination.objects.get(pk=response.data['id'])
|
||||||
self.assertEqual(circuittermination1.term_side, data['term_side'])
|
self.assertEqual(circuittermination1.term_side, data['term_side'])
|
||||||
self.assertEqual(circuittermination1.site_id, data['site'])
|
self.assertEqual(circuittermination1.site_id, data['site'])
|
||||||
self.assertEqual(circuittermination1.interface_id, data['interface'])
|
|
||||||
self.assertEqual(circuittermination1.port_speed, data['port_speed'])
|
self.assertEqual(circuittermination1.port_speed, data['port_speed'])
|
||||||
|
|
||||||
def test_delete_circuittermination(self):
|
def test_delete_circuittermination(self):
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.conf.urls import url
|
||||||
|
|
||||||
|
from dcim.views import CableCreateView, CableTraceView
|
||||||
from extras.views import ObjectChangeLogView
|
from extras.views import ObjectChangeLogView
|
||||||
from . import views
|
from . import views
|
||||||
from .models import Circuit, CircuitType, Provider
|
from .models import Circuit, CircuitTermination, CircuitType, Provider
|
||||||
|
|
||||||
app_name = 'circuits'
|
app_name = 'circuits'
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
@ -44,5 +43,7 @@ urlpatterns = [
|
|||||||
url(r'^circuits/(?P<circuit>\d+)/terminations/add/$', views.CircuitTerminationCreateView.as_view(), name='circuittermination_add'),
|
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+)/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<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}),
|
||||||
|
|
||||||
]
|
]
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth.decorators import permission_required
|
from django.contrib.auth.decorators import permission_required
|
||||||
from django.contrib.auth.mixins import PermissionRequiredMixin
|
from django.contrib.auth.mixins import PermissionRequiredMixin
|
||||||
@ -134,7 +132,7 @@ class CircuitListView(ObjectListView):
|
|||||||
queryset = Circuit.objects.select_related(
|
queryset = Circuit.objects.select_related(
|
||||||
'provider', 'type', 'tenant'
|
'provider', 'type', 'tenant'
|
||||||
).prefetch_related(
|
).prefetch_related(
|
||||||
'terminations__site', 'terminations__interface__device'
|
'terminations__site'
|
||||||
)
|
)
|
||||||
filter = filters.CircuitFilter
|
filter = filters.CircuitFilter
|
||||||
filter_form = forms.CircuitFilterForm
|
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)
|
circuit = get_object_or_404(Circuit.objects.select_related('provider', 'type', 'tenant__group'), pk=pk)
|
||||||
termination_a = CircuitTermination.objects.select_related(
|
termination_a = CircuitTermination.objects.select_related(
|
||||||
'site__region', 'interface__device'
|
'site__region', 'connected_endpoint__device'
|
||||||
).filter(
|
).filter(
|
||||||
circuit=circuit, term_side=TERM_SIDE_A
|
circuit=circuit, term_side=TERM_SIDE_A
|
||||||
).first()
|
).first()
|
||||||
termination_z = CircuitTermination.objects.select_related(
|
termination_z = CircuitTermination.objects.select_related(
|
||||||
'site__region', 'interface__device'
|
'site__region', 'connected_endpoint__device'
|
||||||
).filter(
|
).filter(
|
||||||
circuit=circuit, term_side=TERM_SIDE_Z
|
circuit=circuit, term_side=TERM_SIDE_Z
|
||||||
).first()
|
).first()
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from rest_framework.exceptions import APIException
|
from rest_framework.exceptions import APIException
|
||||||
|
|
||||||
|
|
||||||
|
249
netbox/dcim/api/nested_serializers.py
Normal file
249
netbox/dcim/api/nested_serializers.py
Normal 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']
|
@ -1,43 +1,58 @@
|
|||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from rest_framework.validators import UniqueTogetherValidator
|
from rest_framework.validators import UniqueTogetherValidator
|
||||||
from taggit_serializer.serializers import TaggitSerializer, TagListSerializerField
|
from taggit_serializer.serializers import TaggitSerializer, TagListSerializerField
|
||||||
|
|
||||||
from circuits.models import Circuit, CircuitTermination
|
from dcim.constants import *
|
||||||
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.models import (
|
from dcim.models import (
|
||||||
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
||||||
DeviceBayTemplate, DeviceType, DeviceRole, Interface, InterfaceConnection, InterfaceTemplate, Manufacturer,
|
DeviceBayTemplate, DeviceType, DeviceRole, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate,
|
||||||
InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup,
|
Manufacturer, InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack,
|
||||||
RackReservation, RackRole, Region, Site, VirtualChassis,
|
RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site, VirtualChassis,
|
||||||
)
|
)
|
||||||
from extras.api.customfields import CustomFieldModelSerializer
|
from extras.api.customfields import CustomFieldModelSerializer
|
||||||
from ipam.models import IPAddress, VLAN
|
from ipam.api.nested_serializers import NestedIPAddressSerializer, NestedVLANSerializer
|
||||||
from tenancy.api.serializers import NestedTenantSerializer
|
from ipam.models import VLAN
|
||||||
from users.api.serializers import NestedUserSerializer
|
from tenancy.api.nested_serializers import NestedTenantSerializer
|
||||||
|
from users.api.nested_serializers import NestedUserSerializer
|
||||||
from utilities.api import (
|
from utilities.api import (
|
||||||
ChoiceField, SerializedPKRelatedField, TimeZoneField, ValidatedModelSerializer,
|
ChoiceField, ContentTypeField, SerializedPKRelatedField, TimeZoneField, ValidatedModelSerializer,
|
||||||
WritableNestedSerializer,
|
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):
|
class RegionSerializer(serializers.ModelSerializer):
|
||||||
parent = NestedRegionSerializer(required=False, allow_null=True)
|
parent = NestedRegionSerializer(required=False, allow_null=True)
|
||||||
|
|
||||||
@ -46,10 +61,6 @@ class RegionSerializer(serializers.ModelSerializer):
|
|||||||
fields = ['id', 'name', 'slug', 'parent']
|
fields = ['id', 'name', 'slug', 'parent']
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Sites
|
|
||||||
#
|
|
||||||
|
|
||||||
class SiteSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
class SiteSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
||||||
status = ChoiceField(choices=SITE_STATUS_CHOICES, required=False)
|
status = ChoiceField(choices=SITE_STATUS_CHOICES, required=False)
|
||||||
region = NestedRegionSerializer(required=False, allow_null=True)
|
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):
|
class RackGroupSerializer(ValidatedModelSerializer):
|
||||||
@ -92,18 +95,6 @@ class RackGroupSerializer(ValidatedModelSerializer):
|
|||||||
fields = ['id', 'name', 'slug', 'site']
|
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 RackRoleSerializer(ValidatedModelSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -111,32 +102,23 @@ class RackRoleSerializer(ValidatedModelSerializer):
|
|||||||
fields = ['id', 'name', 'slug', 'color']
|
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):
|
class RackSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
||||||
site = NestedSiteSerializer()
|
site = NestedSiteSerializer()
|
||||||
group = NestedRackGroupSerializer(required=False, allow_null=True, default=None)
|
group = NestedRackGroupSerializer(required=False, allow_null=True, default=None)
|
||||||
tenant = NestedTenantSerializer(required=False, allow_null=True)
|
tenant = NestedTenantSerializer(required=False, allow_null=True)
|
||||||
|
status = ChoiceField(choices=RACK_STATUS_CHOICES, required=False)
|
||||||
role = NestedRackRoleSerializer(required=False, allow_null=True)
|
role = NestedRackRoleSerializer(required=False, allow_null=True)
|
||||||
type = ChoiceField(choices=RACK_TYPE_CHOICES, required=False, allow_null=True)
|
type = ChoiceField(choices=RACK_TYPE_CHOICES, required=False, allow_null=True)
|
||||||
width = ChoiceField(choices=RACK_WIDTH_CHOICES, required=False)
|
width = ChoiceField(choices=RACK_WIDTH_CHOICES, required=False)
|
||||||
|
outer_unit = ChoiceField(choices=RACK_DIMENSION_UNIT_CHOICES, required=False)
|
||||||
tags = TagListSerializerField(required=False)
|
tags = TagListSerializerField(required=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Rack
|
model = Rack
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'name', 'facility_id', 'display_name', 'site', 'group', 'tenant', 'role', 'serial', 'type', 'width',
|
'id', 'name', 'facility_id', 'display_name', 'site', 'group', 'tenant', 'status', 'role', 'serial',
|
||||||
'u_height', 'desc_units', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
|
'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
|
# Omit the UniqueTogetherValidator that would be automatically added to validate (group, facility_id). This
|
||||||
# prevents facility_id from being interpreted as a required field.
|
# prevents facility_id from being interpreted as a required field.
|
||||||
@ -153,31 +135,11 @@ class RackSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
|||||||
validator(data)
|
validator(data)
|
||||||
|
|
||||||
# Enforce model validation
|
# Enforce model validation
|
||||||
super(RackSerializer, self).validate(data)
|
super().validate(data)
|
||||||
|
|
||||||
return 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):
|
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.
|
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)
|
device = NestedDeviceSerializer(read_only=True)
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Rack reservations
|
|
||||||
#
|
|
||||||
|
|
||||||
class RackReservationSerializer(ValidatedModelSerializer):
|
class RackReservationSerializer(ValidatedModelSerializer):
|
||||||
rack = NestedRackSerializer()
|
rack = NestedRackSerializer()
|
||||||
user = NestedUserSerializer()
|
user = NestedUserSerializer()
|
||||||
@ -203,7 +161,7 @@ class RackReservationSerializer(ValidatedModelSerializer):
|
|||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Manufacturers
|
# Device types
|
||||||
#
|
#
|
||||||
|
|
||||||
class ManufacturerSerializer(ValidatedModelSerializer):
|
class ManufacturerSerializer(ValidatedModelSerializer):
|
||||||
@ -213,21 +171,8 @@ class ManufacturerSerializer(ValidatedModelSerializer):
|
|||||||
fields = ['id', 'name', 'slug']
|
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):
|
class DeviceTypeSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
||||||
manufacturer = NestedManufacturerSerializer()
|
manufacturer = NestedManufacturerSerializer()
|
||||||
interface_ordering = ChoiceField(choices=IFACE_ORDERING_CHOICES, required=False)
|
|
||||||
subdevice_role = ChoiceField(choices=SUBDEVICE_ROLE_CHOICES, required=False, allow_null=True)
|
subdevice_role = ChoiceField(choices=SUBDEVICE_ROLE_CHOICES, required=False, allow_null=True)
|
||||||
instance_count = serializers.IntegerField(source='instances.count', read_only=True)
|
instance_count = serializers.IntegerField(source='instances.count', read_only=True)
|
||||||
tags = TagListSerializerField(required=False)
|
tags = TagListSerializerField(required=False)
|
||||||
@ -235,25 +180,11 @@ class DeviceTypeSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = DeviceType
|
model = DeviceType
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'interface_ordering',
|
'id', 'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role',
|
||||||
'is_console_server', 'is_pdu', 'is_network_device', 'subdevice_role', 'comments', 'tags', 'custom_fields',
|
'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'instance_count',
|
||||||
'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):
|
class ConsolePortTemplateSerializer(ValidatedModelSerializer):
|
||||||
device_type = NestedDeviceTypeSerializer()
|
device_type = NestedDeviceTypeSerializer()
|
||||||
|
|
||||||
@ -262,10 +193,6 @@ class ConsolePortTemplateSerializer(ValidatedModelSerializer):
|
|||||||
fields = ['id', 'device_type', 'name']
|
fields = ['id', 'device_type', 'name']
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Console server port templates
|
|
||||||
#
|
|
||||||
|
|
||||||
class ConsoleServerPortTemplateSerializer(ValidatedModelSerializer):
|
class ConsoleServerPortTemplateSerializer(ValidatedModelSerializer):
|
||||||
device_type = NestedDeviceTypeSerializer()
|
device_type = NestedDeviceTypeSerializer()
|
||||||
|
|
||||||
@ -274,10 +201,6 @@ class ConsoleServerPortTemplateSerializer(ValidatedModelSerializer):
|
|||||||
fields = ['id', 'device_type', 'name']
|
fields = ['id', 'device_type', 'name']
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Power port templates
|
|
||||||
#
|
|
||||||
|
|
||||||
class PowerPortTemplateSerializer(ValidatedModelSerializer):
|
class PowerPortTemplateSerializer(ValidatedModelSerializer):
|
||||||
device_type = NestedDeviceTypeSerializer()
|
device_type = NestedDeviceTypeSerializer()
|
||||||
|
|
||||||
@ -286,10 +209,6 @@ class PowerPortTemplateSerializer(ValidatedModelSerializer):
|
|||||||
fields = ['id', 'device_type', 'name']
|
fields = ['id', 'device_type', 'name']
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Power outlet templates
|
|
||||||
#
|
|
||||||
|
|
||||||
class PowerOutletTemplateSerializer(ValidatedModelSerializer):
|
class PowerOutletTemplateSerializer(ValidatedModelSerializer):
|
||||||
device_type = NestedDeviceTypeSerializer()
|
device_type = NestedDeviceTypeSerializer()
|
||||||
|
|
||||||
@ -298,10 +217,6 @@ class PowerOutletTemplateSerializer(ValidatedModelSerializer):
|
|||||||
fields = ['id', 'device_type', 'name']
|
fields = ['id', 'device_type', 'name']
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Interface templates
|
|
||||||
#
|
|
||||||
|
|
||||||
class InterfaceTemplateSerializer(ValidatedModelSerializer):
|
class InterfaceTemplateSerializer(ValidatedModelSerializer):
|
||||||
device_type = NestedDeviceTypeSerializer()
|
device_type = NestedDeviceTypeSerializer()
|
||||||
form_factor = ChoiceField(choices=IFACE_FF_CHOICES, required=False)
|
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']
|
fields = ['id', 'device_type', 'name', 'form_factor', 'mgmt_only']
|
||||||
|
|
||||||
|
|
||||||
#
|
class RearPortTemplateSerializer(ValidatedModelSerializer):
|
||||||
# Device bay templates
|
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):
|
class DeviceBayTemplateSerializer(ValidatedModelSerializer):
|
||||||
device_type = NestedDeviceTypeSerializer()
|
device_type = NestedDeviceTypeSerializer()
|
||||||
@ -324,7 +254,7 @@ class DeviceBayTemplateSerializer(ValidatedModelSerializer):
|
|||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Device roles
|
# Devices
|
||||||
#
|
#
|
||||||
|
|
||||||
class DeviceRoleSerializer(ValidatedModelSerializer):
|
class DeviceRoleSerializer(ValidatedModelSerializer):
|
||||||
@ -334,64 +264,12 @@ class DeviceRoleSerializer(ValidatedModelSerializer):
|
|||||||
fields = ['id', 'name', 'slug', 'color', 'vm_role']
|
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):
|
class PlatformSerializer(ValidatedModelSerializer):
|
||||||
manufacturer = NestedManufacturerSerializer(required=False, allow_null=True)
|
manufacturer = NestedManufacturerSerializer(required=False, allow_null=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Platform
|
model = Platform
|
||||||
fields = ['id', 'name', 'slug', 'manufacturer', 'napalm_driver', 'napalm_args', 'rpc_client']
|
fields = ['id', 'name', 'slug', 'manufacturer', 'napalm_driver', 'napalm_args']
|
||||||
|
|
||||||
|
|
||||||
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']
|
|
||||||
|
|
||||||
|
|
||||||
class DeviceSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
class DeviceSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
||||||
@ -403,12 +281,12 @@ class DeviceSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
|||||||
rack = NestedRackSerializer(required=False, allow_null=True)
|
rack = NestedRackSerializer(required=False, allow_null=True)
|
||||||
face = ChoiceField(choices=RACK_FACE_CHOICES, required=False, allow_null=True)
|
face = ChoiceField(choices=RACK_FACE_CHOICES, required=False, allow_null=True)
|
||||||
status = ChoiceField(choices=DEVICE_STATUS_CHOICES, required=False)
|
status = ChoiceField(choices=DEVICE_STATUS_CHOICES, required=False)
|
||||||
primary_ip = DeviceIPAddressSerializer(read_only=True)
|
primary_ip = NestedIPAddressSerializer(read_only=True)
|
||||||
primary_ip4 = DeviceIPAddressSerializer(required=False, allow_null=True)
|
primary_ip4 = NestedIPAddressSerializer(required=False, allow_null=True)
|
||||||
primary_ip6 = DeviceIPAddressSerializer(required=False, allow_null=True)
|
primary_ip6 = NestedIPAddressSerializer(required=False, allow_null=True)
|
||||||
parent_device = serializers.SerializerMethodField()
|
parent_device = serializers.SerializerMethodField()
|
||||||
cluster = NestedClusterSerializer(required=False, allow_null=True)
|
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)
|
tags = TagListSerializerField(required=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -416,8 +294,8 @@ class DeviceSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
|||||||
fields = [
|
fields = [
|
||||||
'id', 'name', 'display_name', 'device_type', 'device_role', 'tenant', 'platform', 'serial', 'asset_tag',
|
'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',
|
'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',
|
'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'comments', 'local_context_data', 'tags',
|
||||||
'last_updated', 'local_context_data',
|
'custom_fields', 'created', 'last_updated',
|
||||||
]
|
]
|
||||||
validators = []
|
validators = []
|
||||||
|
|
||||||
@ -430,7 +308,7 @@ class DeviceSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
|||||||
validator(data)
|
validator(data)
|
||||||
|
|
||||||
# Enforce model validation
|
# Enforce model validation
|
||||||
super(DeviceSerializer, self).validate(data)
|
super().validate(data)
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@ -452,203 +330,90 @@ class DeviceWithConfigContextSerializer(DeviceSerializer):
|
|||||||
fields = [
|
fields = [
|
||||||
'id', 'name', 'display_name', 'device_type', 'device_role', 'tenant', 'platform', 'serial', 'asset_tag',
|
'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',
|
'site', 'rack', 'position', 'face', 'parent_device', 'status', 'primary_ip', 'primary_ip4', 'primary_ip6',
|
||||||
'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'comments', 'tags', 'custom_fields',
|
'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'comments', 'local_context_data', 'tags',
|
||||||
'config_context', 'created', 'last_updated', 'local_context_data',
|
'custom_fields', 'config_context', 'created', 'last_updated',
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_config_context(self, obj):
|
def get_config_context(self, obj):
|
||||||
return obj.get_config_context()
|
return obj.get_config_context()
|
||||||
|
|
||||||
|
|
||||||
#
|
class ConsoleServerPortSerializer(TaggitSerializer, ConnectedEndpointSerializer):
|
||||||
# Console server ports
|
|
||||||
#
|
|
||||||
|
|
||||||
class ConsoleServerPortSerializer(TaggitSerializer, ValidatedModelSerializer):
|
|
||||||
device = NestedDeviceSerializer()
|
device = NestedDeviceSerializer()
|
||||||
|
cable = NestedCableSerializer(read_only=True)
|
||||||
tags = TagListSerializerField(required=False)
|
tags = TagListSerializerField(required=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ConsoleServerPort
|
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 = [
|
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 ConsolePortSerializer(TaggitSerializer, ConnectedEndpointSerializer):
|
||||||
class InterfaceVLANSerializer(WritableNestedSerializer):
|
device = NestedDeviceSerializer()
|
||||||
url = serializers.HyperlinkedIdentityField(view_name='ipam-api:vlan-detail')
|
cable = NestedCableSerializer(read_only=True)
|
||||||
|
tags = TagListSerializerField(required=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = VLAN
|
model = ConsolePort
|
||||||
fields = ['id', 'url', 'vid', 'name', 'display_name']
|
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()
|
device = NestedDeviceSerializer()
|
||||||
form_factor = ChoiceField(choices=IFACE_FF_CHOICES, required=False)
|
form_factor = ChoiceField(choices=IFACE_FF_CHOICES, required=False)
|
||||||
lag = NestedInterfaceSerializer(required=False, allow_null=True)
|
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)
|
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(
|
tagged_vlans = SerializedPKRelatedField(
|
||||||
queryset=VLAN.objects.all(),
|
queryset=VLAN.objects.all(),
|
||||||
serializer=InterfaceVLANSerializer,
|
serializer=NestedVLANSerializer,
|
||||||
required=False,
|
required=False,
|
||||||
many=True
|
many=True
|
||||||
)
|
)
|
||||||
|
cable = NestedCableSerializer(read_only=True)
|
||||||
tags = TagListSerializerField(required=False)
|
tags = TagListSerializerField(required=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Interface
|
model = Interface
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'device', 'name', 'form_factor', 'enabled', 'lag', 'mtu', 'mac_address', 'mgmt_only', 'description',
|
'id', 'device', 'name', 'form_factor', 'enabled', 'lag', 'mtu', 'mac_address', 'mgmt_only', 'description',
|
||||||
'is_connected', 'interface_connection', 'circuit_termination', 'mode', 'untagged_vlan', 'tagged_vlans',
|
'connected_endpoint_type', 'connected_endpoint', 'connection_status', 'cable', 'mode', 'untagged_vlan',
|
||||||
'tags',
|
'tagged_vlans', 'tags', 'count_ipaddresses',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# TODO: This validation should be handled by Interface.clean()
|
||||||
def validate(self, data):
|
def validate(self, data):
|
||||||
|
|
||||||
# All associated VLANs be global or assigned to the parent device's site.
|
# 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)
|
"be global.".format(vlan)
|
||||||
})
|
})
|
||||||
|
|
||||||
return super(InterfaceSerializer, self).validate(data)
|
return super().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
|
|
||||||
|
|
||||||
|
|
||||||
#
|
class RearPortSerializer(ValidatedModelSerializer):
|
||||||
# Device bays
|
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):
|
class DeviceBaySerializer(TaggitSerializer, ValidatedModelSerializer):
|
||||||
device = NestedDeviceSerializer()
|
device = NestedDeviceSerializer()
|
||||||
@ -692,15 +478,6 @@ class DeviceBaySerializer(TaggitSerializer, ValidatedModelSerializer):
|
|||||||
fields = ['id', 'device', 'name', 'installed_device', 'tags']
|
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
|
# 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
|
# Interface connections
|
||||||
#
|
#
|
||||||
|
|
||||||
class InterfaceConnectionSerializer(ValidatedModelSerializer):
|
class InterfaceConnectionSerializer(ValidatedModelSerializer):
|
||||||
interface_a = NestedInterfaceSerializer()
|
interface_a = serializers.SerializerMethodField()
|
||||||
interface_b = NestedInterfaceSerializer()
|
interface_b = NestedInterfaceSerializer(source='connected_endpoint')
|
||||||
connection_status = ChoiceField(choices=CONNECTION_STATUS_CHOICES, required=False)
|
connection_status = ChoiceField(choices=CONNECTION_STATUS_CHOICES, required=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = InterfaceConnection
|
model = Interface
|
||||||
fields = ['id', 'interface_a', 'interface_b', 'connection_status']
|
fields = ['interface_a', 'interface_b', 'connection_status']
|
||||||
|
|
||||||
|
def get_interface_a(self, obj):
|
||||||
class NestedInterfaceConnectionSerializer(WritableNestedSerializer):
|
context = {'request': self.context['request']}
|
||||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:interfaceconnection-detail')
|
return NestedInterfaceSerializer(instance=obj, context=context).data
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -768,11 +580,3 @@ class VirtualChassisSerializer(TaggitSerializer, ValidatedModelSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = VirtualChassis
|
model = VirtualChassis
|
||||||
fields = ['id', 'master', 'domain', 'tags']
|
fields = ['id', 'master', 'domain', 'tags']
|
||||||
|
|
||||||
|
|
||||||
class NestedVirtualChassisSerializer(WritableNestedSerializer):
|
|
||||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:virtualchassis-detail')
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = VirtualChassis
|
|
||||||
fields = ['id', 'url']
|
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from rest_framework import routers
|
from rest_framework import routers
|
||||||
|
|
||||||
from . import views
|
from . import views
|
||||||
@ -17,7 +15,7 @@ router = routers.DefaultRouter()
|
|||||||
router.APIRootView = DCIMRootView
|
router.APIRootView = DCIMRootView
|
||||||
|
|
||||||
# Field choices
|
# Field choices
|
||||||
router.register(r'_choices', views.DCIMFieldChoicesViewSet, base_name='field-choice')
|
router.register(r'_choices', views.DCIMFieldChoicesViewSet, basename='field-choice')
|
||||||
|
|
||||||
# Sites
|
# Sites
|
||||||
router.register(r'regions', views.RegionViewSet)
|
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-port-templates', views.PowerPortTemplateViewSet)
|
||||||
router.register(r'power-outlet-templates', views.PowerOutletTemplateViewSet)
|
router.register(r'power-outlet-templates', views.PowerOutletTemplateViewSet)
|
||||||
router.register(r'interface-templates', views.InterfaceTemplateViewSet)
|
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)
|
router.register(r'device-bay-templates', views.DeviceBayTemplateViewSet)
|
||||||
|
|
||||||
# Devices
|
# Devices
|
||||||
@ -52,19 +52,24 @@ router.register(r'console-server-ports', views.ConsoleServerPortViewSet)
|
|||||||
router.register(r'power-ports', views.PowerPortViewSet)
|
router.register(r'power-ports', views.PowerPortViewSet)
|
||||||
router.register(r'power-outlets', views.PowerOutletViewSet)
|
router.register(r'power-outlets', views.PowerOutletViewSet)
|
||||||
router.register(r'interfaces', views.InterfaceViewSet)
|
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'device-bays', views.DeviceBayViewSet)
|
||||||
router.register(r'inventory-items', views.InventoryItemViewSet)
|
router.register(r'inventory-items', views.InventoryItemViewSet)
|
||||||
|
|
||||||
# Connections
|
# Connections
|
||||||
router.register(r'console-connections', views.ConsoleConnectionViewSet, base_name='consoleconnections')
|
router.register(r'console-connections', views.ConsoleConnectionViewSet, basename='consoleconnections')
|
||||||
router.register(r'power-connections', views.PowerConnectionViewSet, base_name='powerconnections')
|
router.register(r'power-connections', views.PowerConnectionViewSet, basename='powerconnections')
|
||||||
router.register(r'interface-connections', views.InterfaceConnectionViewSet)
|
router.register(r'interface-connections', views.InterfaceConnectionViewSet, basename='interfaceconnections')
|
||||||
|
|
||||||
|
# Cables
|
||||||
|
router.register(r'cables', views.CableViewSet)
|
||||||
|
|
||||||
# Virtual chassis
|
# Virtual chassis
|
||||||
router.register(r'virtual-chassis', views.VirtualChassisViewSet)
|
router.register(r'virtual-chassis', views.VirtualChassisViewSet)
|
||||||
|
|
||||||
# Miscellaneous
|
# 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'
|
app_name = 'dcim-api'
|
||||||
urlpatterns = router.urls
|
urlpatterns = router.urls
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.db.models import F, Q
|
||||||
from django.http import HttpResponseForbidden
|
from django.http import HttpResponseForbidden
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from drf_yasg import openapi
|
from drf_yasg import openapi
|
||||||
@ -15,15 +14,17 @@ from rest_framework.viewsets import GenericViewSet, ViewSet
|
|||||||
|
|
||||||
from dcim import filters
|
from dcim import filters
|
||||||
from dcim.models import (
|
from dcim.models import (
|
||||||
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
||||||
DeviceBayTemplate, DeviceRole, DeviceType, Interface, InterfaceConnection, InterfaceTemplate, Manufacturer,
|
DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate,
|
||||||
InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup,
|
Manufacturer, InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack,
|
||||||
RackReservation, RackRole, Region, Site, VirtualChassis,
|
RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site, VirtualChassis,
|
||||||
)
|
)
|
||||||
from extras.api.serializers import RenderedGraphSerializer
|
from extras.api.serializers import RenderedGraphSerializer
|
||||||
from extras.api.views import CustomFieldModelViewSet
|
from extras.api.views import CustomFieldModelViewSet
|
||||||
from extras.models import Graph, GRAPH_TYPE_INTERFACE, GRAPH_TYPE_SITE
|
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 . import serializers
|
||||||
from .exceptions import MissingFilterException
|
from .exceptions import MissingFilterException
|
||||||
|
|
||||||
@ -34,17 +35,51 @@ from .exceptions import MissingFilterException
|
|||||||
|
|
||||||
class DCIMFieldChoicesViewSet(FieldChoicesViewSet):
|
class DCIMFieldChoicesViewSet(FieldChoicesViewSet):
|
||||||
fields = (
|
fields = (
|
||||||
|
(Cable, ['length_unit']),
|
||||||
(Device, ['face', 'status']),
|
(Device, ['face', 'status']),
|
||||||
(ConsolePort, ['connection_status']),
|
(ConsolePort, ['connection_status']),
|
||||||
(Interface, ['form_factor', 'mode']),
|
(Interface, ['connection_status', 'form_factor', 'mode']),
|
||||||
(InterfaceConnection, ['connection_status']),
|
|
||||||
(InterfaceTemplate, ['form_factor']),
|
(InterfaceTemplate, ['form_factor']),
|
||||||
(PowerPort, ['connection_status']),
|
(PowerPort, ['connection_status']),
|
||||||
(Rack, ['type', 'width']),
|
(Rack, ['outer_unit', 'status', 'type', 'width']),
|
||||||
(Site, ['status']),
|
(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
|
# Regions
|
||||||
#
|
#
|
||||||
@ -52,7 +87,7 @@ class DCIMFieldChoicesViewSet(FieldChoicesViewSet):
|
|||||||
class RegionViewSet(ModelViewSet):
|
class RegionViewSet(ModelViewSet):
|
||||||
queryset = Region.objects.all()
|
queryset = Region.objects.all()
|
||||||
serializer_class = serializers.RegionSerializer
|
serializer_class = serializers.RegionSerializer
|
||||||
filter_class = filters.RegionFilter
|
filterset_class = filters.RegionFilter
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -62,7 +97,7 @@ class RegionViewSet(ModelViewSet):
|
|||||||
class SiteViewSet(CustomFieldModelViewSet):
|
class SiteViewSet(CustomFieldModelViewSet):
|
||||||
queryset = Site.objects.select_related('region', 'tenant').prefetch_related('tags')
|
queryset = Site.objects.select_related('region', 'tenant').prefetch_related('tags')
|
||||||
serializer_class = serializers.SiteSerializer
|
serializer_class = serializers.SiteSerializer
|
||||||
filter_class = filters.SiteFilter
|
filterset_class = filters.SiteFilter
|
||||||
|
|
||||||
@action(detail=True)
|
@action(detail=True)
|
||||||
def graphs(self, request, pk=None):
|
def graphs(self, request, pk=None):
|
||||||
@ -82,7 +117,7 @@ class SiteViewSet(CustomFieldModelViewSet):
|
|||||||
class RackGroupViewSet(ModelViewSet):
|
class RackGroupViewSet(ModelViewSet):
|
||||||
queryset = RackGroup.objects.select_related('site')
|
queryset = RackGroup.objects.select_related('site')
|
||||||
serializer_class = serializers.RackGroupSerializer
|
serializer_class = serializers.RackGroupSerializer
|
||||||
filter_class = filters.RackGroupFilter
|
filterset_class = filters.RackGroupFilter
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -92,7 +127,7 @@ class RackGroupViewSet(ModelViewSet):
|
|||||||
class RackRoleViewSet(ModelViewSet):
|
class RackRoleViewSet(ModelViewSet):
|
||||||
queryset = RackRole.objects.all()
|
queryset = RackRole.objects.all()
|
||||||
serializer_class = serializers.RackRoleSerializer
|
serializer_class = serializers.RackRoleSerializer
|
||||||
filter_class = filters.RackRoleFilter
|
filterset_class = filters.RackRoleFilter
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -102,7 +137,7 @@ class RackRoleViewSet(ModelViewSet):
|
|||||||
class RackViewSet(CustomFieldModelViewSet):
|
class RackViewSet(CustomFieldModelViewSet):
|
||||||
queryset = Rack.objects.select_related('site', 'group__site', 'tenant').prefetch_related('tags')
|
queryset = Rack.objects.select_related('site', 'group__site', 'tenant').prefetch_related('tags')
|
||||||
serializer_class = serializers.RackSerializer
|
serializer_class = serializers.RackSerializer
|
||||||
filter_class = filters.RackFilter
|
filterset_class = filters.RackFilter
|
||||||
|
|
||||||
@action(detail=True)
|
@action(detail=True)
|
||||||
def units(self, request, pk=None):
|
def units(self, request, pk=None):
|
||||||
@ -132,7 +167,7 @@ class RackViewSet(CustomFieldModelViewSet):
|
|||||||
class RackReservationViewSet(ModelViewSet):
|
class RackReservationViewSet(ModelViewSet):
|
||||||
queryset = RackReservation.objects.select_related('rack', 'user', 'tenant')
|
queryset = RackReservation.objects.select_related('rack', 'user', 'tenant')
|
||||||
serializer_class = serializers.RackReservationSerializer
|
serializer_class = serializers.RackReservationSerializer
|
||||||
filter_class = filters.RackReservationFilter
|
filterset_class = filters.RackReservationFilter
|
||||||
|
|
||||||
# Assign user from request
|
# Assign user from request
|
||||||
def perform_create(self, serializer):
|
def perform_create(self, serializer):
|
||||||
@ -146,7 +181,7 @@ class RackReservationViewSet(ModelViewSet):
|
|||||||
class ManufacturerViewSet(ModelViewSet):
|
class ManufacturerViewSet(ModelViewSet):
|
||||||
queryset = Manufacturer.objects.all()
|
queryset = Manufacturer.objects.all()
|
||||||
serializer_class = serializers.ManufacturerSerializer
|
serializer_class = serializers.ManufacturerSerializer
|
||||||
filter_class = filters.ManufacturerFilter
|
filterset_class = filters.ManufacturerFilter
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -156,7 +191,7 @@ class ManufacturerViewSet(ModelViewSet):
|
|||||||
class DeviceTypeViewSet(CustomFieldModelViewSet):
|
class DeviceTypeViewSet(CustomFieldModelViewSet):
|
||||||
queryset = DeviceType.objects.select_related('manufacturer').prefetch_related('tags')
|
queryset = DeviceType.objects.select_related('manufacturer').prefetch_related('tags')
|
||||||
serializer_class = serializers.DeviceTypeSerializer
|
serializer_class = serializers.DeviceTypeSerializer
|
||||||
filter_class = filters.DeviceTypeFilter
|
filterset_class = filters.DeviceTypeFilter
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -166,37 +201,49 @@ class DeviceTypeViewSet(CustomFieldModelViewSet):
|
|||||||
class ConsolePortTemplateViewSet(ModelViewSet):
|
class ConsolePortTemplateViewSet(ModelViewSet):
|
||||||
queryset = ConsolePortTemplate.objects.select_related('device_type__manufacturer')
|
queryset = ConsolePortTemplate.objects.select_related('device_type__manufacturer')
|
||||||
serializer_class = serializers.ConsolePortTemplateSerializer
|
serializer_class = serializers.ConsolePortTemplateSerializer
|
||||||
filter_class = filters.ConsolePortTemplateFilter
|
filterset_class = filters.ConsolePortTemplateFilter
|
||||||
|
|
||||||
|
|
||||||
class ConsoleServerPortTemplateViewSet(ModelViewSet):
|
class ConsoleServerPortTemplateViewSet(ModelViewSet):
|
||||||
queryset = ConsoleServerPortTemplate.objects.select_related('device_type__manufacturer')
|
queryset = ConsoleServerPortTemplate.objects.select_related('device_type__manufacturer')
|
||||||
serializer_class = serializers.ConsoleServerPortTemplateSerializer
|
serializer_class = serializers.ConsoleServerPortTemplateSerializer
|
||||||
filter_class = filters.ConsoleServerPortTemplateFilter
|
filterset_class = filters.ConsoleServerPortTemplateFilter
|
||||||
|
|
||||||
|
|
||||||
class PowerPortTemplateViewSet(ModelViewSet):
|
class PowerPortTemplateViewSet(ModelViewSet):
|
||||||
queryset = PowerPortTemplate.objects.select_related('device_type__manufacturer')
|
queryset = PowerPortTemplate.objects.select_related('device_type__manufacturer')
|
||||||
serializer_class = serializers.PowerPortTemplateSerializer
|
serializer_class = serializers.PowerPortTemplateSerializer
|
||||||
filter_class = filters.PowerPortTemplateFilter
|
filterset_class = filters.PowerPortTemplateFilter
|
||||||
|
|
||||||
|
|
||||||
class PowerOutletTemplateViewSet(ModelViewSet):
|
class PowerOutletTemplateViewSet(ModelViewSet):
|
||||||
queryset = PowerOutletTemplate.objects.select_related('device_type__manufacturer')
|
queryset = PowerOutletTemplate.objects.select_related('device_type__manufacturer')
|
||||||
serializer_class = serializers.PowerOutletTemplateSerializer
|
serializer_class = serializers.PowerOutletTemplateSerializer
|
||||||
filter_class = filters.PowerOutletTemplateFilter
|
filterset_class = filters.PowerOutletTemplateFilter
|
||||||
|
|
||||||
|
|
||||||
class InterfaceTemplateViewSet(ModelViewSet):
|
class InterfaceTemplateViewSet(ModelViewSet):
|
||||||
queryset = InterfaceTemplate.objects.select_related('device_type__manufacturer')
|
queryset = InterfaceTemplate.objects.select_related('device_type__manufacturer')
|
||||||
serializer_class = serializers.InterfaceTemplateSerializer
|
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):
|
class DeviceBayTemplateViewSet(ModelViewSet):
|
||||||
queryset = DeviceBayTemplate.objects.select_related('device_type__manufacturer')
|
queryset = DeviceBayTemplate.objects.select_related('device_type__manufacturer')
|
||||||
serializer_class = serializers.DeviceBayTemplateSerializer
|
serializer_class = serializers.DeviceBayTemplateSerializer
|
||||||
filter_class = filters.DeviceBayTemplateFilter
|
filterset_class = filters.DeviceBayTemplateFilter
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -206,7 +253,7 @@ class DeviceBayTemplateViewSet(ModelViewSet):
|
|||||||
class DeviceRoleViewSet(ModelViewSet):
|
class DeviceRoleViewSet(ModelViewSet):
|
||||||
queryset = DeviceRole.objects.all()
|
queryset = DeviceRole.objects.all()
|
||||||
serializer_class = serializers.DeviceRoleSerializer
|
serializer_class = serializers.DeviceRoleSerializer
|
||||||
filter_class = filters.DeviceRoleFilter
|
filterset_class = filters.DeviceRoleFilter
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -216,7 +263,7 @@ class DeviceRoleViewSet(ModelViewSet):
|
|||||||
class PlatformViewSet(ModelViewSet):
|
class PlatformViewSet(ModelViewSet):
|
||||||
queryset = Platform.objects.all()
|
queryset = Platform.objects.all()
|
||||||
serializer_class = serializers.PlatformSerializer
|
serializer_class = serializers.PlatformSerializer
|
||||||
filter_class = filters.PlatformFilter
|
filterset_class = filters.PlatformFilter
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -230,7 +277,7 @@ class DeviceViewSet(CustomFieldModelViewSet):
|
|||||||
).prefetch_related(
|
).prefetch_related(
|
||||||
'primary_ip4__nat_outside', 'primary_ip6__nat_outside', 'tags',
|
'primary_ip4__nat_outside', 'primary_ip6__nat_outside', 'tags',
|
||||||
)
|
)
|
||||||
filter_class = filters.DeviceFilter
|
filterset_class = filters.DeviceFilter
|
||||||
|
|
||||||
def get_serializer_class(self):
|
def get_serializer_class(self):
|
||||||
"""
|
"""
|
||||||
@ -321,34 +368,54 @@ class DeviceViewSet(CustomFieldModelViewSet):
|
|||||||
# Device components
|
# Device components
|
||||||
#
|
#
|
||||||
|
|
||||||
class ConsolePortViewSet(ModelViewSet):
|
class ConsolePortViewSet(CableTraceMixin, ModelViewSet):
|
||||||
queryset = ConsolePort.objects.select_related('device', 'cs_port__device').prefetch_related('tags')
|
queryset = ConsolePort.objects.select_related(
|
||||||
|
'device', 'connected_endpoint__device', 'cable'
|
||||||
|
).prefetch_related(
|
||||||
|
'tags'
|
||||||
|
)
|
||||||
serializer_class = serializers.ConsolePortSerializer
|
serializer_class = serializers.ConsolePortSerializer
|
||||||
filter_class = filters.ConsolePortFilter
|
filterset_class = filters.ConsolePortFilter
|
||||||
|
|
||||||
|
|
||||||
class ConsoleServerPortViewSet(ModelViewSet):
|
class ConsoleServerPortViewSet(CableTraceMixin, ModelViewSet):
|
||||||
queryset = ConsoleServerPort.objects.select_related('device', 'connected_console__device').prefetch_related('tags')
|
queryset = ConsoleServerPort.objects.select_related(
|
||||||
|
'device', 'connected_endpoint__device', 'cable'
|
||||||
|
).prefetch_related(
|
||||||
|
'tags'
|
||||||
|
)
|
||||||
serializer_class = serializers.ConsoleServerPortSerializer
|
serializer_class = serializers.ConsoleServerPortSerializer
|
||||||
filter_class = filters.ConsoleServerPortFilter
|
filterset_class = filters.ConsoleServerPortFilter
|
||||||
|
|
||||||
|
|
||||||
class PowerPortViewSet(ModelViewSet):
|
class PowerPortViewSet(CableTraceMixin, ModelViewSet):
|
||||||
queryset = PowerPort.objects.select_related('device', 'power_outlet__device').prefetch_related('tags')
|
queryset = PowerPort.objects.select_related(
|
||||||
|
'device', 'connected_endpoint__device', 'cable'
|
||||||
|
).prefetch_related(
|
||||||
|
'tags'
|
||||||
|
)
|
||||||
serializer_class = serializers.PowerPortSerializer
|
serializer_class = serializers.PowerPortSerializer
|
||||||
filter_class = filters.PowerPortFilter
|
filterset_class = filters.PowerPortFilter
|
||||||
|
|
||||||
|
|
||||||
class PowerOutletViewSet(ModelViewSet):
|
class PowerOutletViewSet(CableTraceMixin, ModelViewSet):
|
||||||
queryset = PowerOutlet.objects.select_related('device', 'connected_port__device').prefetch_related('tags')
|
queryset = PowerOutlet.objects.select_related(
|
||||||
|
'device', 'connected_endpoint__device', 'cable'
|
||||||
|
).prefetch_related(
|
||||||
|
'tags'
|
||||||
|
)
|
||||||
serializer_class = serializers.PowerOutletSerializer
|
serializer_class = serializers.PowerOutletSerializer
|
||||||
filter_class = filters.PowerOutletFilter
|
filterset_class = filters.PowerOutletFilter
|
||||||
|
|
||||||
|
|
||||||
class InterfaceViewSet(ModelViewSet):
|
class InterfaceViewSet(CableTraceMixin, ModelViewSet):
|
||||||
queryset = Interface.objects.select_related('device').prefetch_related('tags')
|
queryset = Interface.objects.select_related(
|
||||||
|
'device', '_connected_interface', '_connected_circuittermination', 'cable'
|
||||||
|
).prefetch_related(
|
||||||
|
'ip_addresses', 'tags'
|
||||||
|
)
|
||||||
serializer_class = serializers.InterfaceSerializer
|
serializer_class = serializers.InterfaceSerializer
|
||||||
filter_class = filters.InterfaceFilter
|
filterset_class = filters.InterfaceFilter
|
||||||
|
|
||||||
@action(detail=True)
|
@action(detail=True)
|
||||||
def graphs(self, request, pk=None):
|
def graphs(self, request, pk=None):
|
||||||
@ -361,16 +428,36 @@ class InterfaceViewSet(ModelViewSet):
|
|||||||
return Response(serializer.data)
|
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):
|
class DeviceBayViewSet(ModelViewSet):
|
||||||
queryset = DeviceBay.objects.select_related('installed_device').prefetch_related('tags')
|
queryset = DeviceBay.objects.select_related('installed_device').prefetch_related('tags')
|
||||||
serializer_class = serializers.DeviceBaySerializer
|
serializer_class = serializers.DeviceBaySerializer
|
||||||
filter_class = filters.DeviceBayFilter
|
filterset_class = filters.DeviceBayFilter
|
||||||
|
|
||||||
|
|
||||||
class InventoryItemViewSet(ModelViewSet):
|
class InventoryItemViewSet(ModelViewSet):
|
||||||
queryset = InventoryItem.objects.select_related('device', 'manufacturer').prefetch_related('tags')
|
queryset = InventoryItem.objects.select_related('device', 'manufacturer').prefetch_related('tags')
|
||||||
serializer_class = serializers.InventoryItemSerializer
|
serializer_class = serializers.InventoryItemSerializer
|
||||||
filter_class = filters.InventoryItemFilter
|
filterset_class = filters.InventoryItemFilter
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -378,21 +465,47 @@ class InventoryItemViewSet(ModelViewSet):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class ConsoleConnectionViewSet(ListModelMixin, GenericViewSet):
|
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
|
serializer_class = serializers.ConsolePortSerializer
|
||||||
filter_class = filters.ConsoleConnectionFilter
|
filterset_class = filters.ConsoleConnectionFilter
|
||||||
|
|
||||||
|
|
||||||
class PowerConnectionViewSet(ListModelMixin, GenericViewSet):
|
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
|
serializer_class = serializers.PowerPortSerializer
|
||||||
filter_class = filters.PowerConnectionFilter
|
filterset_class = filters.PowerConnectionFilter
|
||||||
|
|
||||||
|
|
||||||
class InterfaceConnectionViewSet(ModelViewSet):
|
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
|
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
|
* `peer_interface`: The name of the peer interface
|
||||||
"""
|
"""
|
||||||
permission_classes = [IsAuthenticatedOrLoginNotRequired]
|
permission_classes = [IsAuthenticatedOrLoginNotRequired]
|
||||||
_device_param = Parameter('peer_device', 'query',
|
_device_param = Parameter(
|
||||||
description='The name of the peer device', required=True, type=openapi.TYPE_STRING)
|
name='peer_device',
|
||||||
_interface_param = Parameter('peer_interface', 'query',
|
in_='query',
|
||||||
description='The name of the peer interface', required=True, type=openapi.TYPE_STRING)
|
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):
|
def get_view_name(self):
|
||||||
return "Connected Device Locator"
|
return "Connected Device Locator"
|
||||||
|
|
||||||
@swagger_auto_schema(
|
@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):
|
def list(self, request):
|
||||||
|
|
||||||
peer_device_name = request.query_params.get(self._device_param.name)
|
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)
|
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:
|
if not peer_device_name or not peer_interface_name:
|
||||||
raise MissingFilterException(detail='Request must include "peer_device" and "peer_interface" filters.')
|
raise MissingFilterException(detail='Request must include "peer_device" and "peer_interface" filters.')
|
||||||
|
|
||||||
# Determine local interface from peer interface's connection
|
# Determine local interface from peer interface's connection
|
||||||
peer_interface = get_object_or_404(Interface, device__name=peer_device_name, name=peer_interface_name)
|
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:
|
if local_interface is None:
|
||||||
return Response()
|
return Response()
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
|
|
||||||
# Rack types
|
# Rack types
|
||||||
RACK_TYPE_2POST = 100
|
RACK_TYPE_2POST = 100
|
||||||
@ -31,6 +29,20 @@ RACK_FACE_CHOICES = [
|
|||||||
[RACK_FACE_REAR, 'Rear'],
|
[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
|
# Parent/child device roles
|
||||||
SUBDEVICE_ROLE_PARENT = True
|
SUBDEVICE_ROLE_PARENT = True
|
||||||
SUBDEVICE_ROLE_CHILD = False
|
SUBDEVICE_ROLE_CHILD = False
|
||||||
@ -233,6 +245,36 @@ IFACE_MODE_CHOICES = [
|
|||||||
[IFACE_MODE_TAGGED_ALL, 'Tagged All'],
|
[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 statuses
|
||||||
DEVICE_STATUS_OFFLINE = 0
|
DEVICE_STATUS_OFFLINE = 0
|
||||||
DEVICE_STATUS_ACTIVE = 1
|
DEVICE_STATUS_ACTIVE = 1
|
||||||
@ -259,7 +301,7 @@ SITE_STATUS_CHOICES = [
|
|||||||
[SITE_STATUS_RETIRED, 'Retired'],
|
[SITE_STATUS_RETIRED, 'Retired'],
|
||||||
]
|
]
|
||||||
|
|
||||||
# Bootstrap CSS classes for device statuses
|
# Bootstrap CSS classes for device/rack statuses
|
||||||
STATUS_CLASSES = {
|
STATUS_CLASSES = {
|
||||||
0: 'warning',
|
0: 'warning',
|
||||||
1: 'success',
|
1: 'success',
|
||||||
@ -277,12 +319,81 @@ CONNECTION_STATUS_CHOICES = [
|
|||||||
[CONNECTION_STATUS_CONNECTED, 'Connected'],
|
[CONNECTION_STATUS_CONNECTED, 'Connected'],
|
||||||
]
|
]
|
||||||
|
|
||||||
# Platform -> RPC client mappings
|
# Cable endpoint types
|
||||||
RPC_CLIENT_JUNIPER_JUNOS = 'juniper-junos'
|
CABLE_TERMINATION_TYPES = [
|
||||||
RPC_CLIENT_CISCO_IOS = 'cisco-ios'
|
'consoleport', 'consoleserverport', 'interface', 'poweroutlet', 'powerport', 'frontport', 'rearport',
|
||||||
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 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'),
|
||||||
|
)
|
||||||
|
@ -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.exceptions import ValidationError
|
||||||
from django.core.validators import MinValueValidator, MaxValueValidator
|
from django.core.validators import MinValueValidator, MaxValueValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from netaddr import AddrFormatError, EUI, mac_unix_expanded
|
||||||
|
|
||||||
|
|
||||||
class ASNField(models.BigIntegerField):
|
class ASNField(models.BigIntegerField):
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
import django_filters
|
import django_filters
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
@ -9,17 +7,15 @@ from netaddr.core import AddrFormatError
|
|||||||
|
|
||||||
from extras.filters import CustomFieldFilterSet
|
from extras.filters import CustomFieldFilterSet
|
||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
|
from utilities.constants import COLOR_CHOICES
|
||||||
from utilities.filters import NullableCharFieldFilter, NumericInFilter, TagFilter
|
from utilities.filters import NullableCharFieldFilter, NumericInFilter, TagFilter
|
||||||
from virtualization.models import Cluster
|
from virtualization.models import Cluster
|
||||||
from .constants import (
|
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 .models import (
|
from .models import (
|
||||||
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
||||||
DeviceBayTemplate, DeviceRole, DeviceType, Interface, InterfaceConnection, InterfaceTemplate, Manufacturer,
|
DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate,
|
||||||
InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup,
|
InventoryItem, Manufacturer, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack,
|
||||||
RackReservation, RackRole, Region, Site, VirtualChassis,
|
RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site, VirtualChassis,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -33,7 +29,7 @@ class RegionFilter(django_filters.FilterSet):
|
|||||||
label='Parent region (ID)',
|
label='Parent region (ID)',
|
||||||
)
|
)
|
||||||
parent = django_filters.ModelMultipleChoiceFilter(
|
parent = django_filters.ModelMultipleChoiceFilter(
|
||||||
name='parent__slug',
|
field_name='parent__slug',
|
||||||
queryset=Region.objects.all(),
|
queryset=Region.objects.all(),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label='Parent region (slug)',
|
label='Parent region (slug)',
|
||||||
@ -54,7 +50,10 @@ class RegionFilter(django_filters.FilterSet):
|
|||||||
|
|
||||||
|
|
||||||
class SiteFilter(CustomFieldFilterSet, 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(
|
q = django_filters.CharFilter(
|
||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
||||||
@ -68,7 +67,7 @@ class SiteFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
label='Region (ID)',
|
label='Region (ID)',
|
||||||
)
|
)
|
||||||
region = django_filters.ModelMultipleChoiceFilter(
|
region = django_filters.ModelMultipleChoiceFilter(
|
||||||
name='region__slug',
|
field_name='region__slug',
|
||||||
queryset=Region.objects.all(),
|
queryset=Region.objects.all(),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label='Region (slug)',
|
label='Region (slug)',
|
||||||
@ -78,7 +77,7 @@ class SiteFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
label='Tenant (ID)',
|
label='Tenant (ID)',
|
||||||
)
|
)
|
||||||
tenant = django_filters.ModelMultipleChoiceFilter(
|
tenant = django_filters.ModelMultipleChoiceFilter(
|
||||||
name='tenant__slug',
|
field_name='tenant__slug',
|
||||||
queryset=Tenant.objects.all(),
|
queryset=Tenant.objects.all(),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label='Tenant (slug)',
|
label='Tenant (slug)',
|
||||||
@ -120,7 +119,7 @@ class RackGroupFilter(django_filters.FilterSet):
|
|||||||
label='Site (ID)',
|
label='Site (ID)',
|
||||||
)
|
)
|
||||||
site = django_filters.ModelMultipleChoiceFilter(
|
site = django_filters.ModelMultipleChoiceFilter(
|
||||||
name='site__slug',
|
field_name='site__slug',
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label='Site (slug)',
|
label='Site (slug)',
|
||||||
@ -148,7 +147,10 @@ class RackRoleFilter(django_filters.FilterSet):
|
|||||||
|
|
||||||
|
|
||||||
class RackFilter(CustomFieldFilterSet, 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(
|
q = django_filters.CharFilter(
|
||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
||||||
@ -159,7 +161,7 @@ class RackFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
label='Site (ID)',
|
label='Site (ID)',
|
||||||
)
|
)
|
||||||
site = django_filters.ModelMultipleChoiceFilter(
|
site = django_filters.ModelMultipleChoiceFilter(
|
||||||
name='site__slug',
|
field_name='site__slug',
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label='Site (slug)',
|
label='Site (slug)',
|
||||||
@ -169,7 +171,7 @@ class RackFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
label='Group (ID)',
|
label='Group (ID)',
|
||||||
)
|
)
|
||||||
group = django_filters.ModelMultipleChoiceFilter(
|
group = django_filters.ModelMultipleChoiceFilter(
|
||||||
name='group__slug',
|
field_name='group__slug',
|
||||||
queryset=RackGroup.objects.all(),
|
queryset=RackGroup.objects.all(),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label='Group',
|
label='Group',
|
||||||
@ -179,26 +181,34 @@ class RackFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
label='Tenant (ID)',
|
label='Tenant (ID)',
|
||||||
)
|
)
|
||||||
tenant = django_filters.ModelMultipleChoiceFilter(
|
tenant = django_filters.ModelMultipleChoiceFilter(
|
||||||
name='tenant__slug',
|
field_name='tenant__slug',
|
||||||
queryset=Tenant.objects.all(),
|
queryset=Tenant.objects.all(),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label='Tenant (slug)',
|
label='Tenant (slug)',
|
||||||
)
|
)
|
||||||
|
status = django_filters.MultipleChoiceFilter(
|
||||||
|
choices=RACK_STATUS_CHOICES,
|
||||||
|
null_value=None
|
||||||
|
)
|
||||||
role_id = django_filters.ModelMultipleChoiceFilter(
|
role_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
queryset=RackRole.objects.all(),
|
queryset=RackRole.objects.all(),
|
||||||
label='Role (ID)',
|
label='Role (ID)',
|
||||||
)
|
)
|
||||||
role = django_filters.ModelMultipleChoiceFilter(
|
role = django_filters.ModelMultipleChoiceFilter(
|
||||||
name='role__slug',
|
field_name='role__slug',
|
||||||
queryset=RackRole.objects.all(),
|
queryset=RackRole.objects.all(),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label='Role (slug)',
|
label='Role (slug)',
|
||||||
)
|
)
|
||||||
|
asset_tag = NullableCharFieldFilter()
|
||||||
tag = TagFilter()
|
tag = TagFilter()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Rack
|
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):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
@ -207,12 +217,16 @@ class RackFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
Q(name__icontains=value) |
|
Q(name__icontains=value) |
|
||||||
Q(facility_id__icontains=value) |
|
Q(facility_id__icontains=value) |
|
||||||
Q(serial__icontains=value.strip()) |
|
Q(serial__icontains=value.strip()) |
|
||||||
|
Q(asset_tag__icontains=value.strip()) |
|
||||||
Q(comments__icontains=value)
|
Q(comments__icontains=value)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class RackReservationFilter(django_filters.FilterSet):
|
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(
|
q = django_filters.CharFilter(
|
||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
||||||
@ -222,23 +236,23 @@ class RackReservationFilter(django_filters.FilterSet):
|
|||||||
label='Rack (ID)',
|
label='Rack (ID)',
|
||||||
)
|
)
|
||||||
site_id = django_filters.ModelMultipleChoiceFilter(
|
site_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
name='rack__site',
|
field_name='rack__site',
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
label='Site (ID)',
|
label='Site (ID)',
|
||||||
)
|
)
|
||||||
site = django_filters.ModelMultipleChoiceFilter(
|
site = django_filters.ModelMultipleChoiceFilter(
|
||||||
name='rack__site__slug',
|
field_name='rack__site__slug',
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label='Site (slug)',
|
label='Site (slug)',
|
||||||
)
|
)
|
||||||
group_id = django_filters.ModelMultipleChoiceFilter(
|
group_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
name='rack__group',
|
field_name='rack__group',
|
||||||
queryset=RackGroup.objects.all(),
|
queryset=RackGroup.objects.all(),
|
||||||
label='Group (ID)',
|
label='Group (ID)',
|
||||||
)
|
)
|
||||||
group = django_filters.ModelMultipleChoiceFilter(
|
group = django_filters.ModelMultipleChoiceFilter(
|
||||||
name='rack__group__slug',
|
field_name='rack__group__slug',
|
||||||
queryset=RackGroup.objects.all(),
|
queryset=RackGroup.objects.all(),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label='Group',
|
label='Group',
|
||||||
@ -248,7 +262,7 @@ class RackReservationFilter(django_filters.FilterSet):
|
|||||||
label='Tenant (ID)',
|
label='Tenant (ID)',
|
||||||
)
|
)
|
||||||
tenant = django_filters.ModelMultipleChoiceFilter(
|
tenant = django_filters.ModelMultipleChoiceFilter(
|
||||||
name='tenant__slug',
|
field_name='tenant__slug',
|
||||||
queryset=Tenant.objects.all(),
|
queryset=Tenant.objects.all(),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label='Tenant (slug)',
|
label='Tenant (slug)',
|
||||||
@ -258,7 +272,7 @@ class RackReservationFilter(django_filters.FilterSet):
|
|||||||
label='User (ID)',
|
label='User (ID)',
|
||||||
)
|
)
|
||||||
user = django_filters.ModelMultipleChoiceFilter(
|
user = django_filters.ModelMultipleChoiceFilter(
|
||||||
name='user',
|
field_name='user',
|
||||||
queryset=User.objects.all(),
|
queryset=User.objects.all(),
|
||||||
to_field_name='username',
|
to_field_name='username',
|
||||||
label='User (name)',
|
label='User (name)',
|
||||||
@ -286,8 +300,11 @@ class ManufacturerFilter(django_filters.FilterSet):
|
|||||||
fields = ['name', 'slug']
|
fields = ['name', 'slug']
|
||||||
|
|
||||||
|
|
||||||
class DeviceTypeFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
class DeviceTypeFilter(CustomFieldFilterSet):
|
||||||
id__in = NumericInFilter(name='id', lookup_expr='in')
|
id__in = NumericInFilter(
|
||||||
|
field_name='id',
|
||||||
|
lookup_expr='in'
|
||||||
|
)
|
||||||
q = django_filters.CharFilter(
|
q = django_filters.CharFilter(
|
||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
||||||
@ -297,18 +314,41 @@ class DeviceTypeFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
label='Manufacturer (ID)',
|
label='Manufacturer (ID)',
|
||||||
)
|
)
|
||||||
manufacturer = django_filters.ModelMultipleChoiceFilter(
|
manufacturer = django_filters.ModelMultipleChoiceFilter(
|
||||||
name='manufacturer__slug',
|
field_name='manufacturer__slug',
|
||||||
queryset=Manufacturer.objects.all(),
|
queryset=Manufacturer.objects.all(),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label='Manufacturer (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()
|
tag = TagFilter()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = DeviceType
|
model = DeviceType
|
||||||
fields = [
|
fields = [
|
||||||
'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'is_console_server', 'is_pdu',
|
'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role',
|
||||||
'is_network_device', 'subdevice_role',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
@ -321,11 +361,32 @@ class DeviceTypeFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
Q(comments__icontains=value)
|
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):
|
class DeviceTypeComponentFilterSet(django_filters.FilterSet):
|
||||||
devicetype_id = django_filters.ModelMultipleChoiceFilter(
|
devicetype_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
queryset=DeviceType.objects.all(),
|
queryset=DeviceType.objects.all(),
|
||||||
name='device_type_id',
|
field_name='device_type_id',
|
||||||
label='Device type (ID)',
|
label='Device type (ID)',
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -365,6 +426,20 @@ class InterfaceTemplateFilter(DeviceTypeComponentFilterSet):
|
|||||||
fields = ['name', 'form_factor', 'mgmt_only']
|
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 DeviceBayTemplateFilter(DeviceTypeComponentFilterSet):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -381,12 +456,12 @@ class DeviceRoleFilter(django_filters.FilterSet):
|
|||||||
|
|
||||||
class PlatformFilter(django_filters.FilterSet):
|
class PlatformFilter(django_filters.FilterSet):
|
||||||
manufacturer_id = django_filters.ModelMultipleChoiceFilter(
|
manufacturer_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
name='manufacturer',
|
field_name='manufacturer',
|
||||||
queryset=Manufacturer.objects.all(),
|
queryset=Manufacturer.objects.all(),
|
||||||
label='Manufacturer (ID)',
|
label='Manufacturer (ID)',
|
||||||
)
|
)
|
||||||
manufacturer = django_filters.ModelMultipleChoiceFilter(
|
manufacturer = django_filters.ModelMultipleChoiceFilter(
|
||||||
name='manufacturer__slug',
|
field_name='manufacturer__slug',
|
||||||
queryset=Manufacturer.objects.all(),
|
queryset=Manufacturer.objects.all(),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label='Manufacturer (slug)',
|
label='Manufacturer (slug)',
|
||||||
@ -397,19 +472,22 @@ class PlatformFilter(django_filters.FilterSet):
|
|||||||
fields = ['name', 'slug']
|
fields = ['name', 'slug']
|
||||||
|
|
||||||
|
|
||||||
class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
class DeviceFilter(CustomFieldFilterSet):
|
||||||
id__in = NumericInFilter(name='id', lookup_expr='in')
|
id__in = NumericInFilter(
|
||||||
|
field_name='id',
|
||||||
|
lookup_expr='in'
|
||||||
|
)
|
||||||
q = django_filters.CharFilter(
|
q = django_filters.CharFilter(
|
||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
||||||
)
|
)
|
||||||
manufacturer_id = django_filters.ModelMultipleChoiceFilter(
|
manufacturer_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
name='device_type__manufacturer',
|
field_name='device_type__manufacturer',
|
||||||
queryset=Manufacturer.objects.all(),
|
queryset=Manufacturer.objects.all(),
|
||||||
label='Manufacturer (ID)',
|
label='Manufacturer (ID)',
|
||||||
)
|
)
|
||||||
manufacturer = django_filters.ModelMultipleChoiceFilter(
|
manufacturer = django_filters.ModelMultipleChoiceFilter(
|
||||||
name='device_type__manufacturer__slug',
|
field_name='device_type__manufacturer__slug',
|
||||||
queryset=Manufacturer.objects.all(),
|
queryset=Manufacturer.objects.all(),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label='Manufacturer (slug)',
|
label='Manufacturer (slug)',
|
||||||
@ -419,12 +497,12 @@ class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
label='Device type (ID)',
|
label='Device type (ID)',
|
||||||
)
|
)
|
||||||
role_id = django_filters.ModelMultipleChoiceFilter(
|
role_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
name='device_role_id',
|
field_name='device_role_id',
|
||||||
queryset=DeviceRole.objects.all(),
|
queryset=DeviceRole.objects.all(),
|
||||||
label='Role (ID)',
|
label='Role (ID)',
|
||||||
)
|
)
|
||||||
role = django_filters.ModelMultipleChoiceFilter(
|
role = django_filters.ModelMultipleChoiceFilter(
|
||||||
name='device_role__slug',
|
field_name='device_role__slug',
|
||||||
queryset=DeviceRole.objects.all(),
|
queryset=DeviceRole.objects.all(),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label='Role (slug)',
|
label='Role (slug)',
|
||||||
@ -434,7 +512,7 @@ class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
label='Tenant (ID)',
|
label='Tenant (ID)',
|
||||||
)
|
)
|
||||||
tenant = django_filters.ModelMultipleChoiceFilter(
|
tenant = django_filters.ModelMultipleChoiceFilter(
|
||||||
name='tenant__slug',
|
field_name='tenant__slug',
|
||||||
queryset=Tenant.objects.all(),
|
queryset=Tenant.objects.all(),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label='Tenant (slug)',
|
label='Tenant (slug)',
|
||||||
@ -444,7 +522,7 @@ class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
label='Platform (ID)',
|
label='Platform (ID)',
|
||||||
)
|
)
|
||||||
platform = django_filters.ModelMultipleChoiceFilter(
|
platform = django_filters.ModelMultipleChoiceFilter(
|
||||||
name='platform__slug',
|
field_name='platform__slug',
|
||||||
queryset=Platform.objects.all(),
|
queryset=Platform.objects.all(),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label='Platform (slug)',
|
label='Platform (slug)',
|
||||||
@ -453,12 +531,12 @@ class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
asset_tag = NullableCharFieldFilter()
|
asset_tag = NullableCharFieldFilter()
|
||||||
region_id = django_filters.NumberFilter(
|
region_id = django_filters.NumberFilter(
|
||||||
method='filter_region',
|
method='filter_region',
|
||||||
name='pk',
|
field_name='pk',
|
||||||
label='Region (ID)',
|
label='Region (ID)',
|
||||||
)
|
)
|
||||||
region = django_filters.CharFilter(
|
region = django_filters.CharFilter(
|
||||||
method='filter_region',
|
method='filter_region',
|
||||||
name='slug',
|
field_name='slug',
|
||||||
label='Region (slug)',
|
label='Region (slug)',
|
||||||
)
|
)
|
||||||
site_id = django_filters.ModelMultipleChoiceFilter(
|
site_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
@ -466,18 +544,18 @@ class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
label='Site (ID)',
|
label='Site (ID)',
|
||||||
)
|
)
|
||||||
site = django_filters.ModelMultipleChoiceFilter(
|
site = django_filters.ModelMultipleChoiceFilter(
|
||||||
name='site__slug',
|
field_name='site__slug',
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label='Site name (slug)',
|
label='Site name (slug)',
|
||||||
)
|
)
|
||||||
rack_group_id = django_filters.ModelMultipleChoiceFilter(
|
rack_group_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
name='rack__group',
|
field_name='rack__group',
|
||||||
queryset=RackGroup.objects.all(),
|
queryset=RackGroup.objects.all(),
|
||||||
label='Rack group (ID)',
|
label='Rack group (ID)',
|
||||||
)
|
)
|
||||||
rack_id = django_filters.ModelMultipleChoiceFilter(
|
rack_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
name='rack',
|
field_name='rack',
|
||||||
queryset=Rack.objects.all(),
|
queryset=Rack.objects.all(),
|
||||||
label='Rack (ID)',
|
label='Rack (ID)',
|
||||||
)
|
)
|
||||||
@ -486,7 +564,7 @@ class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
label='VM cluster (ID)',
|
label='VM cluster (ID)',
|
||||||
)
|
)
|
||||||
model = django_filters.ModelMultipleChoiceFilter(
|
model = django_filters.ModelMultipleChoiceFilter(
|
||||||
name='device_type__slug',
|
field_name='device_type__slug',
|
||||||
queryset=DeviceType.objects.all(),
|
queryset=DeviceType.objects.all(),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label='Device model (slug)',
|
label='Device model (slug)',
|
||||||
@ -496,21 +574,9 @@ class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
null_value=None
|
null_value=None
|
||||||
)
|
)
|
||||||
is_full_depth = django_filters.BooleanFilter(
|
is_full_depth = django_filters.BooleanFilter(
|
||||||
name='device_type__is_full_depth',
|
field_name='device_type__is_full_depth',
|
||||||
label='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(
|
mac_address = django_filters.CharFilter(
|
||||||
method='_mac_address',
|
method='_mac_address',
|
||||||
label='MAC address',
|
label='MAC address',
|
||||||
@ -520,10 +586,34 @@ class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
label='Has a primary IP',
|
label='Has a primary IP',
|
||||||
)
|
)
|
||||||
virtual_chassis_id = django_filters.ModelMultipleChoiceFilter(
|
virtual_chassis_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
name='virtual_chassis',
|
field_name='virtual_chassis',
|
||||||
queryset=VirtualChassis.objects.all(),
|
queryset=VirtualChassis.objects.all(),
|
||||||
label='Virtual chassis (ID)',
|
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()
|
tag = TagFilter()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -573,6 +663,27 @@ class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
Q(primary_ip6__isnull=False)
|
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):
|
class DeviceComponentFilterSet(django_filters.FilterSet):
|
||||||
device_id = django_filters.ModelChoiceFilter(
|
device_id = django_filters.ModelChoiceFilter(
|
||||||
@ -588,54 +699,78 @@ class DeviceComponentFilterSet(django_filters.FilterSet):
|
|||||||
|
|
||||||
|
|
||||||
class ConsolePortFilter(DeviceComponentFilterSet):
|
class ConsolePortFilter(DeviceComponentFilterSet):
|
||||||
|
cabled = django_filters.BooleanFilter(
|
||||||
|
field_name='cable',
|
||||||
|
lookup_expr='isnull',
|
||||||
|
exclude=True
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ConsolePort
|
model = ConsolePort
|
||||||
fields = ['name']
|
fields = ['name', 'connection_status']
|
||||||
|
|
||||||
|
|
||||||
class ConsoleServerPortFilter(DeviceComponentFilterSet):
|
class ConsoleServerPortFilter(DeviceComponentFilterSet):
|
||||||
|
cabled = django_filters.BooleanFilter(
|
||||||
|
field_name='cable',
|
||||||
|
lookup_expr='isnull',
|
||||||
|
exclude=True
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ConsoleServerPort
|
model = ConsoleServerPort
|
||||||
fields = ['name']
|
fields = ['name', 'connection_status']
|
||||||
|
|
||||||
|
|
||||||
class PowerPortFilter(DeviceComponentFilterSet):
|
class PowerPortFilter(DeviceComponentFilterSet):
|
||||||
|
cabled = django_filters.BooleanFilter(
|
||||||
|
field_name='cable',
|
||||||
|
lookup_expr='isnull',
|
||||||
|
exclude=True
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = PowerPort
|
model = PowerPort
|
||||||
fields = ['name']
|
fields = ['name', 'connection_status']
|
||||||
|
|
||||||
|
|
||||||
class PowerOutletFilter(DeviceComponentFilterSet):
|
class PowerOutletFilter(DeviceComponentFilterSet):
|
||||||
|
cabled = django_filters.BooleanFilter(
|
||||||
|
field_name='cable',
|
||||||
|
lookup_expr='isnull',
|
||||||
|
exclude=True
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = PowerOutlet
|
model = PowerOutlet
|
||||||
fields = ['name']
|
fields = ['name', 'connection_status']
|
||||||
|
|
||||||
|
|
||||||
class InterfaceFilter(django_filters.FilterSet):
|
class InterfaceFilter(django_filters.FilterSet):
|
||||||
"""
|
"""
|
||||||
Not using DeviceComponentFilterSet for Interfaces because we need to glean the ordering logic from the parent
|
Not using DeviceComponentFilterSet for Interfaces because we need to check for VirtualChassis membership.
|
||||||
Device's DeviceType.
|
|
||||||
"""
|
"""
|
||||||
device = django_filters.CharFilter(
|
device = django_filters.CharFilter(
|
||||||
method='filter_device',
|
method='filter_device',
|
||||||
name='name',
|
field_name='name',
|
||||||
label='Device',
|
label='Device',
|
||||||
)
|
)
|
||||||
device_id = django_filters.NumberFilter(
|
device_id = django_filters.NumberFilter(
|
||||||
method='filter_device',
|
method='filter_device',
|
||||||
name='pk',
|
field_name='pk',
|
||||||
label='Device (ID)',
|
label='Device (ID)',
|
||||||
)
|
)
|
||||||
|
cabled = django_filters.BooleanFilter(
|
||||||
|
field_name='cable',
|
||||||
|
lookup_expr='isnull',
|
||||||
|
exclude=True
|
||||||
|
)
|
||||||
type = django_filters.CharFilter(
|
type = django_filters.CharFilter(
|
||||||
method='filter_type',
|
method='filter_type',
|
||||||
label='Interface type',
|
label='Interface type',
|
||||||
)
|
)
|
||||||
lag_id = django_filters.ModelMultipleChoiceFilter(
|
lag_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
name='lag',
|
field_name='lag',
|
||||||
queryset=Interface.objects.all(),
|
queryset=Interface.objects.all(),
|
||||||
label='LAG interface (ID)',
|
label='LAG interface (ID)',
|
||||||
)
|
)
|
||||||
@ -659,14 +794,13 @@ class InterfaceFilter(django_filters.FilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Interface
|
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):
|
def filter_device(self, queryset, name, value):
|
||||||
try:
|
try:
|
||||||
device = Device.objects.select_related('device_type').get(**{name: value})
|
device = Device.objects.get(**{name: value})
|
||||||
vc_interface_ids = [i['id'] for i in device.vc_interfaces.values('id')]
|
vc_interface_ids = device.vc_interfaces.values_list('id', flat=True)
|
||||||
ordering = device.device_type.interface_ordering
|
return queryset.filter(pk__in=vc_interface_ids)
|
||||||
return queryset.filter(pk__in=vc_interface_ids).order_naturally(ordering)
|
|
||||||
except Device.DoesNotExist:
|
except Device.DoesNotExist:
|
||||||
return queryset.none()
|
return queryset.none()
|
||||||
|
|
||||||
@ -708,6 +842,30 @@ class InterfaceFilter(django_filters.FilterSet):
|
|||||||
return queryset.none()
|
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 DeviceBayFilter(DeviceComponentFilterSet):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -738,7 +896,7 @@ class InventoryItemFilter(DeviceComponentFilterSet):
|
|||||||
label='Manufacturer (ID)',
|
label='Manufacturer (ID)',
|
||||||
)
|
)
|
||||||
manufacturer = django_filters.ModelMultipleChoiceFilter(
|
manufacturer = django_filters.ModelMultipleChoiceFilter(
|
||||||
name='manufacturer__slug',
|
field_name='manufacturer__slug',
|
||||||
queryset=Manufacturer.objects.all(),
|
queryset=Manufacturer.objects.all(),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label='Manufacturer (slug)',
|
label='Manufacturer (slug)',
|
||||||
@ -768,23 +926,23 @@ class VirtualChassisFilter(django_filters.FilterSet):
|
|||||||
label='Search',
|
label='Search',
|
||||||
)
|
)
|
||||||
site_id = django_filters.ModelMultipleChoiceFilter(
|
site_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
name='master__site',
|
field_name='master__site',
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
label='Site (ID)',
|
label='Site (ID)',
|
||||||
)
|
)
|
||||||
site = django_filters.ModelMultipleChoiceFilter(
|
site = django_filters.ModelMultipleChoiceFilter(
|
||||||
name='master__site__slug',
|
field_name='master__site__slug',
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label='Site name (slug)',
|
label='Site name (slug)',
|
||||||
)
|
)
|
||||||
tenant_id = django_filters.ModelMultipleChoiceFilter(
|
tenant_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
name='master__tenant',
|
field_name='master__tenant',
|
||||||
queryset=Tenant.objects.all(),
|
queryset=Tenant.objects.all(),
|
||||||
label='Tenant (ID)',
|
label='Tenant (ID)',
|
||||||
)
|
)
|
||||||
tenant = django_filters.ModelMultipleChoiceFilter(
|
tenant = django_filters.ModelMultipleChoiceFilter(
|
||||||
name='master__tenant__slug',
|
field_name='master__tenant__slug',
|
||||||
queryset=Tenant.objects.all(),
|
queryset=Tenant.objects.all(),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label='Tenant (slug)',
|
label='Tenant (slug)',
|
||||||
@ -805,6 +963,28 @@ class VirtualChassisFilter(django_filters.FilterSet):
|
|||||||
return queryset.filter(qs_filter)
|
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):
|
class ConsoleConnectionFilter(django_filters.FilterSet):
|
||||||
site = django_filters.CharFilter(
|
site = django_filters.CharFilter(
|
||||||
method='filter_site',
|
method='filter_site',
|
||||||
@ -822,14 +1002,14 @@ class ConsoleConnectionFilter(django_filters.FilterSet):
|
|||||||
def filter_site(self, queryset, name, value):
|
def filter_site(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
return queryset
|
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):
|
def filter_device(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
return queryset
|
return queryset
|
||||||
return queryset.filter(
|
return queryset.filter(
|
||||||
Q(device__name__icontains=value) |
|
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):
|
def filter_site(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
return queryset
|
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):
|
def filter_device(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
return queryset
|
return queryset
|
||||||
return queryset.filter(
|
return queryset.filter(
|
||||||
Q(device__name__icontains=value) |
|
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:
|
class Meta:
|
||||||
model = InterfaceConnection
|
model = Interface
|
||||||
fields = ['connection_status']
|
fields = ['connection_status']
|
||||||
|
|
||||||
def filter_site(self, queryset, name, value):
|
def filter_site(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
return queryset
|
return queryset
|
||||||
return queryset.filter(
|
return queryset.filter(
|
||||||
Q(interface_a__device__site__slug=value) |
|
Q(device__site__slug=value) |
|
||||||
Q(interface_b__device__site__slug=value)
|
Q(_connected_interface__device__site__slug=value)
|
||||||
)
|
)
|
||||||
|
|
||||||
def filter_device(self, queryset, name, value):
|
def filter_device(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
return queryset
|
return queryset
|
||||||
return queryset.filter(
|
return queryset.filter(
|
||||||
Q(interface_a__device__name__icontains=value) |
|
Q(device__name__icontains=value) |
|
||||||
Q(interface_b__device__name__icontains=value)
|
Q(_connected_interface__device__name__icontains=value)
|
||||||
)
|
)
|
||||||
|
@ -76,10 +76,7 @@
|
|||||||
"model": "MX960",
|
"model": "MX960",
|
||||||
"slug": "mx960",
|
"slug": "mx960",
|
||||||
"u_height": 16,
|
"u_height": 16,
|
||||||
"is_full_depth": true,
|
"is_full_depth": true
|
||||||
"is_console_server": false,
|
|
||||||
"is_pdu": false,
|
|
||||||
"is_network_device": true
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -92,10 +89,7 @@
|
|||||||
"model": "EX9214",
|
"model": "EX9214",
|
||||||
"slug": "ex9214",
|
"slug": "ex9214",
|
||||||
"u_height": 16,
|
"u_height": 16,
|
||||||
"is_full_depth": true,
|
"is_full_depth": true
|
||||||
"is_console_server": false,
|
|
||||||
"is_pdu": false,
|
|
||||||
"is_network_device": true
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -108,10 +102,7 @@
|
|||||||
"model": "QFX5100-24Q",
|
"model": "QFX5100-24Q",
|
||||||
"slug": "qfx5100-24q",
|
"slug": "qfx5100-24q",
|
||||||
"u_height": 1,
|
"u_height": 1,
|
||||||
"is_full_depth": true,
|
"is_full_depth": true
|
||||||
"is_console_server": false,
|
|
||||||
"is_pdu": false,
|
|
||||||
"is_network_device": true
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -124,10 +115,7 @@
|
|||||||
"model": "QFX5100-48S",
|
"model": "QFX5100-48S",
|
||||||
"slug": "qfx5100-48s",
|
"slug": "qfx5100-48s",
|
||||||
"u_height": 1,
|
"u_height": 1,
|
||||||
"is_full_depth": true,
|
"is_full_depth": true
|
||||||
"is_console_server": false,
|
|
||||||
"is_pdu": false,
|
|
||||||
"is_network_device": true
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -140,10 +128,7 @@
|
|||||||
"model": "CM4148",
|
"model": "CM4148",
|
||||||
"slug": "cm4148",
|
"slug": "cm4148",
|
||||||
"u_height": 1,
|
"u_height": 1,
|
||||||
"is_full_depth": true,
|
"is_full_depth": true
|
||||||
"is_console_server": true,
|
|
||||||
"is_pdu": false,
|
|
||||||
"is_network_device": false
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -156,10 +141,7 @@
|
|||||||
"model": "CWG-24VYM415C9",
|
"model": "CWG-24VYM415C9",
|
||||||
"slug": "cwg-24vym415c9",
|
"slug": "cwg-24vym415c9",
|
||||||
"u_height": 0,
|
"u_height": 0,
|
||||||
"is_full_depth": false,
|
"is_full_depth": false
|
||||||
"is_console_server": false,
|
|
||||||
"is_pdu": true,
|
|
||||||
"is_network_device": false
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1903,8 +1885,7 @@
|
|||||||
"pk": 1,
|
"pk": 1,
|
||||||
"fields": {
|
"fields": {
|
||||||
"name": "Juniper Junos",
|
"name": "Juniper Junos",
|
||||||
"slug": "juniper-junos",
|
"slug": "juniper-junos"
|
||||||
"rpc_client": "juniper-junos"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1912,8 +1893,7 @@
|
|||||||
"pk": 2,
|
"pk": 2,
|
||||||
"fields": {
|
"fields": {
|
||||||
"name": "Opengear",
|
"name": "Opengear",
|
||||||
"slug": "opengear",
|
"slug": "opengear"
|
||||||
"rpc_client": "opengear"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -2153,7 +2133,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"device": 1,
|
"device": 1,
|
||||||
"name": "Console (RE0)",
|
"name": "Console (RE0)",
|
||||||
"cs_port": 27,
|
"connected_endpoint": 27,
|
||||||
"connection_status": true
|
"connection_status": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2163,7 +2143,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"device": 1,
|
"device": 1,
|
||||||
"name": "Console (RE1)",
|
"name": "Console (RE1)",
|
||||||
"cs_port": 38,
|
"connected_endpoint": 38,
|
||||||
"connection_status": true
|
"connection_status": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2173,7 +2153,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"device": 2,
|
"device": 2,
|
||||||
"name": "Console (RE0)",
|
"name": "Console (RE0)",
|
||||||
"cs_port": 5,
|
"connected_endpoint": 5,
|
||||||
"connection_status": true
|
"connection_status": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2183,7 +2163,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"device": 2,
|
"device": 2,
|
||||||
"name": "Console (RE1)",
|
"name": "Console (RE1)",
|
||||||
"cs_port": 16,
|
"connected_endpoint": 16,
|
||||||
"connection_status": true
|
"connection_status": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2193,7 +2173,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"device": 3,
|
"device": 3,
|
||||||
"name": "Console",
|
"name": "Console",
|
||||||
"cs_port": 49,
|
"connected_endpoint": 49,
|
||||||
"connection_status": true
|
"connection_status": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2203,7 +2183,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"device": 4,
|
"device": 4,
|
||||||
"name": "Console",
|
"name": "Console",
|
||||||
"cs_port": 48,
|
"connected_endpoint": 48,
|
||||||
"connection_status": true
|
"connection_status": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2213,7 +2193,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"device": 5,
|
"device": 5,
|
||||||
"name": "Console",
|
"name": "Console",
|
||||||
"cs_port": null,
|
"connected_endpoint": null,
|
||||||
"connection_status": true
|
"connection_status": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2223,7 +2203,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"device": 6,
|
"device": 6,
|
||||||
"name": "Console",
|
"name": "Console",
|
||||||
"cs_port": null,
|
"connected_endpoint": null,
|
||||||
"connection_status": true
|
"connection_status": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2233,7 +2213,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"device": 7,
|
"device": 7,
|
||||||
"name": "Console (RE0)",
|
"name": "Console (RE0)",
|
||||||
"cs_port": null,
|
"connected_endpoint": null,
|
||||||
"connection_status": true
|
"connection_status": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2243,7 +2223,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"device": 7,
|
"device": 7,
|
||||||
"name": "Console (RE1)",
|
"name": "Console (RE1)",
|
||||||
"cs_port": null,
|
"connected_endpoint": null,
|
||||||
"connection_status": true
|
"connection_status": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2253,7 +2233,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"device": 8,
|
"device": 8,
|
||||||
"name": "Console (RE0)",
|
"name": "Console (RE0)",
|
||||||
"cs_port": null,
|
"connected_endpoint": null,
|
||||||
"connection_status": true
|
"connection_status": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2263,7 +2243,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"device": 8,
|
"device": 8,
|
||||||
"name": "Console (RE1)",
|
"name": "Console (RE1)",
|
||||||
"cs_port": null,
|
"connected_endpoint": null,
|
||||||
"connection_status": true
|
"connection_status": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2273,7 +2253,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"device": 9,
|
"device": 9,
|
||||||
"name": "Console",
|
"name": "Console",
|
||||||
"cs_port": null,
|
"connected_endpoint": null,
|
||||||
"connection_status": true
|
"connection_status": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2283,7 +2263,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"device": 11,
|
"device": 11,
|
||||||
"name": "Serial",
|
"name": "Serial",
|
||||||
"cs_port": null,
|
"connected_endpoint": null,
|
||||||
"connection_status": true
|
"connection_status": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2293,7 +2273,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"device": 12,
|
"device": 12,
|
||||||
"name": "Serial",
|
"name": "Serial",
|
||||||
"cs_port": null,
|
"connected_endpoint": null,
|
||||||
"connection_status": true
|
"connection_status": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2687,7 +2667,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"device": 1,
|
"device": 1,
|
||||||
"name": "PEM0",
|
"name": "PEM0",
|
||||||
"power_outlet": 25,
|
"connected_endpoint": 25,
|
||||||
"connection_status": true
|
"connection_status": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2697,7 +2677,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"device": 1,
|
"device": 1,
|
||||||
"name": "PEM1",
|
"name": "PEM1",
|
||||||
"power_outlet": 49,
|
"connected_endpoint": 49,
|
||||||
"connection_status": true
|
"connection_status": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2707,7 +2687,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"device": 1,
|
"device": 1,
|
||||||
"name": "PEM2",
|
"name": "PEM2",
|
||||||
"power_outlet": null,
|
"connected_endpoint": null,
|
||||||
"connection_status": true
|
"connection_status": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2717,7 +2697,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"device": 1,
|
"device": 1,
|
||||||
"name": "PEM3",
|
"name": "PEM3",
|
||||||
"power_outlet": null,
|
"connected_endpoint": null,
|
||||||
"connection_status": true
|
"connection_status": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2727,7 +2707,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"device": 2,
|
"device": 2,
|
||||||
"name": "PEM0",
|
"name": "PEM0",
|
||||||
"power_outlet": 26,
|
"connected_endpoint": 26,
|
||||||
"connection_status": true
|
"connection_status": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2737,7 +2717,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"device": 2,
|
"device": 2,
|
||||||
"name": "PEM1",
|
"name": "PEM1",
|
||||||
"power_outlet": 50,
|
"connected_endpoint": 50,
|
||||||
"connection_status": true
|
"connection_status": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2747,7 +2727,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"device": 2,
|
"device": 2,
|
||||||
"name": "PEM2",
|
"name": "PEM2",
|
||||||
"power_outlet": null,
|
"connected_endpoint": null,
|
||||||
"connection_status": true
|
"connection_status": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2757,7 +2737,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"device": 2,
|
"device": 2,
|
||||||
"name": "PEM3",
|
"name": "PEM3",
|
||||||
"power_outlet": null,
|
"connected_endpoint": null,
|
||||||
"connection_status": true
|
"connection_status": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2767,7 +2747,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"device": 4,
|
"device": 4,
|
||||||
"name": "PSU0",
|
"name": "PSU0",
|
||||||
"power_outlet": 28,
|
"connected_endpoint": 28,
|
||||||
"connection_status": true
|
"connection_status": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2777,7 +2757,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"device": 4,
|
"device": 4,
|
||||||
"name": "PSU1",
|
"name": "PSU1",
|
||||||
"power_outlet": 52,
|
"connected_endpoint": 52,
|
||||||
"connection_status": true
|
"connection_status": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2787,7 +2767,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"device": 5,
|
"device": 5,
|
||||||
"name": "PSU0",
|
"name": "PSU0",
|
||||||
"power_outlet": 56,
|
"connected_endpoint": 56,
|
||||||
"connection_status": true
|
"connection_status": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2797,7 +2777,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"device": 5,
|
"device": 5,
|
||||||
"name": "PSU1",
|
"name": "PSU1",
|
||||||
"power_outlet": 32,
|
"connected_endpoint": 32,
|
||||||
"connection_status": true
|
"connection_status": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2807,7 +2787,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"device": 3,
|
"device": 3,
|
||||||
"name": "PSU0",
|
"name": "PSU0",
|
||||||
"power_outlet": 27,
|
"connected_endpoint": 27,
|
||||||
"connection_status": true
|
"connection_status": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2817,7 +2797,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"device": 3,
|
"device": 3,
|
||||||
"name": "PSU1",
|
"name": "PSU1",
|
||||||
"power_outlet": 51,
|
"connected_endpoint": 51,
|
||||||
"connection_status": true
|
"connection_status": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2827,7 +2807,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"device": 7,
|
"device": 7,
|
||||||
"name": "PEM0",
|
"name": "PEM0",
|
||||||
"power_outlet": 53,
|
"connected_endpoint": 53,
|
||||||
"connection_status": true
|
"connection_status": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2837,7 +2817,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"device": 7,
|
"device": 7,
|
||||||
"name": "PEM1",
|
"name": "PEM1",
|
||||||
"power_outlet": 29,
|
"connected_endpoint": 29,
|
||||||
"connection_status": true
|
"connection_status": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2847,7 +2827,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"device": 7,
|
"device": 7,
|
||||||
"name": "PEM2",
|
"name": "PEM2",
|
||||||
"power_outlet": null,
|
"connected_endpoint": null,
|
||||||
"connection_status": true
|
"connection_status": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2857,7 +2837,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"device": 7,
|
"device": 7,
|
||||||
"name": "PEM3",
|
"name": "PEM3",
|
||||||
"power_outlet": null,
|
"connected_endpoint": null,
|
||||||
"connection_status": true
|
"connection_status": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2867,7 +2847,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"device": 8,
|
"device": 8,
|
||||||
"name": "PEM0",
|
"name": "PEM0",
|
||||||
"power_outlet": 54,
|
"connected_endpoint": 54,
|
||||||
"connection_status": true
|
"connection_status": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2877,7 +2857,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"device": 8,
|
"device": 8,
|
||||||
"name": "PEM1",
|
"name": "PEM1",
|
||||||
"power_outlet": 30,
|
"connected_endpoint": 30,
|
||||||
"connection_status": true
|
"connection_status": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2887,7 +2867,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"device": 8,
|
"device": 8,
|
||||||
"name": "PEM2",
|
"name": "PEM2",
|
||||||
"power_outlet": null,
|
"connected_endpoint": null,
|
||||||
"connection_status": true
|
"connection_status": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2897,7 +2877,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"device": 8,
|
"device": 8,
|
||||||
"name": "PEM3",
|
"name": "PEM3",
|
||||||
"power_outlet": null,
|
"connected_endpoint": null,
|
||||||
"connection_status": true
|
"connection_status": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2907,7 +2887,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"device": 6,
|
"device": 6,
|
||||||
"name": "PSU0",
|
"name": "PSU0",
|
||||||
"power_outlet": 55,
|
"connected_endpoint": 55,
|
||||||
"connection_status": true
|
"connection_status": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2917,7 +2897,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"device": 6,
|
"device": 6,
|
||||||
"name": "PSU1",
|
"name": "PSU1",
|
||||||
"power_outlet": 31,
|
"connected_endpoint": 31,
|
||||||
"connection_status": true
|
"connection_status": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2927,7 +2907,7 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"device": 9,
|
"device": 9,
|
||||||
"name": "PSU",
|
"name": "PSU",
|
||||||
"power_outlet": null,
|
"connected_endpoint": null,
|
||||||
"connection_status": true
|
"connection_status": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -5748,158 +5728,5 @@
|
|||||||
"mgmt_only": true,
|
"mgmt_only": true,
|
||||||
"description": ""
|
"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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -149,8 +149,7 @@
|
|||||||
"pk": 1,
|
"pk": 1,
|
||||||
"fields": {
|
"fields": {
|
||||||
"name": "Cisco IOS",
|
"name": "Cisco IOS",
|
||||||
"slug": "cisco-ios",
|
"slug": "cisco-ios"
|
||||||
"rpc_client": "cisco-ios"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -158,8 +157,7 @@
|
|||||||
"pk": 2,
|
"pk": 2,
|
||||||
"fields": {
|
"fields": {
|
||||||
"name": "Cisco NX-OS",
|
"name": "Cisco NX-OS",
|
||||||
"slug": "cisco-nx-os",
|
"slug": "cisco-nx-os"
|
||||||
"rpc_client": ""
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -167,8 +165,7 @@
|
|||||||
"pk": 3,
|
"pk": 3,
|
||||||
"fields": {
|
"fields": {
|
||||||
"name": "Juniper Junos",
|
"name": "Juniper Junos",
|
||||||
"slug": "juniper-junos",
|
"slug": "juniper-junos"
|
||||||
"rpc_client": "juniper-junos"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -176,8 +173,7 @@
|
|||||||
"pk": 4,
|
"pk": 4,
|
||||||
"fields": {
|
"fields": {
|
||||||
"name": "Arista EOS",
|
"name": "Arista EOS",
|
||||||
"slug": "arista-eos",
|
"slug": "arista-eos"
|
||||||
"rpc_client": ""
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -185,8 +181,7 @@
|
|||||||
"pk": 5,
|
"pk": 5,
|
||||||
"fields": {
|
"fields": {
|
||||||
"name": "Linux",
|
"name": "Linux",
|
||||||
"slug": "linux",
|
"slug": "linux"
|
||||||
"rpc_client": ""
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -194,8 +189,7 @@
|
|||||||
"pk": 6,
|
"pk": 6,
|
||||||
"fields": {
|
"fields": {
|
||||||
"name": "Opengear",
|
"name": "Opengear",
|
||||||
"slug": "opengear",
|
"slug": "opengear"
|
||||||
"rpc_client": "opengear"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
2053
netbox/dcim/forms.py
2053
netbox/dcim/forms.py
File diff suppressed because it is too large
Load Diff
85
netbox/dcim/managers.py
Normal file
85
netbox/dcim/managers.py
Normal 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)
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.9.7 on 2016-06-22 18:21
|
# Generated by Django 1.9.7 on 2016-06-22 18:21
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
import django.core.validators
|
import django.core.validators
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.9.7 on 2016-06-22 18:21
|
# Generated by Django 1.9.7 on 2016-06-22 18:21
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.11.14 on 2018-07-31 02:06
|
# Generated by Django 1.11.14 on 2018-07-31 02:06
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
import dcim.fields
|
import dcim.fields
|
||||||
import django.core.validators
|
import django.core.validators
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.9.7 on 2016-06-28 17:21
|
# Generated by Django 1.9.7 on 2016-06-28 17:21
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.9.7 on 2016-07-01 20:49
|
# Generated by Django 1.9.7 on 2016-07-01 20:49
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.9.7 on 2016-07-06 17:22
|
# Generated by Django 1.9.7 on 2016-07-06 17:22
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
import dcim.fields
|
import dcim.fields
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.9.7 on 2016-07-11 18:40
|
# Generated by Django 1.9.7 on 2016-07-11 18:40
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.9.7 on 2016-07-11 18:40
|
# Generated by Django 1.9.7 on 2016-07-11 18:40
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.9.7 on 2016-07-11 19:01
|
# Generated by Django 1.9.7 on 2016-07-11 19:01
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.9.7 on 2016-07-13 19:24
|
# Generated by Django 1.9.7 on 2016-07-13 19:24
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
import dcim.fields
|
import dcim.fields
|
||||||
from django.db import migrations
|
from django.db import migrations
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.9.7 on 2016-07-14 21:38
|
# Generated by Django 1.9.7 on 2016-07-14 21:38
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.9.8 on 2016-07-26 15:05
|
# Generated by Django 1.9.8 on 2016-07-26 15:05
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.9.8 on 2016-07-26 21:59
|
# Generated by Django 1.9.8 on 2016-07-26 21:59
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.9.8 on 2016-08-06 20:24
|
# Generated by Django 1.9.8 on 2016-08-06 20:24
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.9.8 on 2016-08-08 21:11
|
# Generated by Django 1.9.8 on 2016-08-08 21:11
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.9.8 on 2016-08-09 21:18
|
# Generated by Django 1.9.8 on 2016-08-09 21:18
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
import django.core.validators
|
import django.core.validators
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.9.8 on 2016-08-10 13:45
|
# Generated by Django 1.9.8 on 2016-08-10 13:45
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.9.8 on 2016-08-10 14:58
|
# Generated by Django 1.9.8 on 2016-08-10 14:58
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.10 on 2016-08-11 15:42
|
# Generated by Django 1.10 on 2016-08-11 15:42
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations
|
from django.db import migrations
|
||||||
import utilities.fields
|
import utilities.fields
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.10 on 2016-09-13 15:20
|
# Generated by Django 1.10 on 2016-09-13 15:20
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.10 on 2016-10-28 15:01
|
# Generated by Django 1.10 on 2016-10-28 15:01
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.10 on 2016-10-31 18:47
|
# Generated by Django 1.10 on 2016-10-31 18:47
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
import django.core.validators
|
import django.core.validators
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.10 on 2016-12-06 16:35
|
# Generated by Django 1.10 on 2016-12-06 16:35
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations
|
from django.db import migrations
|
||||||
import utilities.fields
|
import utilities.fields
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.10 on 2016-12-16 16:08
|
# Generated by Django 1.10 on 2016-12-16 16:08
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.11.14 on 2018-07-31 02:13
|
# Generated by Django 1.11.14 on 2018-07-31 02:13
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
import dcim.fields
|
import dcim.fields
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
import django.contrib.postgres.fields
|
import django.contrib.postgres.fields
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.10.4 on 2016-12-29 16:23
|
# Generated by Django 1.10.4 on 2016-12-29 16:23
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.10.4 on 2017-01-06 16:56
|
# Generated by Django 1.10.4 on 2017-01-06 16:56
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.10.4 on 2017-02-16 18:43
|
# Generated by Django 1.10.4 on 2017-02-16 18:43
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
import django.contrib.postgres.fields
|
import django.contrib.postgres.fields
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.10.4 on 2017-02-16 21:21
|
# Generated by Django 1.10.4 on 2017-02-16 21:21
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.10.4 on 2017-02-16 21:23
|
# Generated by Django 1.10.4 on 2017-02-16 21:23
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.10.4 on 2017-02-16 21:25
|
# Generated by Django 1.10.4 on 2017-02-16 21:25
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.10.4 on 2017-02-27 19:55
|
# Generated by Django 1.10.4 on 2017-02-27 19:55
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.10.4 on 2017-02-28 17:14
|
# Generated by Django 1.10.4 on 2017-02-28 17:14
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
import mptt.fields
|
import mptt.fields
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.10.4 on 2017-03-02 15:09
|
# Generated by Django 1.10.4 on 2017-03-02 15:09
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations
|
from django.db import migrations
|
||||||
import utilities.fields
|
import utilities.fields
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.10.6 on 2017-03-17 18:39
|
# Generated by Django 1.10.6 on 2017-03-17 18:39
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.10.6 on 2017-03-21 14:55
|
# Generated by Django 1.10.6 on 2017-03-21 14:55
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.10.7 on 2017-05-08 15:57
|
# Generated by Django 1.10.7 on 2017-05-08 15:57
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.11.1 on 2017-05-09 16:00
|
# Generated by Django 1.11.1 on 2017-05-09 16:00
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.11 on 2017-05-24 15:34
|
# Generated by Django 1.11 on 2017-05-24 15:34
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
import dcim.fields
|
import dcim.fields
|
||||||
import django.core.validators
|
import django.core.validators
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.11.1 on 2017-06-16 21:38
|
# Generated by Django 1.11.1 on 2017-06-16 21:38
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.11.1 on 2017-06-23 17:05
|
# Generated by Django 1.11.1 on 2017-06-23 17:05
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.11 on 2017-06-23 20:44
|
# Generated by Django 1.11 on 2017-06-23 20:44
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
import utilities.fields
|
import utilities.fields
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.11.3 on 2017-07-14 17:26
|
# Generated by Django 1.11.3 on 2017-07-14 17:26
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.11.4 on 2017-08-29 21:00
|
# Generated by Django 1.11.4 on 2017-08-29 21:00
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.11.4 on 2017-08-29 21:26
|
# Generated by Django 1.11.4 on 2017-08-29 21:26
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.11.4 on 2017-08-31 14:15
|
# Generated by Django 1.11.4 on 2017-08-31 14:15
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.11.14 on 2018-07-31 02:17
|
# Generated by Django 1.11.14 on 2018-07-31 02:17
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
import django.core.validators
|
import django.core.validators
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.11.4 on 2017-09-29 16:09
|
# Generated by Django 1.11.4 on 2017-09-29 16:09
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.11.4 on 2017-10-09 17:43
|
# Generated by Django 1.11.4 on 2017-10-09 17:43
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations
|
from django.db import migrations
|
||||||
import utilities.fields
|
import utilities.fields
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user