mirror of
https://github.com/netbox-community/netbox.git
synced 2025-12-27 23:57:46 -06:00
Compare commits
298 Commits
v2.4.2
...
v2.5-beta2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3f1b42d466 | ||
|
|
7d8ae5e763 | ||
|
|
2bae50f501 | ||
|
|
a46f68c6e4 | ||
|
|
d59be2912e | ||
|
|
240d22696f | ||
|
|
2b1516ea79 | ||
|
|
89622f1ddf | ||
|
|
874acab90f | ||
|
|
34bfb899d1 | ||
|
|
55c153c5a9 | ||
|
|
c29ae9b785 | ||
|
|
5ce955a719 | ||
|
|
8c3a294384 | ||
|
|
55cc327e05 | ||
|
|
a324638f1f | ||
|
|
3366a6ae3d | ||
|
|
7dde370ee1 | ||
|
|
dfe6ba5603 | ||
|
|
fd9b2f2fda | ||
|
|
3c0181ef35 | ||
|
|
641254b23a | ||
|
|
63bd48003e | ||
|
|
23cde65add | ||
|
|
408f632636 | ||
|
|
83be0b5db4 | ||
|
|
7bed48f5fe | ||
|
|
2fce7ebd8f | ||
|
|
f8e6cfbeba | ||
|
|
fc41359df6 | ||
|
|
5649024d93 | ||
|
|
65bc8f0254 | ||
|
|
7887a70b70 | ||
|
|
8a6913fe19 | ||
|
|
7cd0e0b244 | ||
|
|
9543b5e716 | ||
|
|
bc8dbfde7c | ||
|
|
0c33af2140 | ||
|
|
b6a256dc5d | ||
|
|
5785fb6ba2 | ||
|
|
59589fdd29 | ||
|
|
75f0d8ee90 | ||
|
|
04ae6ec7af | ||
|
|
3bbf4a3352 | ||
|
|
0316072863 | ||
|
|
845d467fd9 | ||
|
|
be5bf6b711 | ||
|
|
788847edaa | ||
|
|
61ca7ee7c2 | ||
|
|
30f8fb4c11 | ||
|
|
3e92aa9fe7 | ||
|
|
bb5432de7d | ||
|
|
21fd889810 | ||
|
|
a228f1e1c2 | ||
|
|
4b5181d640 | ||
|
|
0dee55885b | ||
|
|
1e36a884fa | ||
|
|
d4e266d48c | ||
|
|
69d829ce8d | ||
|
|
c1838104ae | ||
|
|
c716ca1e87 | ||
|
|
c063961e4a | ||
|
|
f61cea695b | ||
|
|
e806b58ac9 | ||
|
|
338ce737c8 | ||
|
|
f41f141130 | ||
|
|
9f0169286b | ||
|
|
60452a7b0c | ||
|
|
6140dd955a | ||
|
|
5e06c5a9ea | ||
|
|
ad4e1f01aa | ||
|
|
bb114ea66b | ||
|
|
ce00226bc6 | ||
|
|
8bad25b860 | ||
|
|
99edb8b8d5 | ||
|
|
b4998f4b01 | ||
|
|
51295389c6 | ||
|
|
5baf86dc89 | ||
|
|
fd4a9db13e | ||
|
|
e243234c4e | ||
|
|
817dc89279 | ||
|
|
798a87b31e | ||
|
|
4d47d848c5 | ||
|
|
bd3ccfe020 | ||
|
|
ded90df01b | ||
|
|
ce7930abfd | ||
|
|
82b4aad585 | ||
|
|
f321e2c705 | ||
|
|
0c86fd89ca | ||
|
|
01628cdd31 | ||
|
|
e57b3bc4ab | ||
|
|
82076d3ca6 | ||
|
|
46e594f1f1 | ||
|
|
e8caa46484 | ||
|
|
1847e82d04 | ||
|
|
045856c9ac | ||
|
|
53b6a07a41 | ||
|
|
976388f29c | ||
|
|
e5f12109c5 | ||
|
|
319869c22e | ||
|
|
66ef5c726c | ||
|
|
f8a961b568 | ||
|
|
521a989d23 | ||
|
|
0a71c63583 | ||
|
|
b05171b126 | ||
|
|
9804db4551 | ||
|
|
516faac73f | ||
|
|
29d546a3f1 | ||
|
|
bb5c0989f3 | ||
|
|
3ea2978df4 | ||
|
|
cf9c8f35a0 | ||
|
|
cefec1be60 | ||
|
|
16d8308fa8 | ||
|
|
af95ba48aa | ||
|
|
43446cc642 | ||
|
|
c60c5502f6 | ||
|
|
4ffe1866c5 | ||
|
|
d4ec309d68 | ||
|
|
fa6c4db13b | ||
|
|
91ce6c2420 | ||
|
|
62da0778ee | ||
|
|
7ffb5f16dd | ||
|
|
623de7d210 | ||
|
|
1119209fa0 | ||
|
|
c3db7191d2 | ||
|
|
2af3685dae | ||
|
|
841ddd5d83 | ||
|
|
a5f0deaea3 | ||
|
|
105f9365bf | ||
|
|
903dfb6f07 | ||
|
|
571a3e9677 | ||
|
|
79c110df39 | ||
|
|
ad03a0925b | ||
|
|
5e7fccb64e | ||
|
|
3bc6fc5e24 | ||
|
|
849db8fe44 | ||
|
|
55c632ace7 | ||
|
|
cd243a90d0 | ||
|
|
4e9ace39ce | ||
|
|
18628166dc | ||
|
|
f7e23d912c | ||
|
|
121a6afb45 | ||
|
|
c2656c4ce1 | ||
|
|
d22c23290f | ||
|
|
8992c57039 | ||
|
|
939a52dcbd | ||
|
|
6f788fcd23 | ||
|
|
f2f9ff5cbd | ||
|
|
470aabe1d7 | ||
|
|
669fe1f192 | ||
|
|
62ca511619 | ||
|
|
10492de50d | ||
|
|
e1e9a451da | ||
|
|
60f5371b31 | ||
|
|
aad4f58633 | ||
|
|
2fa1c81070 | ||
|
|
3420b5fbdf | ||
|
|
4df74780b8 | ||
|
|
cbfb25f003 | ||
|
|
e3dc12338b | ||
|
|
e75ef5fd2d | ||
|
|
8ebf7a7ad0 | ||
|
|
d7766b9828 | ||
|
|
427226de8c | ||
|
|
cb1bdccf51 | ||
|
|
1ab4344537 | ||
|
|
17a71d3e26 | ||
|
|
05431aa83b | ||
|
|
e21b23cd98 | ||
|
|
dc67028bd9 | ||
|
|
8f8df2b8e8 | ||
|
|
8573c8b8cb | ||
|
|
33cdecbc52 | ||
|
|
0c7510c1a8 | ||
|
|
94946c5854 | ||
|
|
83dec3b587 | ||
|
|
3518d023dc | ||
|
|
4ad7882762 | ||
|
|
957de0cd98 | ||
|
|
6bea8cc546 | ||
|
|
2d90fc608e | ||
|
|
f134a6ec63 | ||
|
|
266429101b | ||
|
|
b19e2037af | ||
|
|
e79a156e1f | ||
|
|
35f80f5085 | ||
|
|
47c523a40b | ||
|
|
cca44d44f7 | ||
|
|
54c917beff | ||
|
|
9985f2cb82 | ||
|
|
f30367e094 | ||
|
|
2ec8dc8319 | ||
|
|
1595a5ecd7 | ||
|
|
ea0de629df | ||
|
|
471bddea09 | ||
|
|
e97708ada0 | ||
|
|
a36b120c8b | ||
|
|
4cfe9fe182 | ||
|
|
59af8cc924 | ||
|
|
d908dffab7 | ||
|
|
ea5121ffe1 | ||
|
|
0bb5d229e8 | ||
|
|
409a9256a1 | ||
|
|
2c37e85a4c | ||
|
|
0ae2dfbff3 | ||
|
|
df5d105f29 | ||
|
|
219c9e7d95 | ||
|
|
6832df4699 | ||
|
|
22ed4f1b53 | ||
|
|
c31c7b50b7 | ||
|
|
7d1f6b7049 | ||
|
|
c2f4cf3407 | ||
|
|
83f3dc99ce | ||
|
|
44d3606ce1 | ||
|
|
470d22c835 | ||
|
|
4c37628784 | ||
|
|
1daf7f8e2b | ||
|
|
841db3b0c2 | ||
|
|
5d10d8418e | ||
|
|
2fee977b4c | ||
|
|
52f1b1c3bf | ||
|
|
ab53d0e863 | ||
|
|
259da2d18a | ||
|
|
bf47e7cae3 | ||
|
|
db2721c581 | ||
|
|
1b2e9a6d06 | ||
|
|
99848aab6a | ||
|
|
3eddeeadc5 | ||
|
|
364bbdeab8 | ||
|
|
7583912de7 | ||
|
|
20fed375d1 | ||
|
|
fc1b3d6927 | ||
|
|
aed2a3cd1b | ||
|
|
15babeb584 | ||
|
|
020b5ea870 | ||
|
|
2ee5b2344e | ||
|
|
7616bcad3d | ||
|
|
f76ce980e3 | ||
|
|
9440ac7640 | ||
|
|
0e18997c79 | ||
|
|
95464772ac | ||
|
|
b4445dfdf8 | ||
|
|
fb5dca2711 | ||
|
|
6cdff955dc | ||
|
|
4039753b2f | ||
|
|
9df33cef8b | ||
|
|
e3e9211e8a | ||
|
|
0da113b723 | ||
|
|
e965adad7c | ||
|
|
57b225b680 | ||
|
|
b97597c645 | ||
|
|
162828da90 | ||
|
|
292647da14 | ||
|
|
3a88e43103 | ||
|
|
2ac60bdf48 | ||
|
|
010765e131 | ||
|
|
cde6e9757b | ||
|
|
f2d9a3e0a1 | ||
|
|
b917e8d3b0 | ||
|
|
a457a73826 | ||
|
|
ac36339491 | ||
|
|
dbbf7ab664 | ||
|
|
66400a98f1 | ||
|
|
aa50e2e385 | ||
|
|
118b8db209 | ||
|
|
e1e41a768a | ||
|
|
c333af33dc | ||
|
|
9e5b482b1d | ||
|
|
771747147c | ||
|
|
bc49979243 | ||
|
|
d46b3e2446 | ||
|
|
2804d89c5e | ||
|
|
19b737a534 | ||
|
|
29380b1120 | ||
|
|
fd32a71131 | ||
|
|
1556fd0e92 | ||
|
|
c4be440cd1 | ||
|
|
5dce7c4e48 | ||
|
|
4bfc32ec99 | ||
|
|
ff65f7fd7b | ||
|
|
7145f86a6e | ||
|
|
7ec4155845 | ||
|
|
6dd62dc891 | ||
|
|
5f66710fcd | ||
|
|
980d62d579 | ||
|
|
ac546a9711 | ||
|
|
cd2aee3053 | ||
|
|
9d9318f38a | ||
|
|
f43d861b50 | ||
|
|
17714b0c12 | ||
|
|
9914576eaa | ||
|
|
bf8eff11ea | ||
|
|
a6c78b99c4 | ||
|
|
6a56ffc650 | ||
|
|
05059606c5 | ||
|
|
a2ff21fab9 | ||
|
|
134370f48d | ||
|
|
c7fa610842 |
@@ -5,7 +5,6 @@ addons:
|
||||
postgresql: "9.4"
|
||||
language: python
|
||||
python:
|
||||
- "2.7"
|
||||
- "3.5"
|
||||
install:
|
||||
- pip install -r requirements.txt
|
||||
|
||||
1816
CHANGELOG.md
Normal file
1816
CHANGELOG.md
Normal file
File diff suppressed because it is too large
Load Diff
17
README.md
17
README.md
@@ -16,8 +16,6 @@ or join us in the #netbox Slack channel on [NetworkToCode](https://networktocode
|
||||
|
||||
### Build Status
|
||||
|
||||
NetBox is built against both Python 2.7 and 3.5. Python 3.5 or higher is strongly recommended.
|
||||
|
||||
| | status |
|
||||
|-------------|------------|
|
||||
| **master** | [](https://travis-ci.org/digitalocean/netbox) |
|
||||
@@ -42,3 +40,18 @@ and run `upgrade.sh`.
|
||||
* [Docker container](https://github.com/ninech/netbox-docker) (via [@cimnine](https://github.com/cimnine))
|
||||
* [Vagrant deployment](https://github.com/ryanmerolle/netbox-vagrant) (via [@ryanmerolle](https://github.com/ryanmerolle))
|
||||
* [Ansible deployment](https://github.com/lae/ansible-role-netbox) (via [@lae](https://github.com/lae))
|
||||
|
||||
# Related projects
|
||||
|
||||
## Supported SDK
|
||||
|
||||
- [pynetbox](https://github.com/digitalocean/pynetbox) Python API client library for Netbox.
|
||||
|
||||
## Community SDK
|
||||
|
||||
- [netbox-client-ruby](https://github.com/ninech/netbox-client-ruby) A ruby client library for Netbox v2.
|
||||
|
||||
## Ansible Inventory
|
||||
|
||||
- [netbox-as-ansible-inventory](https://github.com/AAbouZaid/netbox-as-ansible-inventory) Ansible dynamic inventory script for Netbox.
|
||||
|
||||
|
||||
@@ -1,25 +1,19 @@
|
||||
# django-filter-1.1.0 breaks with Django-2.1
|
||||
Django>=1.11,<2.1
|
||||
Django
|
||||
django-cors-headers
|
||||
django-debug-toolbar
|
||||
# django-filter-2.0.0 drops Python 2 support (blocked by #2000)
|
||||
django-filter==1.1.0
|
||||
django-filter
|
||||
django-mptt
|
||||
django-tables2
|
||||
django-taggit
|
||||
django-taggit-serializer
|
||||
django-timezone-field
|
||||
# https://github.com/encode/django-rest-framework/issues/6053
|
||||
djangorestframework==3.8.1
|
||||
djangorestframework
|
||||
drf-yasg[validation]
|
||||
graphviz
|
||||
Markdown
|
||||
natsort
|
||||
ncclient
|
||||
# py-gfm requires Markdown<3.0
|
||||
Markdown<3.0
|
||||
netaddr
|
||||
paramiko
|
||||
Pillow
|
||||
psycopg2-binary
|
||||
py-gfm
|
||||
pycryptodome
|
||||
xmltodict
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# Contextual Configuration Data
|
||||
|
||||
Sometimes it is desirable to associate arbitrary data with a group of devices to aid in their configuration. For example, you might want to associate a set of syslog servers for all devices at a particular site. Context data enables the association of arbitrary data to devices and virtual machines grouped by region, site, role, platform, and/or tenant. Context data is arranged hierarchically, so that data with a higher weight can be entered to override more general lower-weight data. Multiple instances of data are automatically merged by NetBox to present a single dictionary for each object.
|
||||
|
||||
Devices and Virtual Machines may also have a local config context defined. This local context will always overwrite the rendered config context objects for the Device/VM. This is useful in situations were the device requires a one-off value different from the rest of the environment.
|
||||
|
||||
@@ -9,7 +9,7 @@ This will launch a customized version of [the built-in Django shell](https://doc
|
||||
```
|
||||
$ ./manage.py nbshell
|
||||
### NetBox interactive shell (jstretch-laptop)
|
||||
### Python 2.7.6 | Django 1.11.3 | NetBox 2.1.0-dev
|
||||
### Python 3.5.2 | Django 2.0.8 | NetBox 2.4.3
|
||||
### lsmodels() will show available models. Use help(<model>) for more info.
|
||||
```
|
||||
|
||||
|
||||
@@ -4,6 +4,14 @@ A webhook defines an HTTP request that is sent to an external application when c
|
||||
|
||||
An optional secret key can be configured for each webhook. This will append a `X-Hook-Signature` header to the request, consisting of a HMAC (SHA-512) hex digest of the request body using the secret as the key. This digest can be used by the receiver to authenticate the request's content.
|
||||
|
||||
## Installation
|
||||
|
||||
If you are upgrading from a previous version of Netbox and want to enable the webhook feature, please follow the directions listed in the sections below.
|
||||
|
||||
* [Install Redis server and djano-rq package](../installation/2-netbox/#install-python-packages)
|
||||
* [Modify configuration to enable webhooks](../installation/2-netbox/#webhooks-configuration)
|
||||
* [Create supervisord program to run the rqworker process](../installation/3-http-daemon/#supervisord-installation)
|
||||
|
||||
## Requests
|
||||
|
||||
The webhook POST request is structured as so (assuming `application/json` as the Content-Type):
|
||||
|
||||
@@ -4,6 +4,9 @@ The NetBox API employs token-based authentication. For convenience, cookie authe
|
||||
|
||||
A token is a unique identifier that identifies a user to the API. Each user in NetBox may have one or more tokens which he or she can use to authenticate to the API. To create a token, navigate to the API tokens page at `/user/api-tokens/`.
|
||||
|
||||
!!! note
|
||||
The creation and modification of API tokens can be restricted per user by an administrator. If you don't see an option to create an API token, ask an administrator to grant you access.
|
||||
|
||||
Each token contains a 160-bit key represented as 40 hexadecimal characters. When creating a token, you'll typically leave the key field blank so that a random key will be automatically generated. However, NetBox allows you to specify a key in case you need to restore a previously deleted token to operation.
|
||||
|
||||
By default, a token can be used for all operations available via the API. Deselecting the "write enabled" option will restrict API requests made with the token to read operations (e.g. GET) only.
|
||||
|
||||
@@ -12,5 +12,5 @@ While NetBox has many configuration settings, only a few of them must be defined
|
||||
Configuration settings may be changed at any time. However, the NetBox service must be restarted before the changes will take effect:
|
||||
|
||||
```no-highlight
|
||||
# sudo supervsiorctl restart netbox
|
||||
# sudo supervisorctl restart netbox
|
||||
```
|
||||
|
||||
@@ -133,6 +133,14 @@ Setting this to True will permit only authenticated users to access any part of
|
||||
|
||||
---
|
||||
|
||||
## LOGIN_TIMEOUT
|
||||
|
||||
Default: 1209600 seconds (14 days)
|
||||
|
||||
The liftetime (in seconds) of the authentication cookie issued to a NetBox user upon login.
|
||||
|
||||
---
|
||||
|
||||
## MAINTENANCE_MODE
|
||||
|
||||
Default: False
|
||||
@@ -223,6 +231,14 @@ The file path to the location where custom reports will be kept. By default, thi
|
||||
|
||||
---
|
||||
|
||||
## SESSION_FILE_PATH
|
||||
|
||||
Default: None
|
||||
|
||||
Session data is used to track authenticated users when they access NetBox. By default, NetBox stores session data in the PostgreSQL database. However, this inhibits authentication to a standby instance of NetBox without write access to the database. Alternatively, a local file path may be specified here and NetBox will store session data as files instead of using the database. Note that the user as which NetBox runs must have read and write permissions to this path.
|
||||
|
||||
---
|
||||
|
||||
## TIME_ZONE
|
||||
|
||||
Default: UTC
|
||||
|
||||
@@ -25,7 +25,7 @@ Circuit types are fully customizable.
|
||||
|
||||
A circuit may have one or two terminations, annotated as the "A" and "Z" sides of the circuit. A single-termination circuit can be used when you don't know (or care) about the far end of a circuit (for example, an Internet access circuit which connects to a transit provider). A dual-termination circuit is useful for tracking circuits which connect two sites.
|
||||
|
||||
Each circuit termination is tied to a site, and optionally to a specific device and interface within that site. Each termination can be assigned a separate downstream and upstream speed independent from one another. Fields are also available to track cross-connect and patch panel details.
|
||||
Each circuit termination is tied to a site, and may optionally be connected via a cable to a specific device interface or pass-through port. Each termination can be assigned a separate downstream and upstream speed independent from one another. Fields are also available to track cross-connect and patch panel details.
|
||||
|
||||
!!! note
|
||||
A circuit represents a physical link, and cannot have more than two endpoints. When modeling a multi-point topology, each leg of the topology must be defined as a discrete circuit.
|
||||
|
||||
@@ -4,12 +4,6 @@ A device type represents a particular make and model of hardware that exists in
|
||||
|
||||
Device types are instantiated as devices installed within racks. For example, you might define a device type to represent a Juniper EX4300-48T network switch with 48 Ethernet interfaces. You can then create multiple devices of this type named "switch1," "switch2," and so on. Each device will inherit the components (such as interfaces) of its device type at the time of creation. (However, changes made to a device type will **not** apply to instances of that device type retroactively.)
|
||||
|
||||
The device type model includes three flags which inform what type of components may be added to it:
|
||||
|
||||
* `is_console_server`: This device type has console server ports
|
||||
* `is_pdu`: This device type has power outlets
|
||||
* `is_network_device`: This device type has network interfaces
|
||||
|
||||
Some devices house child devices which share physical resources, like space and power, but which functional independently from one another. A common example of this is blade server chassis. Each device type is designated as one of the following:
|
||||
|
||||
* A parent device (which has device bays)
|
||||
@@ -32,6 +26,8 @@ Each device type is assigned a number of component templates which define the ph
|
||||
* Power ports
|
||||
* Power outlets
|
||||
* Network interfaces
|
||||
* Front ports
|
||||
* Rear ports
|
||||
* Device bays (which house child devices)
|
||||
|
||||
Whenever a new device is created, its components are automatically created per the templates assigned to its device type. For example, a Juniper EX4300-48T device type might have the following component templates defined:
|
||||
@@ -56,32 +52,28 @@ When assigning a multi-U device to a rack, it is considered to be mounted in the
|
||||
|
||||
A device is said to be full depth if its installation on one rack face prevents the installation of any other device on the opposite face within the same rack unit(s). This could be either because the device is physically too deep to allow a device behind it, or because the installation of an opposing device would impede airflow.
|
||||
|
||||
## Device Roles
|
||||
## Device Components
|
||||
|
||||
Devices can be organized by functional roles. These roles are fully cusomizable. For example, you might create roles for core switches, distribution switches, and access switches.
|
||||
|
||||
---
|
||||
|
||||
# Device Components
|
||||
|
||||
There are six types of device components which comprise all of the interconnection logic with NetBox:
|
||||
There are eight types of device components which comprise all of the interconnection logic with NetBox:
|
||||
|
||||
* Console ports
|
||||
* Console server ports
|
||||
* Power ports
|
||||
* Power outlets
|
||||
* Network interfaces
|
||||
* Front ports
|
||||
* Rear ports
|
||||
* Device bays
|
||||
|
||||
## Console
|
||||
### Console
|
||||
|
||||
Console ports connect only to console server ports. Console connections can be marked as either *planned* or *connected*.
|
||||
|
||||
## Power
|
||||
### Power
|
||||
|
||||
Power ports connect only to power outlets. Power connections can be marked as either *planned* or *connected*.
|
||||
|
||||
## Interfaces
|
||||
### Interfaces
|
||||
|
||||
Interfaces connect to one another in a symmetric manner: If interface A connects to interface B, interface B therefore connects to interface A. Each type of connection can be classified as either *planned* or *connected*.
|
||||
|
||||
@@ -91,17 +83,27 @@ Each interface can also be enabled or disabled, and optionally designated as man
|
||||
|
||||
VLANs can be assigned to each interface as either tagged or untagged. (An interface may have only one untagged VLAN.)
|
||||
|
||||
## Device Bays
|
||||
### Pass-through Ports
|
||||
|
||||
Pass-through ports are used to model physical terminations which comprise part of a longer path, such as a cable terminated to a patch panel. Each front port maps to a position on a rear port. A 24-port UTP patch panel, for instance, would have 24 front ports and 24 rear ports. Although this relationship is typically one-to-one, a rear port may have multiple front ports mapped to it. This can be useful for modeling instances where multiple paths share a common cable (for example, six different fiber connections sharing a 12-strand MPO cable).
|
||||
|
||||
Pass-through ports can also be used to model "bump in the wire" devices, such as a media convertor or passive tap.
|
||||
|
||||
### Device Bays
|
||||
|
||||
Device bays represent the ability of a device to house child devices. For example, you might install four blade servers into a 2U chassis. The chassis would appear in the rack elevation as a 2U device with four device bays. Each server within it would be defined as a 0U device installed in one of the device bays. Child devices do not appear within rack elevations, but they are included in the "Non-Racked Devices" list within the rack view.
|
||||
|
||||
## Device Roles
|
||||
|
||||
Devices can be organized by functional roles. These roles are fully customizable. For example, you might create roles for core switches, distribution switches, and access switches.
|
||||
|
||||
---
|
||||
|
||||
# Platforms
|
||||
|
||||
A platform defines the type of software running on a device or virtual machine. This can be helpful when it is necessary to distinguish between, for instance, different feature sets. Note that two devices of same type may be assigned different platforms: for example, one Juniper MX240 running Junos 14 and another running Junos 15.
|
||||
A platform defines the type of software running on a device or virtual machine. This can be helpful when it is necessary to distinguish between, for instance, different feature sets. Note that two devices of the same type may be assigned different platforms: for example, one Juniper MX240 running Junos 14 and another running Junos 15.
|
||||
|
||||
The platform model is also used to indicate which [NAPALM](https://napalm-automation.net/) driver NetBox should use when connecting to a remote device. The name of the driver along with optional parameters are stored with the platform. See the [API documentation](api/napalm-integration.md) for more information on NAPALM integration.
|
||||
The platform model is also used to indicate which [NAPALM](https://napalm-automation.net/) driver NetBox should use when connecting to a remote device. The name of the driver along with optional parameters are stored with the platform.
|
||||
|
||||
The assignment of platforms to devices is an optional feature, and may be disregarded if not desired.
|
||||
|
||||
@@ -118,3 +120,25 @@ Inventory items represent hardware components installed within a device, such as
|
||||
A virtual chassis represents a set of devices which share a single control plane: a stack of switches which are managed as a single device, for example. Each device in the virtual chassis is assigned a position and (optionally) a priority. Exactly one device is designated the virtual chassis master: This device will typically be assigned a name, secrets, services, and other attributes related to its management.
|
||||
|
||||
It's important to recognize the distinction between a virtual chassis and a chassis-based device. For instance, a virtual chassis is not used to model a chassis switch with removable line cards such as the Juniper EX9208, as its line cards are _not_ physically separate devices capable of operating independently.
|
||||
|
||||
---
|
||||
|
||||
# Cables
|
||||
|
||||
A cable represents a physical connection between two termination points, such as between a console port and a patch panel port, or between two network interfaces. Cables can be traced through pass-through ports to form a complete path between two endpoints. In the example below, three individual cables comprise a path between the two connected endpoints.
|
||||
|
||||
```
|
||||
|<------------------------------------------ Cable Path ------------------------------------------->|
|
||||
|
||||
Device A Patch Panel A Patch Panel B Device B
|
||||
+-----------+ +-------------+ +-------------+ +-----------+
|
||||
| Interface | --- Cable --- | Front Port | | Front Port | --- Cable --- | Interface |
|
||||
+-----------+ +-------------+ +-------------+ +-----------+
|
||||
+-------------+ +-------------+
|
||||
| Rear Port | --- Cable --- | Rear Port |
|
||||
+-------------+ +-------------+
|
||||
```
|
||||
|
||||
All connections between device components in NetBox are represented using cables. However, defining the actual cable plant is optional: Components can be be directly connected using cables with no type or other attributes assigned.
|
||||
|
||||
Cables are also used to associated ports and interfaces with circuit terminations. To do this, first create the circuit termination, then navigate the desired component and connect a cable between the two.
|
||||
|
||||
@@ -4,7 +4,7 @@ The first step to documenting your IP space is to define its scope by creating a
|
||||
|
||||
* 10.0.0.0/8 (RFC 1918)
|
||||
* 100.64.0.0/10 (RFC 6598)
|
||||
* 172.16.0.0/20 (RFC 1918)
|
||||
* 172.16.0.0/12 (RFC 1918)
|
||||
* 192.168.0.0/16 (RFC 1918)
|
||||
* One or more /48s within fd00::/8 (IPv6 unique local addressing)
|
||||
|
||||
|
||||
70
docs/development/extending-models.md
Normal file
70
docs/development/extending-models.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# Extending Models
|
||||
|
||||
Below is a list of items to consider when adding a new field to a model:
|
||||
|
||||
### 1. Generate and run database migration
|
||||
|
||||
Django migrations are used to express changes to the database schema. In most cases, Django can generate these automatically, however very complex changes may require manual intervention. Always remember to specify a short but descriptive name when generating a new migration.
|
||||
|
||||
```
|
||||
./manage.py makemigrations <app> -n <name>
|
||||
./manage.py migrate
|
||||
```
|
||||
|
||||
Where possible, try to merge related changes into a single migration. For example, if three new fields are being added to different models within an app, these can be expressed in the same migration. You can merge a new migration with an existing one by combining their `operations` lists.
|
||||
|
||||
!!! note
|
||||
Migrations can only be merged within a release. Once a new release has been published, its migrations cannot be altered.
|
||||
|
||||
### 2. Add validation logic to `clean()`
|
||||
|
||||
If the new field introduces additional validation requirements (beyond what's included with the field itself), implement them in the model's `clean()` method. Remember to call the model's original method using `super()` before or agter your custom validation as appropriate:
|
||||
|
||||
```
|
||||
class Foo(models.Model):
|
||||
|
||||
def clean(self):
|
||||
|
||||
super(DeviceCSVForm, self).clean()
|
||||
|
||||
# Custom validation goes here
|
||||
if self.bar is None:
|
||||
raise ValidationError()
|
||||
```
|
||||
|
||||
### 3. Add CSV helpers
|
||||
|
||||
Add the name of the new field to `csv_headers` and included a CSV-friendly representation of its data in the model's `to_csv()` method. These will be used when exporting objects in CSV format.
|
||||
|
||||
### 4. Update relevant querysets
|
||||
|
||||
If you're adding a relational field (e.g. `ForeignKey`) and intend to include the data when retreiving a list of objects, be sure to include the field using `select_related()` or `prefetch_related()` as appropriate. This will optimize the view and avoid excessive database lookups.
|
||||
|
||||
### 5. Update API serializer
|
||||
|
||||
Extend the model's API serializer in `<app>.api.serializers` to include the new field. In most cases, it will not be necessary to also extend the nested serializer, which produces a minimal represenation of the model.
|
||||
|
||||
### 6. Add field to forms
|
||||
|
||||
Extend any forms to include the new field as appropriate. Common forms include:
|
||||
|
||||
* **Credit/edit** - Manipulating a single object
|
||||
* **Bulk edit** - Performing a change on mnay objects at once
|
||||
* **CSV import** - The form used when bulk importing objects in CSV format
|
||||
* **Filter** - Displays the options available for filtering a list of objects (both UI and API)
|
||||
|
||||
### 7. Extend object filter set
|
||||
|
||||
If the new field should be filterable, add it to the `FilterSet` for the model. If the field should be searchable, remember to reference it in the FilterSet's `search()` method.
|
||||
|
||||
### 8. Add column to object table
|
||||
|
||||
If the new field will be included in the object list view, add a column to the model's table. For simple fields, adding the field name to `Meta.fields` will be sufficient. More complex fields may require explicitly declaring a new column.
|
||||
|
||||
### 9. Update the UI templates
|
||||
|
||||
Edit the object's view template to display the new field. There may also be a custom add/edit form template that needs to be updated.
|
||||
|
||||
### 10. Adjust API and model tests
|
||||
|
||||
Extend the model and/or API tests to verify that the new field and any accompanying validation logic perform as expected. This is especially important for relational fields.
|
||||
@@ -28,10 +28,3 @@ NetBox components are arranged into functional subsections called _apps_ (a carr
|
||||
* `tenancy`: Tenants (such as customers) to which NetBox objects may be assigned
|
||||
* `utilities`: Resources which are not user-facing (extendable classes, etc.)
|
||||
* `virtualization`: Virtual machines and clusters
|
||||
|
||||
## Style Guide
|
||||
|
||||
NetBox generally follows the [Django style guide](https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/coding-style/), which is itself based on [PEP 8](https://www.python.org/dev/peps/pep-0008/). The following exceptions are noted:
|
||||
|
||||
* [Pycodestyle](https://github.com/pycqa/pycodestyle) is used to validate code formatting, ignoring certain violations. See `scripts/cibuild.sh`.
|
||||
* Constants may be imported via wildcard (for example, `from .constants import *`).
|
||||
|
||||
@@ -48,9 +48,9 @@ Close the release milestone on GitHub. Ensure that there are no remaining open i
|
||||
|
||||
Ensure that continuous integration testing on the `develop` branch is completing successfully.
|
||||
|
||||
## Update VERSION
|
||||
## Update Version and Changelog
|
||||
|
||||
Update the `VERSION` constant in `settings.py` to the new release.
|
||||
Update the `VERSION` constant in `settings.py` to the new release version and add the current date to the release notes in `CHANGELOG.md`.
|
||||
|
||||
## Submit a Pull Request
|
||||
|
||||
|
||||
41
docs/development/style-guide.md
Normal file
41
docs/development/style-guide.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Style Guide
|
||||
|
||||
NetBox generally follows the [Django style guide](https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/coding-style/), which is itself based on [PEP 8](https://www.python.org/dev/peps/pep-0008/). [Pycodestyle](https://github.com/pycqa/pycodestyle) is used to validate code formatting, ignoring certain violations. See `scripts/cibuild.sh`.
|
||||
|
||||
## PEP 8 Exceptions
|
||||
|
||||
* Wildcard imports (for example, `from .constants import *`) are acceptable under any of the following conditions:
|
||||
* The library being import contains only constant declarations (`constants.py`)
|
||||
* The library being imported explicitly defines `__all__` (e.g. `<app>.api.nested_serializers`)
|
||||
|
||||
* Maximum line length is 120 characters (E501)
|
||||
* This does not apply to HTML templates or to automatically generated code (e.g. database migrations).
|
||||
|
||||
* Line breaks are permitted following binary operators (W504)
|
||||
|
||||
## Enforcing Code Style
|
||||
|
||||
The `pycodestyle` utility (previously `pep8`) is used by the CI process to enforce code style. It is strongly recommended to include as part of your commit process. A git commit hook is provided in the source at `scripts/git-hooks/pre-commit`. Linking to this script from `.git/hooks/` will invoke `pycodestyle` prior to every commit attempt and abort if the validation fails.
|
||||
|
||||
```
|
||||
$ cd .git/hooks/
|
||||
$ ln -s ../../scripts/git-hooks/pre-commit
|
||||
```
|
||||
|
||||
To invoke `pycodestyle` manually, run:
|
||||
|
||||
```
|
||||
pycodestyle --ignore=W504,E501 netbox/
|
||||
```
|
||||
|
||||
## General Guidance
|
||||
|
||||
* When in doubt, remain consistent: It is better to be consistently incorrect than inconsistently correct. If you notice in the course of unrelated work a pattern that should be corrected, continue to follow the pattern for now and open a bug so that the entire code base can be evaluated at a later point.
|
||||
|
||||
* No easter eggs. While they can be fun, NetBox must be considered as a business-critical tool. The potential, however minor, for introducing a bug caused by unnecessary logic is best avoided entirely.
|
||||
|
||||
* Constants (variables which generally do not change) should be declared in `constants.py` within each app. Wildcard imports from the file are acceptable.
|
||||
|
||||
* Every model should have a docstring. Every custom method should include an expalantion of its function.
|
||||
|
||||
* Nested API serializers generate minimal representations of an object. These are stored separately from the primary serializers to avoid circular dependencies. Always import nested serializers from other apps directly. For example, from within the DCIM app you would write `from ipam.api.nested_serializers import NestedIPAddressSerializer`.
|
||||
@@ -71,7 +71,7 @@ Checking connectivity... done.
|
||||
|
||||
`# chown -R netbox:netbox /opt/netbox/netbox/media/`
|
||||
|
||||
## Install Python Packages
|
||||
# Install Python Packages
|
||||
|
||||
Install the required Python packages using pip. (If you encounter any compilation errors during this step, ensure that you've installed all of the system dependencies listed above.)
|
||||
|
||||
@@ -82,7 +82,7 @@ Install the required Python packages using pip. (If you encounter any compilatio
|
||||
!!! note
|
||||
If you encounter errors while installing the required packages, check that you're running a recent version of pip (v9.0.1 or higher) with the command `pip3 -V`.
|
||||
|
||||
### NAPALM Automation (Optional)
|
||||
## NAPALM Automation (Optional)
|
||||
|
||||
NetBox supports integration with the [NAPALM automation](https://napalm-automation.net/) library. NAPALM allows NetBox to fetch live data from devices and return it to a requester via its REST API. Installation of NAPALM is optional. To enable it, install the `napalm` package using pip or pip3:
|
||||
|
||||
@@ -90,7 +90,7 @@ NetBox supports integration with the [NAPALM automation](https://napalm-automati
|
||||
# pip3 install napalm
|
||||
```
|
||||
|
||||
### Webhooks (Optional)
|
||||
## Webhooks (Optional)
|
||||
|
||||
[Webhooks](../data-model/extras/#webhooks) allow NetBox to integrate with external services by pushing out a notification each time a relevant object is created, updated, or deleted. Enabling the webhooks feature requires [Redis](https://redis.io/), a lightweight in-memory database. You may opt to install a Redis sevice locally (see below) or connect to an external one.
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ To enable SSL, consider this guide on [securing nginx with Let's Encrypt](https:
|
||||
## Option B: Apache
|
||||
|
||||
```no-highlight
|
||||
# apt-get install -y apache2
|
||||
# apt-get install -y apache2 libapache2-mod-wsgi-py3
|
||||
```
|
||||
|
||||
Once Apache is installed, proceed with the following configuration (Be sure to modify the `ServerName` appropriately):
|
||||
|
||||
@@ -64,13 +64,6 @@ Once the new code is in place, run the upgrade script (which may need to be run
|
||||
# ./upgrade.sh
|
||||
```
|
||||
|
||||
!!! warning
|
||||
The upgrade script will prefer Python3 and pip3 if both executables are available. To force it to use Python2 and pip, use the `-2` argument as below. Note that Python 2 will no longer be supported in NetBox v2.5.
|
||||
|
||||
```no-highlight
|
||||
# ./upgrade.sh -2
|
||||
```
|
||||
|
||||
This script:
|
||||
|
||||
* Installs or upgrades any new required Python packages
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
site_name: NetBox
|
||||
theme: readthedocs
|
||||
repo_url: https://github.com/digitalocean/netbox
|
||||
|
||||
pages:
|
||||
@@ -44,7 +45,9 @@ pages:
|
||||
- Examples: 'api/examples.md'
|
||||
- Development:
|
||||
- Introduction: 'development/index.md'
|
||||
- Style Guide: 'development/style-guide.md'
|
||||
- Utility Views: 'development/utility-views.md'
|
||||
- Extending Models: 'development/extending-models.md'
|
||||
- Release Checklist: 'development/release-checklist.md'
|
||||
|
||||
markdown_extensions:
|
||||
|
||||
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 circuits.constants import CIRCUIT_STATUS_CHOICES
|
||||
from circuits.models import Provider, Circuit, CircuitTermination, CircuitType
|
||||
from dcim.api.serializers import NestedSiteSerializer, InterfaceSerializer
|
||||
from dcim.api.nested_serializers import NestedCableSerializer, NestedSiteSerializer
|
||||
from dcim.api.serializers import ConnectedEndpointSerializer
|
||||
from extras.api.customfields import CustomFieldModelSerializer
|
||||
from tenancy.api.serializers import NestedTenantSerializer
|
||||
from utilities.api import ChoiceField, ValidatedModelSerializer, WritableNestedSerializer
|
||||
from tenancy.api.nested_serializers import NestedTenantSerializer
|
||||
from utilities.api import ChoiceField, ValidatedModelSerializer
|
||||
from .nested_serializers import *
|
||||
|
||||
|
||||
#
|
||||
@@ -26,16 +25,8 @@ class ProviderSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
||||
]
|
||||
|
||||
|
||||
class NestedProviderSerializer(WritableNestedSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='circuits-api:provider-detail')
|
||||
|
||||
class Meta:
|
||||
model = Provider
|
||||
fields = ['id', 'url', 'name', 'slug']
|
||||
|
||||
|
||||
#
|
||||
# Circuit types
|
||||
# Circuits
|
||||
#
|
||||
|
||||
class CircuitTypeSerializer(ValidatedModelSerializer):
|
||||
@@ -45,18 +36,6 @@ class CircuitTypeSerializer(ValidatedModelSerializer):
|
||||
fields = ['id', 'name', 'slug']
|
||||
|
||||
|
||||
class NestedCircuitTypeSerializer(WritableNestedSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='circuits-api:circuittype-detail')
|
||||
|
||||
class Meta:
|
||||
model = CircuitType
|
||||
fields = ['id', 'url', 'name', 'slug']
|
||||
|
||||
|
||||
#
|
||||
# Circuits
|
||||
#
|
||||
|
||||
class CircuitSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
||||
provider = NestedProviderSerializer()
|
||||
status = ChoiceField(choices=CIRCUIT_STATUS_CHOICES, required=False)
|
||||
@@ -72,25 +51,14 @@ class CircuitSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
||||
]
|
||||
|
||||
|
||||
class NestedCircuitSerializer(WritableNestedSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='circuits-api:circuit-detail')
|
||||
|
||||
class Meta:
|
||||
model = Circuit
|
||||
fields = ['id', 'url', 'cid']
|
||||
|
||||
|
||||
#
|
||||
# Circuit Terminations
|
||||
#
|
||||
|
||||
class CircuitTerminationSerializer(ValidatedModelSerializer):
|
||||
class CircuitTerminationSerializer(ConnectedEndpointSerializer):
|
||||
circuit = NestedCircuitSerializer()
|
||||
site = NestedSiteSerializer()
|
||||
interface = InterfaceSerializer(required=False, allow_null=True)
|
||||
cable = NestedCableSerializer(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = CircuitTermination
|
||||
fields = [
|
||||
'id', 'circuit', 'term_side', 'site', 'interface', 'port_speed', 'upstream_speed', 'xconnect_id', 'pp_info',
|
||||
'id', 'circuit', 'term_side', 'site', 'port_speed', 'upstream_speed', 'xconnect_id', 'pp_info',
|
||||
'description', 'connected_endpoint', 'connection_status', 'cable',
|
||||
]
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from rest_framework import routers
|
||||
|
||||
from . import views
|
||||
@@ -17,7 +15,7 @@ router = routers.DefaultRouter()
|
||||
router.APIRootView = CircuitsRootView
|
||||
|
||||
# Field choices
|
||||
router.register(r'_choices', views.CircuitsFieldChoicesViewSet, base_name='field-choice')
|
||||
router.register(r'_choices', views.CircuitsFieldChoicesViewSet, basename='field-choice')
|
||||
|
||||
# Providers
|
||||
router.register(r'providers', views.ProviderViewSet)
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.shortcuts import get_object_or_404
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.response import Response
|
||||
@@ -29,9 +27,9 @@ class CircuitsFieldChoicesViewSet(FieldChoicesViewSet):
|
||||
#
|
||||
|
||||
class ProviderViewSet(CustomFieldModelViewSet):
|
||||
queryset = Provider.objects.all()
|
||||
queryset = Provider.objects.prefetch_related('tags')
|
||||
serializer_class = serializers.ProviderSerializer
|
||||
filter_class = filters.ProviderFilter
|
||||
filterset_class = filters.ProviderFilter
|
||||
|
||||
@action(detail=True)
|
||||
def graphs(self, request, pk=None):
|
||||
@@ -51,7 +49,7 @@ class ProviderViewSet(CustomFieldModelViewSet):
|
||||
class CircuitTypeViewSet(ModelViewSet):
|
||||
queryset = CircuitType.objects.all()
|
||||
serializer_class = serializers.CircuitTypeSerializer
|
||||
filter_class = filters.CircuitTypeFilter
|
||||
filterset_class = filters.CircuitTypeFilter
|
||||
|
||||
|
||||
#
|
||||
@@ -59,9 +57,9 @@ class CircuitTypeViewSet(ModelViewSet):
|
||||
#
|
||||
|
||||
class CircuitViewSet(CustomFieldModelViewSet):
|
||||
queryset = Circuit.objects.select_related('type', 'tenant', 'provider')
|
||||
queryset = Circuit.objects.select_related('type', 'tenant', 'provider').prefetch_related('tags')
|
||||
serializer_class = serializers.CircuitSerializer
|
||||
filter_class = filters.CircuitFilter
|
||||
filterset_class = filters.CircuitFilter
|
||||
|
||||
|
||||
#
|
||||
@@ -69,6 +67,8 @@ class CircuitViewSet(CustomFieldModelViewSet):
|
||||
#
|
||||
|
||||
class CircuitTerminationViewSet(ModelViewSet):
|
||||
queryset = CircuitTermination.objects.select_related('circuit', 'site', 'interface__device')
|
||||
queryset = CircuitTermination.objects.select_related(
|
||||
'circuit', 'site', 'connected_endpoint__device', 'cable'
|
||||
)
|
||||
serializer_class = serializers.CircuitTerminationSerializer
|
||||
filter_class = filters.CircuitTerminationFilter
|
||||
filterset_class = filters.CircuitTerminationFilter
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
# Circuit statuses
|
||||
CIRCUIT_STATUS_DEPROVISIONING = 0
|
||||
|
||||
@@ -1,36 +1,35 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import django_filters
|
||||
from django.db.models import Q
|
||||
|
||||
from dcim.models import Site
|
||||
from extras.filters import CustomFieldFilterSet
|
||||
from tenancy.models import Tenant
|
||||
from utilities.filters import NumericInFilter
|
||||
from utilities.filters import NumericInFilter, TagFilter
|
||||
from .constants import CIRCUIT_STATUS_CHOICES
|
||||
from .models import Provider, Circuit, CircuitTermination, CircuitType
|
||||
|
||||
|
||||
class ProviderFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
||||
id__in = NumericInFilter(name='id', lookup_expr='in')
|
||||
id__in = NumericInFilter(
|
||||
field_name='id',
|
||||
lookup_expr='in'
|
||||
)
|
||||
q = django_filters.CharFilter(
|
||||
method='search',
|
||||
label='Search',
|
||||
)
|
||||
site_id = django_filters.ModelMultipleChoiceFilter(
|
||||
name='circuits__terminations__site',
|
||||
field_name='circuits__terminations__site',
|
||||
queryset=Site.objects.all(),
|
||||
label='Site',
|
||||
)
|
||||
site = django_filters.ModelMultipleChoiceFilter(
|
||||
name='circuits__terminations__site__slug',
|
||||
field_name='circuits__terminations__site__slug',
|
||||
queryset=Site.objects.all(),
|
||||
to_field_name='slug',
|
||||
label='Site (slug)',
|
||||
)
|
||||
tag = django_filters.CharFilter(
|
||||
name='tags__slug',
|
||||
)
|
||||
tag = TagFilter()
|
||||
|
||||
class Meta:
|
||||
model = Provider
|
||||
@@ -56,7 +55,10 @@ class CircuitTypeFilter(django_filters.FilterSet):
|
||||
|
||||
|
||||
class CircuitFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
||||
id__in = NumericInFilter(name='id', lookup_expr='in')
|
||||
id__in = NumericInFilter(
|
||||
field_name='id',
|
||||
lookup_expr='in'
|
||||
)
|
||||
q = django_filters.CharFilter(
|
||||
method='search',
|
||||
label='Search',
|
||||
@@ -66,7 +68,7 @@ class CircuitFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
||||
label='Provider (ID)',
|
||||
)
|
||||
provider = django_filters.ModelMultipleChoiceFilter(
|
||||
name='provider__slug',
|
||||
field_name='provider__slug',
|
||||
queryset=Provider.objects.all(),
|
||||
to_field_name='slug',
|
||||
label='Provider (slug)',
|
||||
@@ -76,7 +78,7 @@ class CircuitFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
||||
label='Circuit type (ID)',
|
||||
)
|
||||
type = django_filters.ModelMultipleChoiceFilter(
|
||||
name='type__slug',
|
||||
field_name='type__slug',
|
||||
queryset=CircuitType.objects.all(),
|
||||
to_field_name='slug',
|
||||
label='Circuit type (slug)',
|
||||
@@ -90,25 +92,23 @@ class CircuitFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
||||
label='Tenant (ID)',
|
||||
)
|
||||
tenant = django_filters.ModelMultipleChoiceFilter(
|
||||
name='tenant__slug',
|
||||
field_name='tenant__slug',
|
||||
queryset=Tenant.objects.all(),
|
||||
to_field_name='slug',
|
||||
label='Tenant (slug)',
|
||||
)
|
||||
site_id = django_filters.ModelMultipleChoiceFilter(
|
||||
name='terminations__site',
|
||||
field_name='terminations__site',
|
||||
queryset=Site.objects.all(),
|
||||
label='Site (ID)',
|
||||
)
|
||||
site = django_filters.ModelMultipleChoiceFilter(
|
||||
name='terminations__site__slug',
|
||||
field_name='terminations__site__slug',
|
||||
queryset=Site.objects.all(),
|
||||
to_field_name='slug',
|
||||
label='Site (slug)',
|
||||
)
|
||||
tag = django_filters.CharFilter(
|
||||
name='tags__slug',
|
||||
)
|
||||
tag = TagFilter()
|
||||
|
||||
class Meta:
|
||||
model = Circuit
|
||||
@@ -121,6 +121,7 @@ class CircuitFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
||||
Q(cid__icontains=value) |
|
||||
Q(terminations__xconnect_id__icontains=value) |
|
||||
Q(terminations__pp_info__icontains=value) |
|
||||
Q(terminations__description__icontains=value) |
|
||||
Q(description__icontains=value) |
|
||||
Q(comments__icontains=value)
|
||||
).distinct()
|
||||
@@ -140,7 +141,7 @@ class CircuitTerminationFilter(django_filters.FilterSet):
|
||||
label='Site (ID)',
|
||||
)
|
||||
site = django_filters.ModelMultipleChoiceFilter(
|
||||
name='site__slug',
|
||||
field_name='site__slug',
|
||||
queryset=Site.objects.all(),
|
||||
to_field_name='slug',
|
||||
label='Site (slug)',
|
||||
@@ -156,5 +157,6 @@ class CircuitTerminationFilter(django_filters.FilterSet):
|
||||
return queryset.filter(
|
||||
Q(circuit__cid__icontains=value) |
|
||||
Q(xconnect_id__icontains=value) |
|
||||
Q(pp_info__icontains=value)
|
||||
Q(pp_info__icontains=value) |
|
||||
Q(description__icontains=value)
|
||||
).distinct()
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django import forms
|
||||
from django.db.models import Count
|
||||
from taggit.forms import TagField
|
||||
|
||||
from dcim.models import Site, Device, Interface, Rack
|
||||
from dcim.models import Site
|
||||
from extras.forms import AddRemoveTagsForm, CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm
|
||||
from tenancy.forms import TenancyForm
|
||||
from tenancy.models import Tenant
|
||||
from utilities.forms import (
|
||||
AnnotatedMultipleChoiceField, APISelect, add_blank_choice, BootstrapMixin, ChainedFieldsMixin,
|
||||
ChainedModelChoiceField, CommentField, CSVChoiceField, FilterChoiceField, SmallTextarea, SlugField,
|
||||
AnnotatedMultipleChoiceField, add_blank_choice, BootstrapMixin, CommentField, CSVChoiceField, FilterChoiceField,
|
||||
SmallTextarea, SlugField,
|
||||
)
|
||||
from .constants import CIRCUIT_STATUS_CHOICES
|
||||
from .models import Circuit, CircuitTermination, CircuitType, Provider
|
||||
@@ -205,59 +203,12 @@ class CircuitFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
||||
# Circuit terminations
|
||||
#
|
||||
|
||||
class CircuitTerminationForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelForm):
|
||||
site = forms.ModelChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
widget=forms.Select(
|
||||
attrs={'filter-for': 'rack'}
|
||||
)
|
||||
)
|
||||
rack = ChainedModelChoiceField(
|
||||
queryset=Rack.objects.all(),
|
||||
chains=(
|
||||
('site', 'site'),
|
||||
),
|
||||
required=False,
|
||||
label='Rack',
|
||||
widget=APISelect(
|
||||
api_url='/api/dcim/racks/?site_id={{site}}',
|
||||
attrs={'filter-for': 'device', 'nullable': 'true'}
|
||||
)
|
||||
)
|
||||
device = ChainedModelChoiceField(
|
||||
queryset=Device.objects.all(),
|
||||
chains=(
|
||||
('site', 'site'),
|
||||
('rack', 'rack'),
|
||||
),
|
||||
required=False,
|
||||
label='Device',
|
||||
widget=APISelect(
|
||||
api_url='/api/dcim/devices/?site_id={{site}}&rack_id={{rack}}',
|
||||
display_field='display_name',
|
||||
attrs={'filter-for': 'interface'}
|
||||
)
|
||||
)
|
||||
interface = ChainedModelChoiceField(
|
||||
queryset=Interface.objects.connectable().select_related(
|
||||
'circuit_termination', 'connected_as_a', 'connected_as_b'
|
||||
),
|
||||
chains=(
|
||||
('device', 'device'),
|
||||
),
|
||||
required=False,
|
||||
label='Interface',
|
||||
widget=APISelect(
|
||||
api_url='/api/dcim/interfaces/?device_id={{device}}&type=physical',
|
||||
disabled_indicator='is_connected'
|
||||
)
|
||||
)
|
||||
class CircuitTerminationForm(BootstrapMixin, forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = CircuitTermination
|
||||
fields = [
|
||||
'term_side', 'site', 'rack', 'device', 'interface', 'port_speed', 'upstream_speed', 'xconnect_id',
|
||||
'pp_info',
|
||||
'term_side', 'site', 'port_speed', 'upstream_speed', 'xconnect_id', 'pp_info', 'description',
|
||||
]
|
||||
help_texts = {
|
||||
'port_speed': "Physical circuit speed",
|
||||
@@ -267,25 +218,3 @@ class CircuitTerminationForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelForm
|
||||
widgets = {
|
||||
'term_side': forms.HiddenInput(),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
# Initialize helper selectors
|
||||
instance = kwargs.get('instance')
|
||||
if instance and instance.interface is not None:
|
||||
initial = kwargs.get('initial', {}).copy()
|
||||
initial['rack'] = instance.interface.device.rack
|
||||
initial['device'] = instance.interface.device
|
||||
kwargs['initial'] = initial
|
||||
|
||||
super(CircuitTerminationForm, self).__init__(*args, **kwargs)
|
||||
|
||||
# Mark connected interfaces as disabled
|
||||
self.fields['interface'].choices = []
|
||||
for iface in self.fields['interface'].queryset:
|
||||
self.fields['interface'].choices.append(
|
||||
(iface.id, {
|
||||
'label': iface.name,
|
||||
'disabled': iface.is_connected and iface.pk != self.initial.get('interface'),
|
||||
})
|
||||
)
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.7 on 2016-06-22 18:21
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.14 on 2018-07-31 02:25
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import dcim.fields
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.7 on 2016-06-22 18:21
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.7 on 2016-07-13 19:24
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import dcim.fields
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.8 on 2016-07-26 21:59
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.8 on 2016-08-08 20:24
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10 on 2016-12-13 16:30
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.4 on 2017-01-17 20:08
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2017-04-19 17:17
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2017-05-24 15:34
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import dcim.fields
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.9 on 2018-02-06 18:48
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.12 on 2018-05-22 19:04
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
import taggit.managers
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.12 on 2018-06-13 17:14
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
||||
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.db import models
|
||||
from django.urls import reverse
|
||||
from django.utils.encoding import python_2_unicode_compatible
|
||||
from taggit.managers import TaggableManager
|
||||
|
||||
from dcim.constants import STATUS_CLASSES
|
||||
from dcim.constants import CONNECTION_STATUS_CHOICES, CONNECTION_STATUS_CONNECTED, STATUS_CLASSES
|
||||
from dcim.fields import ASNField
|
||||
from dcim.models import CableTermination
|
||||
from extras.models import CustomFieldModel, ObjectChange
|
||||
from utilities.models import ChangeLoggedModel
|
||||
from utilities.utils import serialize_object
|
||||
from .constants import CIRCUIT_STATUS_ACTIVE, CIRCUIT_STATUS_CHOICES, TERM_SIDE_CHOICES
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class Provider(ChangeLoggedModel, CustomFieldModel):
|
||||
"""
|
||||
Each Circuit belongs to a Provider. This is usually a telecommunications company or similar organization. This model
|
||||
@@ -84,7 +81,6 @@ class Provider(ChangeLoggedModel, CustomFieldModel):
|
||||
)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class CircuitType(ChangeLoggedModel):
|
||||
"""
|
||||
Circuits can be organized by their functional role. For example, a user might wish to define CircuitTypes named
|
||||
@@ -116,12 +112,11 @@ class CircuitType(ChangeLoggedModel):
|
||||
)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class Circuit(ChangeLoggedModel, CustomFieldModel):
|
||||
"""
|
||||
A communications circuit connects two points. Each Circuit belongs to a Provider; Providers may have multiple
|
||||
circuits. Each circuit is also assigned a CircuitType and a Site. A Circuit may be terminated to a specific device
|
||||
interface, but this is not required. Circuit port speed and commit rate are measured in Kbps.
|
||||
circuits. Each circuit is also assigned a CircuitType and a Site. Circuit port speed and commit rate are measured
|
||||
in Kbps.
|
||||
"""
|
||||
cid = models.CharField(
|
||||
max_length=50,
|
||||
@@ -217,8 +212,7 @@ class Circuit(ChangeLoggedModel, CustomFieldModel):
|
||||
return self._get_termination('Z')
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class CircuitTermination(models.Model):
|
||||
class CircuitTermination(CableTermination):
|
||||
circuit = models.ForeignKey(
|
||||
to='circuits.Circuit',
|
||||
on_delete=models.CASCADE,
|
||||
@@ -234,13 +228,17 @@ class CircuitTermination(models.Model):
|
||||
on_delete=models.PROTECT,
|
||||
related_name='circuit_terminations'
|
||||
)
|
||||
interface = models.OneToOneField(
|
||||
connected_endpoint = models.OneToOneField(
|
||||
to='dcim.Interface',
|
||||
on_delete=models.PROTECT,
|
||||
related_name='circuit_termination',
|
||||
on_delete=models.SET_NULL,
|
||||
related_name='+',
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
connection_status = models.NullBooleanField(
|
||||
choices=CONNECTION_STATUS_CHOICES,
|
||||
blank=True
|
||||
)
|
||||
port_speed = models.PositiveIntegerField(
|
||||
verbose_name='Port speed (Kbps)'
|
||||
)
|
||||
@@ -260,13 +258,17 @@ class CircuitTermination(models.Model):
|
||||
blank=True,
|
||||
verbose_name='Patch panel/port(s)'
|
||||
)
|
||||
description = models.CharField(
|
||||
max_length=100,
|
||||
blank=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ['circuit', 'term_side']
|
||||
unique_together = ['circuit', 'term_side']
|
||||
|
||||
def __str__(self):
|
||||
return '{} (Side {})'.format(self.circuit, self.get_term_side_display())
|
||||
return 'Side {}'.format(self.get_term_side_display())
|
||||
|
||||
def log_change(self, user, request_id, action):
|
||||
"""
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db.models.signals import post_delete, post_save
|
||||
from django.dispatch import receiver
|
||||
from django.utils import timezone
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import django_tables2 as tables
|
||||
from django.utils.safestring import mark_safe
|
||||
from django_tables2.utils import Accessor
|
||||
@@ -25,12 +23,6 @@ STATUS_LABEL = """
|
||||
class CircuitTerminationColumn(tables.Column):
|
||||
|
||||
def render(self, value):
|
||||
if value.interface:
|
||||
return mark_safe('<a href="{}" title="{}">{}</a>'.format(
|
||||
value.interface.device.get_absolute_url(),
|
||||
value.site,
|
||||
value.interface.device
|
||||
))
|
||||
return mark_safe('<a href="{}">{}</a>'.format(
|
||||
value.site.get_absolute_url(),
|
||||
value.site
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.urls import reverse
|
||||
from rest_framework import status
|
||||
|
||||
from circuits.constants import CIRCUIT_STATUS_ACTIVE, TERM_SIDE_A, TERM_SIDE_Z
|
||||
from circuits.models import Circuit, CircuitTermination, CircuitType, Provider
|
||||
from dcim.models import Site
|
||||
from dcim.models import Device, DeviceRole, DeviceType, Interface, Manufacturer, Site
|
||||
from extras.constants import GRAPH_TYPE_PROVIDER
|
||||
from extras.models import Graph
|
||||
from utilities.testing import APITestCase
|
||||
@@ -56,6 +54,16 @@ class ProviderTest(APITestCase):
|
||||
|
||||
self.assertEqual(response.data['count'], 3)
|
||||
|
||||
def test_list_providers_brief(self):
|
||||
|
||||
url = reverse('circuits-api:provider-list')
|
||||
response = self.client.get('{}?brief=1'.format(url), **self.header)
|
||||
|
||||
self.assertEqual(
|
||||
sorted(response.data['results'][0]),
|
||||
['id', 'name', 'slug', 'url']
|
||||
)
|
||||
|
||||
def test_create_provider(self):
|
||||
|
||||
data = {
|
||||
@@ -147,6 +155,16 @@ class CircuitTypeTest(APITestCase):
|
||||
|
||||
self.assertEqual(response.data['count'], 3)
|
||||
|
||||
def test_list_circuittypes_brief(self):
|
||||
|
||||
url = reverse('circuits-api:circuittype-list')
|
||||
response = self.client.get('{}?brief=1'.format(url), **self.header)
|
||||
|
||||
self.assertEqual(
|
||||
sorted(response.data['results'][0]),
|
||||
['id', 'name', 'slug', 'url']
|
||||
)
|
||||
|
||||
def test_create_circuittype(self):
|
||||
|
||||
data = {
|
||||
@@ -216,6 +234,16 @@ class CircuitTest(APITestCase):
|
||||
|
||||
self.assertEqual(response.data['count'], 3)
|
||||
|
||||
def test_list_circuits_brief(self):
|
||||
|
||||
url = reverse('circuits-api:circuit-list')
|
||||
response = self.client.get('{}?brief=1'.format(url), **self.header)
|
||||
|
||||
self.assertEqual(
|
||||
sorted(response.data['results'][0]),
|
||||
['cid', 'id', 'url']
|
||||
)
|
||||
|
||||
def test_create_circuit(self):
|
||||
|
||||
data = {
|
||||
@@ -300,21 +328,24 @@ class CircuitTerminationTest(APITestCase):
|
||||
|
||||
super(CircuitTerminationTest, self).setUp()
|
||||
|
||||
self.site1 = Site.objects.create(name='Test Site 1', slug='test-site-1')
|
||||
self.site2 = Site.objects.create(name='Test Site 2', slug='test-site-2')
|
||||
provider = Provider.objects.create(name='Test Provider', slug='test-provider')
|
||||
circuittype = CircuitType.objects.create(name='Test Circuit Type', slug='test-circuit-type')
|
||||
self.circuit1 = Circuit.objects.create(cid='TEST0001', provider=provider, type=circuittype)
|
||||
self.circuit2 = Circuit.objects.create(cid='TEST0002', provider=provider, type=circuittype)
|
||||
self.circuit3 = Circuit.objects.create(cid='TEST0003', provider=provider, type=circuittype)
|
||||
self.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.circuittermination1 = CircuitTermination.objects.create(
|
||||
circuit=self.circuit1, term_side=TERM_SIDE_A, site=self.site1, port_speed=1000000
|
||||
)
|
||||
self.circuittermination2 = CircuitTermination.objects.create(
|
||||
circuit=self.circuit2, term_side=TERM_SIDE_A, site=self.site1, port_speed=1000000
|
||||
circuit=self.circuit1, term_side=TERM_SIDE_Z, site=self.site2, port_speed=1000000
|
||||
)
|
||||
self.circuittermination3 = CircuitTermination.objects.create(
|
||||
circuit=self.circuit3, term_side=TERM_SIDE_A, site=self.site1, port_speed=1000000
|
||||
circuit=self.circuit2, term_side=TERM_SIDE_A, site=self.site1, port_speed=1000000
|
||||
)
|
||||
self.circuittermination4 = CircuitTermination.objects.create(
|
||||
circuit=self.circuit2, term_side=TERM_SIDE_Z, site=self.site2, port_speed=1000000
|
||||
)
|
||||
|
||||
def test_get_circuittermination(self):
|
||||
@@ -329,14 +360,14 @@ class CircuitTerminationTest(APITestCase):
|
||||
url = reverse('circuits-api:circuittermination-list')
|
||||
response = self.client.get(url, **self.header)
|
||||
|
||||
self.assertEqual(response.data['count'], 3)
|
||||
self.assertEqual(response.data['count'], 4)
|
||||
|
||||
def test_create_circuittermination(self):
|
||||
|
||||
data = {
|
||||
'circuit': self.circuit1.pk,
|
||||
'term_side': TERM_SIDE_Z,
|
||||
'site': self.site2.pk,
|
||||
'circuit': self.circuit3.pk,
|
||||
'term_side': TERM_SIDE_A,
|
||||
'site': self.site1.pk,
|
||||
'port_speed': 1000000,
|
||||
}
|
||||
|
||||
@@ -344,7 +375,7 @@ class CircuitTerminationTest(APITestCase):
|
||||
response = self.client.post(url, data, format='json', **self.header)
|
||||
|
||||
self.assertHttpStatus(response, status.HTTP_201_CREATED)
|
||||
self.assertEqual(CircuitTermination.objects.count(), 4)
|
||||
self.assertEqual(CircuitTermination.objects.count(), 5)
|
||||
circuittermination4 = CircuitTermination.objects.get(pk=response.data['id'])
|
||||
self.assertEqual(circuittermination4.circuit_id, data['circuit'])
|
||||
self.assertEqual(circuittermination4.term_side, data['term_side'])
|
||||
@@ -353,20 +384,23 @@ class CircuitTerminationTest(APITestCase):
|
||||
|
||||
def test_update_circuittermination(self):
|
||||
|
||||
circuittermination5 = CircuitTermination.objects.create(
|
||||
circuit=self.circuit3, term_side=TERM_SIDE_A, site=self.site1, port_speed=1000000
|
||||
)
|
||||
|
||||
data = {
|
||||
'circuit': self.circuit1.pk,
|
||||
'circuit': self.circuit3.pk,
|
||||
'term_side': TERM_SIDE_Z,
|
||||
'site': self.site2.pk,
|
||||
'port_speed': 1000000,
|
||||
}
|
||||
|
||||
url = reverse('circuits-api:circuittermination-detail', kwargs={'pk': self.circuittermination1.pk})
|
||||
url = reverse('circuits-api:circuittermination-detail', kwargs={'pk': circuittermination5.pk})
|
||||
response = self.client.put(url, data, format='json', **self.header)
|
||||
|
||||
self.assertHttpStatus(response, status.HTTP_200_OK)
|
||||
self.assertEqual(CircuitTermination.objects.count(), 3)
|
||||
self.assertEqual(CircuitTermination.objects.count(), 5)
|
||||
circuittermination1 = CircuitTermination.objects.get(pk=response.data['id'])
|
||||
self.assertEqual(circuittermination1.circuit_id, data['circuit'])
|
||||
self.assertEqual(circuittermination1.term_side, data['term_side'])
|
||||
self.assertEqual(circuittermination1.site_id, data['site'])
|
||||
self.assertEqual(circuittermination1.port_speed, data['port_speed'])
|
||||
@@ -377,4 +411,4 @@ class CircuitTerminationTest(APITestCase):
|
||||
response = self.client.delete(url, **self.header)
|
||||
|
||||
self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
|
||||
self.assertEqual(CircuitTermination.objects.count(), 2)
|
||||
self.assertEqual(CircuitTermination.objects.count(), 3)
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.conf.urls import url
|
||||
|
||||
from dcim.views import CableCreateView, CableTraceView
|
||||
from extras.views import ObjectChangeLogView
|
||||
from . import views
|
||||
from .models import Circuit, CircuitType, Provider
|
||||
from .models import Circuit, CircuitTermination, CircuitType, Provider
|
||||
|
||||
app_name = 'circuits'
|
||||
urlpatterns = [
|
||||
@@ -44,5 +43,7 @@ urlpatterns = [
|
||||
url(r'^circuits/(?P<circuit>\d+)/terminations/add/$', views.CircuitTerminationCreateView.as_view(), name='circuittermination_add'),
|
||||
url(r'^circuit-terminations/(?P<pk>\d+)/edit/$', views.CircuitTerminationEditView.as_view(), name='circuittermination_edit'),
|
||||
url(r'^circuit-terminations/(?P<pk>\d+)/delete/$', views.CircuitTerminationDeleteView.as_view(), name='circuittermination_delete'),
|
||||
url(r'^circuit-terminations/(?P<termination_a_id>\d+)/connect/$', CableCreateView.as_view(), name='circuittermination_connect', kwargs={'termination_a_type': CircuitTermination}),
|
||||
url(r'^circuit-terminations/(?P<pk>\d+)/trace/$', CableTraceView.as_view(), name='circuittermination_trace', kwargs={'model': CircuitTermination}),
|
||||
|
||||
]
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import permission_required
|
||||
from django.contrib.auth.mixins import PermissionRequiredMixin
|
||||
@@ -134,7 +132,7 @@ class CircuitListView(ObjectListView):
|
||||
queryset = Circuit.objects.select_related(
|
||||
'provider', 'type', 'tenant'
|
||||
).prefetch_related(
|
||||
'terminations__site', 'terminations__interface__device'
|
||||
'terminations__site'
|
||||
)
|
||||
filter = filters.CircuitFilter
|
||||
filter_form = forms.CircuitFilterForm
|
||||
@@ -148,12 +146,12 @@ class CircuitView(View):
|
||||
|
||||
circuit = get_object_or_404(Circuit.objects.select_related('provider', 'type', 'tenant__group'), pk=pk)
|
||||
termination_a = CircuitTermination.objects.select_related(
|
||||
'site__region', 'interface__device'
|
||||
'site__region', 'connected_endpoint__device'
|
||||
).filter(
|
||||
circuit=circuit, term_side=TERM_SIDE_A
|
||||
).first()
|
||||
termination_z = CircuitTermination.objects.select_related(
|
||||
'site__region', 'interface__device'
|
||||
'site__region', 'connected_endpoint__device'
|
||||
).filter(
|
||||
circuit=circuit, term_side=TERM_SIDE_Z
|
||||
).first()
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from rest_framework.exceptions import APIException
|
||||
|
||||
|
||||
|
||||
243
netbox/dcim/api/nested_serializers.py
Normal file
243
netbox/dcim/api/nested_serializers.py
Normal file
@@ -0,0 +1,243 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
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 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)
|
||||
|
||||
class Meta:
|
||||
model = ConsoleServerPort
|
||||
fields = ['id', 'url', 'device', 'name', 'cable']
|
||||
|
||||
|
||||
class NestedConsolePortSerializer(WritableNestedSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:consoleport-detail')
|
||||
device = NestedDeviceSerializer(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = ConsolePort
|
||||
fields = ['id', 'url', 'device', 'name', 'cable']
|
||||
|
||||
|
||||
class NestedPowerOutletSerializer(WritableNestedSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:poweroutlet-detail')
|
||||
device = NestedDeviceSerializer(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = PowerOutlet
|
||||
fields = ['id', 'url', 'device', 'name', 'cable']
|
||||
|
||||
|
||||
class NestedPowerPortSerializer(WritableNestedSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:powerport-detail')
|
||||
device = NestedDeviceSerializer(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = PowerPort
|
||||
fields = ['id', 'url', 'device', 'name', 'cable']
|
||||
|
||||
|
||||
class NestedInterfaceSerializer(WritableNestedSerializer):
|
||||
device = NestedDeviceSerializer(read_only=True)
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:interface-detail')
|
||||
|
||||
class Meta:
|
||||
model = Interface
|
||||
fields = ['id', 'url', 'device', 'name', 'cable']
|
||||
|
||||
|
||||
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,49 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from rest_framework import serializers
|
||||
from rest_framework.validators import UniqueTogetherValidator
|
||||
from taggit_serializer.serializers import TaggitSerializer, TagListSerializerField
|
||||
|
||||
from circuits.models import Circuit, CircuitTermination
|
||||
from dcim.constants import (
|
||||
CONNECTION_STATUS_CHOICES, DEVICE_STATUS_CHOICES, IFACE_FF_CHOICES, IFACE_MODE_CHOICES, IFACE_ORDERING_CHOICES,
|
||||
RACK_FACE_CHOICES, RACK_TYPE_CHOICES, RACK_WIDTH_CHOICES, SITE_STATUS_CHOICES, SUBDEVICE_ROLE_CHOICES,
|
||||
)
|
||||
from dcim.constants import *
|
||||
from dcim.models import (
|
||||
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
||||
DeviceBayTemplate, DeviceType, DeviceRole, Interface, InterfaceConnection, InterfaceTemplate, Manufacturer,
|
||||
InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup,
|
||||
RackReservation, RackRole, Region, Site, VirtualChassis,
|
||||
Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
||||
DeviceBayTemplate, DeviceType, DeviceRole, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate,
|
||||
Manufacturer, InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack,
|
||||
RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site, VirtualChassis,
|
||||
)
|
||||
from extras.api.customfields import CustomFieldModelSerializer
|
||||
from ipam.models import IPAddress, VLAN
|
||||
from tenancy.api.serializers import NestedTenantSerializer
|
||||
from users.api.serializers import NestedUserSerializer
|
||||
from ipam.api.nested_serializers import NestedIPAddressSerializer, NestedVLANSerializer
|
||||
from ipam.models import VLAN
|
||||
from tenancy.api.nested_serializers import NestedTenantSerializer
|
||||
from users.api.nested_serializers import NestedUserSerializer
|
||||
from utilities.api import (
|
||||
ChoiceField, SerializedPKRelatedField, TimeZoneField, ValidatedModelSerializer,
|
||||
WritableNestedSerializer,
|
||||
ChoiceField, ContentTypeField, SerializedPKRelatedField, TimeZoneField, ValidatedModelSerializer,
|
||||
WritableNestedSerializer, get_serializer_for_model,
|
||||
)
|
||||
from virtualization.models import Cluster
|
||||
from virtualization.api.nested_serializers import NestedClusterSerializer
|
||||
from .nested_serializers import *
|
||||
|
||||
|
||||
class ConnectedEndpointSerializer(ValidatedModelSerializer):
|
||||
connected_endpoint = serializers.SerializerMethodField(read_only=True)
|
||||
connection_status = ChoiceField(choices=CONNECTION_STATUS_CHOICES, read_only=True)
|
||||
|
||||
def get_connected_endpoint(self, obj):
|
||||
"""
|
||||
Return the appropriate serializer for the type of connected object.
|
||||
"""
|
||||
if getattr(obj, 'connected_endpoint', None) is None:
|
||||
return None
|
||||
|
||||
serializer = get_serializer_for_model(obj.connected_endpoint, prefix='Nested')
|
||||
context = {'request': self.context['request']}
|
||||
data = serializer(obj.connected_endpoint, context=context).data
|
||||
|
||||
return data
|
||||
|
||||
|
||||
#
|
||||
# Regions
|
||||
# Regions/sites
|
||||
#
|
||||
|
||||
class NestedRegionSerializer(WritableNestedSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:region-detail')
|
||||
|
||||
class Meta:
|
||||
model = Region
|
||||
fields = ['id', 'url', 'name', 'slug']
|
||||
|
||||
|
||||
class RegionSerializer(serializers.ModelSerializer):
|
||||
parent = NestedRegionSerializer(required=False, allow_null=True)
|
||||
|
||||
@@ -46,10 +52,6 @@ class RegionSerializer(serializers.ModelSerializer):
|
||||
fields = ['id', 'name', 'slug', 'parent']
|
||||
|
||||
|
||||
#
|
||||
# Sites
|
||||
#
|
||||
|
||||
class SiteSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
||||
status = ChoiceField(choices=SITE_STATUS_CHOICES, required=False)
|
||||
region = NestedRegionSerializer(required=False, allow_null=True)
|
||||
@@ -67,16 +69,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):
|
||||
@@ -87,18 +81,6 @@ class RackGroupSerializer(ValidatedModelSerializer):
|
||||
fields = ['id', 'name', 'slug', 'site']
|
||||
|
||||
|
||||
class NestedRackGroupSerializer(WritableNestedSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rackgroup-detail')
|
||||
|
||||
class Meta:
|
||||
model = RackGroup
|
||||
fields = ['id', 'url', 'name', 'slug']
|
||||
|
||||
|
||||
#
|
||||
# Rack roles
|
||||
#
|
||||
|
||||
class RackRoleSerializer(ValidatedModelSerializer):
|
||||
|
||||
class Meta:
|
||||
@@ -106,32 +88,23 @@ class RackRoleSerializer(ValidatedModelSerializer):
|
||||
fields = ['id', 'name', 'slug', 'color']
|
||||
|
||||
|
||||
class NestedRackRoleSerializer(WritableNestedSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rackrole-detail')
|
||||
|
||||
class Meta:
|
||||
model = RackRole
|
||||
fields = ['id', 'url', 'name', 'slug']
|
||||
|
||||
|
||||
#
|
||||
# Racks
|
||||
#
|
||||
|
||||
class RackSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
||||
site = NestedSiteSerializer()
|
||||
group = NestedRackGroupSerializer(required=False, allow_null=True)
|
||||
group = NestedRackGroupSerializer(required=False, allow_null=True, default=None)
|
||||
tenant = NestedTenantSerializer(required=False, allow_null=True)
|
||||
status = ChoiceField(choices=RACK_STATUS_CHOICES, required=False)
|
||||
role = NestedRackRoleSerializer(required=False, allow_null=True)
|
||||
type = ChoiceField(choices=RACK_TYPE_CHOICES, required=False, allow_null=True)
|
||||
width = ChoiceField(choices=RACK_WIDTH_CHOICES, required=False)
|
||||
outer_unit = ChoiceField(choices=RACK_DIMENSION_UNIT_CHOICES, required=False)
|
||||
tags = TagListSerializerField(required=False)
|
||||
|
||||
class Meta:
|
||||
model = Rack
|
||||
fields = [
|
||||
'id', 'name', 'facility_id', 'display_name', 'site', 'group', 'tenant', 'role', 'serial', 'type', 'width',
|
||||
'u_height', 'desc_units', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
|
||||
'id', 'name', 'facility_id', 'display_name', 'site', 'group', 'tenant', 'status', 'role', 'serial',
|
||||
'asset_tag', 'type', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit',
|
||||
'comments', 'tags', 'custom_fields', 'created', 'last_updated',
|
||||
]
|
||||
# Omit the UniqueTogetherValidator that would be automatically added to validate (group, facility_id). This
|
||||
# prevents facility_id from being interpreted as a required field.
|
||||
@@ -153,26 +126,6 @@ class RackSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
||||
return data
|
||||
|
||||
|
||||
class NestedRackSerializer(WritableNestedSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rack-detail')
|
||||
|
||||
class Meta:
|
||||
model = Rack
|
||||
fields = ['id', 'url', 'name', 'display_name']
|
||||
|
||||
|
||||
#
|
||||
# Rack units
|
||||
#
|
||||
|
||||
class NestedDeviceSerializer(WritableNestedSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:device-detail')
|
||||
|
||||
class Meta:
|
||||
model = Device
|
||||
fields = ['id', 'url', 'name', 'display_name']
|
||||
|
||||
|
||||
class RackUnitSerializer(serializers.Serializer):
|
||||
"""
|
||||
A rack unit is an abstraction formed by the set (rack, position, face); it does not exist as a row in the database.
|
||||
@@ -183,10 +136,6 @@ class RackUnitSerializer(serializers.Serializer):
|
||||
device = NestedDeviceSerializer(read_only=True)
|
||||
|
||||
|
||||
#
|
||||
# Rack reservations
|
||||
#
|
||||
|
||||
class RackReservationSerializer(ValidatedModelSerializer):
|
||||
rack = NestedRackSerializer()
|
||||
user = NestedUserSerializer()
|
||||
@@ -198,7 +147,7 @@ class RackReservationSerializer(ValidatedModelSerializer):
|
||||
|
||||
|
||||
#
|
||||
# Manufacturers
|
||||
# Device types
|
||||
#
|
||||
|
||||
class ManufacturerSerializer(ValidatedModelSerializer):
|
||||
@@ -208,21 +157,8 @@ class ManufacturerSerializer(ValidatedModelSerializer):
|
||||
fields = ['id', 'name', 'slug']
|
||||
|
||||
|
||||
class NestedManufacturerSerializer(WritableNestedSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:manufacturer-detail')
|
||||
|
||||
class Meta:
|
||||
model = Manufacturer
|
||||
fields = ['id', 'url', 'name', 'slug']
|
||||
|
||||
|
||||
#
|
||||
# Device types
|
||||
#
|
||||
|
||||
class DeviceTypeSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
||||
manufacturer = NestedManufacturerSerializer()
|
||||
interface_ordering = ChoiceField(choices=IFACE_ORDERING_CHOICES, required=False)
|
||||
subdevice_role = ChoiceField(choices=SUBDEVICE_ROLE_CHOICES, required=False, allow_null=True)
|
||||
instance_count = serializers.IntegerField(source='instances.count', read_only=True)
|
||||
tags = TagListSerializerField(required=False)
|
||||
@@ -230,25 +166,11 @@ class DeviceTypeSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
||||
class Meta:
|
||||
model = DeviceType
|
||||
fields = [
|
||||
'id', 'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'interface_ordering',
|
||||
'is_console_server', 'is_pdu', 'is_network_device', 'subdevice_role', 'comments', 'tags', 'custom_fields',
|
||||
'created', 'last_updated', 'instance_count',
|
||||
'id', 'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role',
|
||||
'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'instance_count',
|
||||
]
|
||||
|
||||
|
||||
class NestedDeviceTypeSerializer(WritableNestedSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicetype-detail')
|
||||
manufacturer = NestedManufacturerSerializer(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = DeviceType
|
||||
fields = ['id', 'url', 'manufacturer', 'model', 'slug']
|
||||
|
||||
|
||||
#
|
||||
# Console port templates
|
||||
#
|
||||
|
||||
class ConsolePortTemplateSerializer(ValidatedModelSerializer):
|
||||
device_type = NestedDeviceTypeSerializer()
|
||||
|
||||
@@ -257,10 +179,6 @@ class ConsolePortTemplateSerializer(ValidatedModelSerializer):
|
||||
fields = ['id', 'device_type', 'name']
|
||||
|
||||
|
||||
#
|
||||
# Console server port templates
|
||||
#
|
||||
|
||||
class ConsoleServerPortTemplateSerializer(ValidatedModelSerializer):
|
||||
device_type = NestedDeviceTypeSerializer()
|
||||
|
||||
@@ -269,10 +187,6 @@ class ConsoleServerPortTemplateSerializer(ValidatedModelSerializer):
|
||||
fields = ['id', 'device_type', 'name']
|
||||
|
||||
|
||||
#
|
||||
# Power port templates
|
||||
#
|
||||
|
||||
class PowerPortTemplateSerializer(ValidatedModelSerializer):
|
||||
device_type = NestedDeviceTypeSerializer()
|
||||
|
||||
@@ -281,10 +195,6 @@ class PowerPortTemplateSerializer(ValidatedModelSerializer):
|
||||
fields = ['id', 'device_type', 'name']
|
||||
|
||||
|
||||
#
|
||||
# Power outlet templates
|
||||
#
|
||||
|
||||
class PowerOutletTemplateSerializer(ValidatedModelSerializer):
|
||||
device_type = NestedDeviceTypeSerializer()
|
||||
|
||||
@@ -293,10 +203,6 @@ class PowerOutletTemplateSerializer(ValidatedModelSerializer):
|
||||
fields = ['id', 'device_type', 'name']
|
||||
|
||||
|
||||
#
|
||||
# Interface templates
|
||||
#
|
||||
|
||||
class InterfaceTemplateSerializer(ValidatedModelSerializer):
|
||||
device_type = NestedDeviceTypeSerializer()
|
||||
form_factor = ChoiceField(choices=IFACE_FF_CHOICES, required=False)
|
||||
@@ -306,9 +212,24 @@ class InterfaceTemplateSerializer(ValidatedModelSerializer):
|
||||
fields = ['id', 'device_type', 'name', 'form_factor', 'mgmt_only']
|
||||
|
||||
|
||||
#
|
||||
# Device bay templates
|
||||
#
|
||||
class RearPortTemplateSerializer(ValidatedModelSerializer):
|
||||
device_type = NestedDeviceTypeSerializer()
|
||||
type = ChoiceField(choices=PORT_TYPE_CHOICES)
|
||||
|
||||
class Meta:
|
||||
model = RearPortTemplate
|
||||
fields = ['id', 'device_type', 'name', 'type', 'positions']
|
||||
|
||||
|
||||
class FrontPortTemplateSerializer(ValidatedModelSerializer):
|
||||
device_type = NestedDeviceTypeSerializer()
|
||||
type = ChoiceField(choices=PORT_TYPE_CHOICES)
|
||||
rear_port = NestedRearPortTemplateSerializer()
|
||||
|
||||
class Meta:
|
||||
model = FrontPortTemplate
|
||||
fields = ['id', 'device_type', 'name', 'type', 'rear_port', 'rear_port_position']
|
||||
|
||||
|
||||
class DeviceBayTemplateSerializer(ValidatedModelSerializer):
|
||||
device_type = NestedDeviceTypeSerializer()
|
||||
@@ -319,7 +240,7 @@ class DeviceBayTemplateSerializer(ValidatedModelSerializer):
|
||||
|
||||
|
||||
#
|
||||
# Device roles
|
||||
# Devices
|
||||
#
|
||||
|
||||
class DeviceRoleSerializer(ValidatedModelSerializer):
|
||||
@@ -329,64 +250,12 @@ class DeviceRoleSerializer(ValidatedModelSerializer):
|
||||
fields = ['id', 'name', 'slug', 'color', 'vm_role']
|
||||
|
||||
|
||||
class NestedDeviceRoleSerializer(WritableNestedSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicerole-detail')
|
||||
|
||||
class Meta:
|
||||
model = DeviceRole
|
||||
fields = ['id', 'url', 'name', 'slug']
|
||||
|
||||
|
||||
#
|
||||
# Platforms
|
||||
#
|
||||
|
||||
class PlatformSerializer(ValidatedModelSerializer):
|
||||
manufacturer = NestedManufacturerSerializer(required=False, allow_null=True)
|
||||
|
||||
class Meta:
|
||||
model = Platform
|
||||
fields = ['id', 'name', 'slug', 'manufacturer', 'napalm_driver', 'napalm_args', 'rpc_client']
|
||||
|
||||
|
||||
class NestedPlatformSerializer(WritableNestedSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:platform-detail')
|
||||
|
||||
class Meta:
|
||||
model = Platform
|
||||
fields = ['id', 'url', 'name', 'slug']
|
||||
|
||||
|
||||
#
|
||||
# Devices
|
||||
#
|
||||
|
||||
# Cannot import ipam.api.NestedIPAddressSerializer due to circular dependency
|
||||
class DeviceIPAddressSerializer(WritableNestedSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='ipam-api:ipaddress-detail')
|
||||
|
||||
class Meta:
|
||||
model = IPAddress
|
||||
fields = ['id', 'url', 'family', 'address']
|
||||
|
||||
|
||||
# Cannot import virtualization.api.NestedClusterSerializer due to circular dependency
|
||||
class NestedClusterSerializer(WritableNestedSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='virtualization-api:cluster-detail')
|
||||
|
||||
class Meta:
|
||||
model = Cluster
|
||||
fields = ['id', 'url', 'name']
|
||||
|
||||
|
||||
# Cannot import NestedVirtualChassisSerializer due to circular dependency
|
||||
class DeviceVirtualChassisSerializer(WritableNestedSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:virtualchassis-detail')
|
||||
master = NestedDeviceSerializer()
|
||||
|
||||
class Meta:
|
||||
model = VirtualChassis
|
||||
fields = ['id', 'url', 'master']
|
||||
fields = ['id', 'name', 'slug', 'manufacturer', 'napalm_driver', 'napalm_args']
|
||||
|
||||
|
||||
class DeviceSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
||||
@@ -398,12 +267,12 @@ class DeviceSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
||||
rack = NestedRackSerializer(required=False, allow_null=True)
|
||||
face = ChoiceField(choices=RACK_FACE_CHOICES, required=False, allow_null=True)
|
||||
status = ChoiceField(choices=DEVICE_STATUS_CHOICES, required=False)
|
||||
primary_ip = DeviceIPAddressSerializer(read_only=True)
|
||||
primary_ip4 = DeviceIPAddressSerializer(required=False, allow_null=True)
|
||||
primary_ip6 = DeviceIPAddressSerializer(required=False, allow_null=True)
|
||||
primary_ip = NestedIPAddressSerializer(read_only=True)
|
||||
primary_ip4 = NestedIPAddressSerializer(required=False, allow_null=True)
|
||||
primary_ip6 = NestedIPAddressSerializer(required=False, allow_null=True)
|
||||
parent_device = serializers.SerializerMethodField()
|
||||
cluster = NestedClusterSerializer(required=False, allow_null=True)
|
||||
virtual_chassis = DeviceVirtualChassisSerializer(required=False, allow_null=True)
|
||||
virtual_chassis = NestedVirtualChassisSerializer(required=False, allow_null=True)
|
||||
tags = TagListSerializerField(required=False)
|
||||
|
||||
class Meta:
|
||||
@@ -411,8 +280,8 @@ class DeviceSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
||||
fields = [
|
||||
'id', 'name', 'display_name', 'device_type', 'device_role', 'tenant', 'platform', 'serial', 'asset_tag',
|
||||
'site', 'rack', 'position', 'face', 'parent_device', 'status', 'primary_ip', 'primary_ip4', 'primary_ip6',
|
||||
'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'comments', 'tags', 'custom_fields', 'created',
|
||||
'last_updated',
|
||||
'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'comments', 'local_context_data', 'tags',
|
||||
'custom_fields', 'created', 'last_updated',
|
||||
]
|
||||
validators = []
|
||||
|
||||
@@ -447,153 +316,78 @@ class DeviceWithConfigContextSerializer(DeviceSerializer):
|
||||
fields = [
|
||||
'id', 'name', 'display_name', 'device_type', 'device_role', 'tenant', 'platform', 'serial', 'asset_tag',
|
||||
'site', 'rack', 'position', 'face', 'parent_device', 'status', 'primary_ip', 'primary_ip4', 'primary_ip6',
|
||||
'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'comments', 'tags', 'custom_fields',
|
||||
'config_context', 'created', 'last_updated',
|
||||
'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'comments', 'local_context_data', 'tags',
|
||||
'custom_fields', 'config_context', 'created', 'last_updated',
|
||||
]
|
||||
|
||||
def get_config_context(self, obj):
|
||||
return obj.get_config_context()
|
||||
|
||||
|
||||
#
|
||||
# Console server ports
|
||||
#
|
||||
|
||||
class ConsoleServerPortSerializer(TaggitSerializer, ValidatedModelSerializer):
|
||||
class ConsoleServerPortSerializer(TaggitSerializer, ConnectedEndpointSerializer):
|
||||
device = NestedDeviceSerializer()
|
||||
cable = NestedCableSerializer(read_only=True)
|
||||
tags = TagListSerializerField(required=False)
|
||||
|
||||
class Meta:
|
||||
model = ConsoleServerPort
|
||||
fields = ['id', 'device', 'name', 'connected_console', 'tags']
|
||||
read_only_fields = ['connected_console']
|
||||
fields = ['id', 'device', 'name', 'connected_endpoint', 'connection_status', 'cable', 'tags']
|
||||
|
||||
|
||||
class NestedConsoleServerPortSerializer(WritableNestedSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:consoleserverport-detail')
|
||||
device = NestedDeviceSerializer(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = ConsoleServerPort
|
||||
fields = ['id', 'url', 'device', 'name']
|
||||
|
||||
|
||||
#
|
||||
# Console ports
|
||||
#
|
||||
|
||||
class ConsolePortSerializer(TaggitSerializer, ValidatedModelSerializer):
|
||||
class ConsolePortSerializer(TaggitSerializer, ConnectedEndpointSerializer):
|
||||
device = NestedDeviceSerializer()
|
||||
cs_port = NestedConsoleServerPortSerializer(required=False, allow_null=True)
|
||||
cable = NestedCableSerializer(read_only=True)
|
||||
tags = TagListSerializerField(required=False)
|
||||
|
||||
class Meta:
|
||||
model = ConsolePort
|
||||
fields = ['id', 'device', 'name', 'cs_port', 'connection_status', 'tags']
|
||||
fields = ['id', 'device', 'name', 'connected_endpoint', 'connection_status', 'cable', 'tags']
|
||||
|
||||
|
||||
#
|
||||
# Power outlets
|
||||
#
|
||||
|
||||
class PowerOutletSerializer(TaggitSerializer, 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_port', 'tags']
|
||||
read_only_fields = ['connected_port']
|
||||
fields = ['id', 'device', 'name', 'connected_endpoint', 'connection_status', 'cable', 'tags']
|
||||
|
||||
|
||||
class NestedPowerOutletSerializer(WritableNestedSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:poweroutlet-detail')
|
||||
device = NestedDeviceSerializer(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = PowerOutlet
|
||||
fields = ['id', 'url', 'device', 'name']
|
||||
|
||||
|
||||
#
|
||||
# Power ports
|
||||
#
|
||||
|
||||
class PowerPortSerializer(TaggitSerializer, ValidatedModelSerializer):
|
||||
class PowerPortSerializer(TaggitSerializer, ConnectedEndpointSerializer):
|
||||
device = NestedDeviceSerializer()
|
||||
power_outlet = NestedPowerOutletSerializer(required=False, allow_null=True)
|
||||
cable = NestedCableSerializer(read_only=True)
|
||||
tags = TagListSerializerField(required=False)
|
||||
|
||||
class Meta:
|
||||
model = PowerPort
|
||||
fields = ['id', 'device', 'name', 'power_outlet', 'connection_status', 'tags']
|
||||
fields = ['id', 'device', 'name', 'connected_endpoint', 'connection_status', 'cable', 'tags']
|
||||
|
||||
|
||||
#
|
||||
# Interfaces
|
||||
#
|
||||
|
||||
class NestedInterfaceSerializer(WritableNestedSerializer):
|
||||
device = NestedDeviceSerializer(read_only=True)
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:interface-detail')
|
||||
|
||||
class Meta:
|
||||
model = Interface
|
||||
fields = ['id', 'url', 'device', 'name']
|
||||
|
||||
|
||||
class InterfaceNestedCircuitSerializer(serializers.ModelSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='circuits-api:circuit-detail')
|
||||
|
||||
class Meta:
|
||||
model = Circuit
|
||||
fields = ['id', 'url', 'cid']
|
||||
|
||||
|
||||
class InterfaceCircuitTerminationSerializer(WritableNestedSerializer):
|
||||
circuit = InterfaceNestedCircuitSerializer(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = CircuitTermination
|
||||
fields = [
|
||||
'id', 'circuit', 'term_side', 'port_speed', 'upstream_speed', 'xconnect_id', 'pp_info',
|
||||
]
|
||||
|
||||
|
||||
# Cannot import ipam.api.NestedVLANSerializer due to circular dependency
|
||||
class InterfaceVLANSerializer(WritableNestedSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='ipam-api:vlan-detail')
|
||||
|
||||
class Meta:
|
||||
model = VLAN
|
||||
fields = ['id', 'url', 'vid', 'name', 'display_name']
|
||||
|
||||
|
||||
class InterfaceSerializer(TaggitSerializer, ValidatedModelSerializer):
|
||||
class InterfaceSerializer(TaggitSerializer, ConnectedEndpointSerializer):
|
||||
device = NestedDeviceSerializer()
|
||||
form_factor = ChoiceField(choices=IFACE_FF_CHOICES, required=False)
|
||||
lag = NestedInterfaceSerializer(required=False, allow_null=True)
|
||||
is_connected = serializers.SerializerMethodField(read_only=True)
|
||||
interface_connection = serializers.SerializerMethodField(read_only=True)
|
||||
circuit_termination = InterfaceCircuitTerminationSerializer(read_only=True)
|
||||
mode = ChoiceField(choices=IFACE_MODE_CHOICES, required=False, allow_null=True)
|
||||
untagged_vlan = InterfaceVLANSerializer(required=False, allow_null=True)
|
||||
untagged_vlan = NestedVLANSerializer(required=False, allow_null=True)
|
||||
tagged_vlans = SerializedPKRelatedField(
|
||||
queryset=VLAN.objects.all(),
|
||||
serializer=InterfaceVLANSerializer,
|
||||
serializer=NestedVLANSerializer,
|
||||
required=False,
|
||||
many=True
|
||||
)
|
||||
cable = NestedCableSerializer(read_only=True)
|
||||
tags = TagListSerializerField(required=False)
|
||||
|
||||
class Meta:
|
||||
model = Interface
|
||||
fields = [
|
||||
'id', 'device', 'name', 'form_factor', 'enabled', 'lag', 'mtu', 'mac_address', 'mgmt_only', 'description',
|
||||
'is_connected', 'interface_connection', 'circuit_termination', 'mode', 'untagged_vlan', 'tagged_vlans',
|
||||
'tags',
|
||||
'connected_endpoint', 'connection_status', 'cable', 'mode', 'untagged_vlan', 'tagged_vlans', 'tags',
|
||||
'count_ipaddresses',
|
||||
]
|
||||
|
||||
# TODO: This validation should be handled by Interface.clean()
|
||||
def validate(self, data):
|
||||
|
||||
# All associated VLANs be global or assigned to the parent device's site.
|
||||
@@ -613,32 +407,40 @@ class InterfaceSerializer(TaggitSerializer, ValidatedModelSerializer):
|
||||
|
||||
return super(InterfaceSerializer, self).validate(data)
|
||||
|
||||
def get_is_connected(self, obj):
|
||||
"""
|
||||
Return True if the interface has a connected interface or circuit termination.
|
||||
"""
|
||||
if obj.connection:
|
||||
return True
|
||||
try:
|
||||
circuit_termination = obj.circuit_termination
|
||||
return True
|
||||
except CircuitTermination.DoesNotExist:
|
||||
pass
|
||||
return False
|
||||
|
||||
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 = 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']
|
||||
|
||||
|
||||
#
|
||||
# Device bays
|
||||
#
|
||||
class FrontPortRearPortSerializer(WritableNestedSerializer):
|
||||
"""
|
||||
NestedRearPortSerializer but with parent device omitted (since front and rear ports must belong to same device)
|
||||
"""
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:rearport-detail')
|
||||
|
||||
class Meta:
|
||||
model = RearPort
|
||||
fields = ['id', 'url', 'name']
|
||||
|
||||
|
||||
class FrontPortSerializer(ValidatedModelSerializer):
|
||||
device = NestedDeviceSerializer()
|
||||
type = ChoiceField(choices=PORT_TYPE_CHOICES)
|
||||
rear_port = FrontPortRearPortSerializer()
|
||||
cable = NestedCableSerializer(read_only=True)
|
||||
tags = TagListSerializerField(required=False)
|
||||
|
||||
class Meta:
|
||||
model = FrontPort
|
||||
fields = ['id', 'device', 'name', 'type', 'rear_port', 'rear_port_position', 'description', 'cable', 'tags']
|
||||
|
||||
|
||||
class DeviceBaySerializer(TaggitSerializer, ValidatedModelSerializer):
|
||||
device = NestedDeviceSerializer()
|
||||
@@ -650,14 +452,6 @@ class DeviceBaySerializer(TaggitSerializer, ValidatedModelSerializer):
|
||||
fields = ['id', 'device', 'name', 'installed_device', 'tags']
|
||||
|
||||
|
||||
class NestedDeviceBaySerializer(WritableNestedSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:devicebay-detail')
|
||||
|
||||
class Meta:
|
||||
model = DeviceBay
|
||||
fields = ['id', 'url', 'name']
|
||||
|
||||
|
||||
#
|
||||
# Inventory items
|
||||
#
|
||||
@@ -666,7 +460,7 @@ class InventoryItemSerializer(TaggitSerializer, ValidatedModelSerializer):
|
||||
device = NestedDeviceSerializer()
|
||||
# Provide a default value to satisfy UniqueTogetherValidator
|
||||
parent = serializers.PrimaryKeyRelatedField(queryset=InventoryItem.objects.all(), allow_null=True, default=None)
|
||||
manufacturer = NestedManufacturerSerializer()
|
||||
manufacturer = NestedManufacturerSerializer(required=False, allow_null=True, default=None)
|
||||
tags = TagListSerializerField(required=False)
|
||||
|
||||
class Meta:
|
||||
@@ -677,41 +471,76 @@ class InventoryItemSerializer(TaggitSerializer, ValidatedModelSerializer):
|
||||
]
|
||||
|
||||
|
||||
#
|
||||
# Cables
|
||||
#
|
||||
|
||||
class CableSerializer(ValidatedModelSerializer):
|
||||
termination_a_type = ContentTypeField()
|
||||
termination_b_type = ContentTypeField()
|
||||
termination_a = serializers.SerializerMethodField(read_only=True)
|
||||
termination_b = serializers.SerializerMethodField(read_only=True)
|
||||
status = ChoiceField(choices=CONNECTION_STATUS_CHOICES, required=False)
|
||||
length_unit = ChoiceField(choices=CABLE_LENGTH_UNIT_CHOICES, required=False)
|
||||
|
||||
class Meta:
|
||||
model = Cable
|
||||
fields = [
|
||||
'id', 'termination_a_type', 'termination_a_id', 'termination_a', 'termination_b_type', 'termination_b_id',
|
||||
'termination_b', 'type', 'status', 'label', 'color', 'length', 'length_unit',
|
||||
]
|
||||
|
||||
def _get_termination(self, obj, side):
|
||||
"""
|
||||
Serialize a nested representation of a termination.
|
||||
"""
|
||||
if side.lower() not in ['a', 'b']:
|
||||
raise ValueError("Termination side must be either A or B.")
|
||||
termination = getattr(obj, 'termination_{}'.format(side.lower()))
|
||||
if termination is None:
|
||||
return None
|
||||
serializer = get_serializer_for_model(termination, prefix='Nested')
|
||||
context = {'request': self.context['request']}
|
||||
data = serializer(termination, context=context).data
|
||||
|
||||
return data
|
||||
|
||||
def get_termination_a(self, obj):
|
||||
return self._get_termination(obj, 'a')
|
||||
|
||||
def get_termination_b(self, obj):
|
||||
return self._get_termination(obj, 'b')
|
||||
|
||||
|
||||
class TracedCableSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
Used only while tracing a cable path.
|
||||
"""
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:cable-detail')
|
||||
|
||||
class Meta:
|
||||
model = Cable
|
||||
fields = [
|
||||
'id', 'url', 'type', 'status', 'label', 'color', 'length', 'length_unit',
|
||||
]
|
||||
|
||||
|
||||
#
|
||||
# Interface connections
|
||||
#
|
||||
|
||||
class InterfaceConnectionSerializer(ValidatedModelSerializer):
|
||||
interface_a = NestedInterfaceSerializer()
|
||||
interface_b = NestedInterfaceSerializer()
|
||||
interface_a = serializers.SerializerMethodField()
|
||||
interface_b = NestedInterfaceSerializer(source='connected_endpoint')
|
||||
connection_status = ChoiceField(choices=CONNECTION_STATUS_CHOICES, required=False)
|
||||
|
||||
class Meta:
|
||||
model = InterfaceConnection
|
||||
fields = ['id', 'interface_a', 'interface_b', 'connection_status']
|
||||
model = Interface
|
||||
fields = ['interface_a', 'interface_b', 'connection_status']
|
||||
|
||||
|
||||
class NestedInterfaceConnectionSerializer(WritableNestedSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:interfaceconnection-detail')
|
||||
|
||||
class Meta:
|
||||
model = InterfaceConnection
|
||||
fields = ['id', 'url', 'connection_status']
|
||||
|
||||
|
||||
class ContextualInterfaceConnectionSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
A read-only representation of an InterfaceConnection from the perspective of either of its two connected Interfaces.
|
||||
"""
|
||||
interface = serializers.SerializerMethodField(read_only=True)
|
||||
connection_status = ChoiceField(choices=CONNECTION_STATUS_CHOICES, read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = InterfaceConnection
|
||||
fields = ['id', 'interface', 'connection_status']
|
||||
|
||||
def get_interface(self, obj):
|
||||
return NestedInterfaceSerializer(self.context['interface'], context=self.context).data
|
||||
def get_interface_a(self, obj):
|
||||
context = {'request': self.context['request']}
|
||||
return NestedInterfaceSerializer(instance=obj, context=context).data
|
||||
|
||||
|
||||
#
|
||||
@@ -725,11 +554,3 @@ class VirtualChassisSerializer(TaggitSerializer, ValidatedModelSerializer):
|
||||
class Meta:
|
||||
model = VirtualChassis
|
||||
fields = ['id', 'master', 'domain', 'tags']
|
||||
|
||||
|
||||
class NestedVirtualChassisSerializer(WritableNestedSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:virtualchassis-detail')
|
||||
|
||||
class Meta:
|
||||
model = VirtualChassis
|
||||
fields = ['id', 'url']
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from rest_framework import routers
|
||||
|
||||
from . import views
|
||||
@@ -17,7 +15,7 @@ router = routers.DefaultRouter()
|
||||
router.APIRootView = DCIMRootView
|
||||
|
||||
# Field choices
|
||||
router.register(r'_choices', views.DCIMFieldChoicesViewSet, base_name='field-choice')
|
||||
router.register(r'_choices', views.DCIMFieldChoicesViewSet, basename='field-choice')
|
||||
|
||||
# Sites
|
||||
router.register(r'regions', views.RegionViewSet)
|
||||
@@ -39,6 +37,8 @@ router.register(r'console-server-port-templates', views.ConsoleServerPortTemplat
|
||||
router.register(r'power-port-templates', views.PowerPortTemplateViewSet)
|
||||
router.register(r'power-outlet-templates', views.PowerOutletTemplateViewSet)
|
||||
router.register(r'interface-templates', views.InterfaceTemplateViewSet)
|
||||
router.register(r'front-port-templates', views.FrontPortTemplateViewSet)
|
||||
router.register(r'rear-port-templates', views.RearPortTemplateViewSet)
|
||||
router.register(r'device-bay-templates', views.DeviceBayTemplateViewSet)
|
||||
|
||||
# Devices
|
||||
@@ -52,19 +52,24 @@ router.register(r'console-server-ports', views.ConsoleServerPortViewSet)
|
||||
router.register(r'power-ports', views.PowerPortViewSet)
|
||||
router.register(r'power-outlets', views.PowerOutletViewSet)
|
||||
router.register(r'interfaces', views.InterfaceViewSet)
|
||||
router.register(r'front-ports', views.FrontPortViewSet)
|
||||
router.register(r'rear-ports', views.RearPortViewSet)
|
||||
router.register(r'device-bays', views.DeviceBayViewSet)
|
||||
router.register(r'inventory-items', views.InventoryItemViewSet)
|
||||
|
||||
# Connections
|
||||
router.register(r'console-connections', views.ConsoleConnectionViewSet, base_name='consoleconnections')
|
||||
router.register(r'power-connections', views.PowerConnectionViewSet, base_name='powerconnections')
|
||||
router.register(r'interface-connections', views.InterfaceConnectionViewSet)
|
||||
router.register(r'console-connections', views.ConsoleConnectionViewSet, basename='consoleconnections')
|
||||
router.register(r'power-connections', views.PowerConnectionViewSet, basename='powerconnections')
|
||||
router.register(r'interface-connections', views.InterfaceConnectionViewSet, basename='interfaceconnections')
|
||||
|
||||
# Cables
|
||||
router.register(r'cables', views.CableViewSet)
|
||||
|
||||
# Virtual chassis
|
||||
router.register(r'virtual-chassis', views.VirtualChassisViewSet)
|
||||
|
||||
# Miscellaneous
|
||||
router.register(r'connected-device', views.ConnectedDeviceViewSet, base_name='connected-device')
|
||||
router.register(r'connected-device', views.ConnectedDeviceViewSet, basename='connected-device')
|
||||
|
||||
app_name = 'dcim-api'
|
||||
urlpatterns = router.urls
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from collections import OrderedDict
|
||||
|
||||
from django.conf import settings
|
||||
from django.http import HttpResponseBadRequest, HttpResponseForbidden
|
||||
from django.db.models import F, Q
|
||||
from django.http import HttpResponseForbidden
|
||||
from django.shortcuts import get_object_or_404
|
||||
from drf_yasg import openapi
|
||||
from drf_yasg.openapi import Parameter
|
||||
@@ -15,15 +14,17 @@ from rest_framework.viewsets import GenericViewSet, ViewSet
|
||||
|
||||
from dcim import filters
|
||||
from dcim.models import (
|
||||
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
||||
DeviceBayTemplate, DeviceRole, DeviceType, Interface, InterfaceConnection, InterfaceTemplate, Manufacturer,
|
||||
InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup,
|
||||
RackReservation, RackRole, Region, Site, VirtualChassis,
|
||||
Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
||||
DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate,
|
||||
Manufacturer, InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack,
|
||||
RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site, VirtualChassis,
|
||||
)
|
||||
from extras.api.serializers import RenderedGraphSerializer
|
||||
from extras.api.views import CustomFieldModelViewSet
|
||||
from extras.models import Graph, GRAPH_TYPE_INTERFACE, GRAPH_TYPE_SITE
|
||||
from utilities.api import IsAuthenticatedOrLoginNotRequired, FieldChoicesViewSet, ModelViewSet, ServiceUnavailable
|
||||
from utilities.api import (
|
||||
get_serializer_for_model, IsAuthenticatedOrLoginNotRequired, FieldChoicesViewSet, ModelViewSet, ServiceUnavailable,
|
||||
)
|
||||
from . import serializers
|
||||
from .exceptions import MissingFilterException
|
||||
|
||||
@@ -36,8 +37,7 @@ class DCIMFieldChoicesViewSet(FieldChoicesViewSet):
|
||||
fields = (
|
||||
(Device, ['face', 'status']),
|
||||
(ConsolePort, ['connection_status']),
|
||||
(Interface, ['form_factor', 'mode']),
|
||||
(InterfaceConnection, ['connection_status']),
|
||||
(Interface, ['connection_status', 'form_factor', 'mode']),
|
||||
(InterfaceTemplate, ['form_factor']),
|
||||
(PowerPort, ['connection_status']),
|
||||
(Rack, ['type', 'width']),
|
||||
@@ -45,6 +45,40 @@ class DCIMFieldChoicesViewSet(FieldChoicesViewSet):
|
||||
)
|
||||
|
||||
|
||||
# Mixins
|
||||
|
||||
class CableTraceMixin(object):
|
||||
|
||||
@action(detail=True, url_path='trace')
|
||||
def trace(self, request, pk):
|
||||
"""
|
||||
Trace a complete cable path and return each segment as a three-tuple of (termination, cable, termination).
|
||||
"""
|
||||
obj = get_object_or_404(self.queryset.model, pk=pk)
|
||||
|
||||
# Initialize the path array
|
||||
path = []
|
||||
|
||||
for near_end, cable, far_end in obj.trace():
|
||||
|
||||
# Serialize each object
|
||||
serializer_a = get_serializer_for_model(near_end, prefix='Nested')
|
||||
x = serializer_a(near_end, context={'request': request}).data
|
||||
if cable is not None:
|
||||
y = serializers.TracedCableSerializer(cable, context={'request': request}).data
|
||||
else:
|
||||
y = None
|
||||
if far_end is not None:
|
||||
serializer_b = get_serializer_for_model(far_end, prefix='Nested')
|
||||
z = serializer_b(far_end, context={'request': request}).data
|
||||
else:
|
||||
z = None
|
||||
|
||||
path.append((x, y, z))
|
||||
|
||||
return Response(path)
|
||||
|
||||
|
||||
#
|
||||
# Regions
|
||||
#
|
||||
@@ -52,7 +86,7 @@ class DCIMFieldChoicesViewSet(FieldChoicesViewSet):
|
||||
class RegionViewSet(ModelViewSet):
|
||||
queryset = Region.objects.all()
|
||||
serializer_class = serializers.RegionSerializer
|
||||
filter_class = filters.RegionFilter
|
||||
filterset_class = filters.RegionFilter
|
||||
|
||||
|
||||
#
|
||||
@@ -60,9 +94,9 @@ class RegionViewSet(ModelViewSet):
|
||||
#
|
||||
|
||||
class SiteViewSet(CustomFieldModelViewSet):
|
||||
queryset = Site.objects.select_related('region', 'tenant')
|
||||
queryset = Site.objects.select_related('region', 'tenant').prefetch_related('tags')
|
||||
serializer_class = serializers.SiteSerializer
|
||||
filter_class = filters.SiteFilter
|
||||
filterset_class = filters.SiteFilter
|
||||
|
||||
@action(detail=True)
|
||||
def graphs(self, request, pk=None):
|
||||
@@ -82,7 +116,7 @@ class SiteViewSet(CustomFieldModelViewSet):
|
||||
class RackGroupViewSet(ModelViewSet):
|
||||
queryset = RackGroup.objects.select_related('site')
|
||||
serializer_class = serializers.RackGroupSerializer
|
||||
filter_class = filters.RackGroupFilter
|
||||
filterset_class = filters.RackGroupFilter
|
||||
|
||||
|
||||
#
|
||||
@@ -92,7 +126,7 @@ class RackGroupViewSet(ModelViewSet):
|
||||
class RackRoleViewSet(ModelViewSet):
|
||||
queryset = RackRole.objects.all()
|
||||
serializer_class = serializers.RackRoleSerializer
|
||||
filter_class = filters.RackRoleFilter
|
||||
filterset_class = filters.RackRoleFilter
|
||||
|
||||
|
||||
#
|
||||
@@ -100,9 +134,9 @@ class RackRoleViewSet(ModelViewSet):
|
||||
#
|
||||
|
||||
class RackViewSet(CustomFieldModelViewSet):
|
||||
queryset = Rack.objects.select_related('site', 'group__site', 'tenant')
|
||||
queryset = Rack.objects.select_related('site', 'group__site', 'tenant').prefetch_related('tags')
|
||||
serializer_class = serializers.RackSerializer
|
||||
filter_class = filters.RackFilter
|
||||
filterset_class = filters.RackFilter
|
||||
|
||||
@action(detail=True)
|
||||
def units(self, request, pk=None):
|
||||
@@ -132,7 +166,7 @@ class RackViewSet(CustomFieldModelViewSet):
|
||||
class RackReservationViewSet(ModelViewSet):
|
||||
queryset = RackReservation.objects.select_related('rack', 'user', 'tenant')
|
||||
serializer_class = serializers.RackReservationSerializer
|
||||
filter_class = filters.RackReservationFilter
|
||||
filterset_class = filters.RackReservationFilter
|
||||
|
||||
# Assign user from request
|
||||
def perform_create(self, serializer):
|
||||
@@ -146,7 +180,7 @@ class RackReservationViewSet(ModelViewSet):
|
||||
class ManufacturerViewSet(ModelViewSet):
|
||||
queryset = Manufacturer.objects.all()
|
||||
serializer_class = serializers.ManufacturerSerializer
|
||||
filter_class = filters.ManufacturerFilter
|
||||
filterset_class = filters.ManufacturerFilter
|
||||
|
||||
|
||||
#
|
||||
@@ -154,9 +188,9 @@ class ManufacturerViewSet(ModelViewSet):
|
||||
#
|
||||
|
||||
class DeviceTypeViewSet(CustomFieldModelViewSet):
|
||||
queryset = DeviceType.objects.select_related('manufacturer')
|
||||
queryset = DeviceType.objects.select_related('manufacturer').prefetch_related('tags')
|
||||
serializer_class = serializers.DeviceTypeSerializer
|
||||
filter_class = filters.DeviceTypeFilter
|
||||
filterset_class = filters.DeviceTypeFilter
|
||||
|
||||
|
||||
#
|
||||
@@ -166,37 +200,49 @@ class DeviceTypeViewSet(CustomFieldModelViewSet):
|
||||
class ConsolePortTemplateViewSet(ModelViewSet):
|
||||
queryset = ConsolePortTemplate.objects.select_related('device_type__manufacturer')
|
||||
serializer_class = serializers.ConsolePortTemplateSerializer
|
||||
filter_class = filters.ConsolePortTemplateFilter
|
||||
filterset_class = filters.ConsolePortTemplateFilter
|
||||
|
||||
|
||||
class ConsoleServerPortTemplateViewSet(ModelViewSet):
|
||||
queryset = ConsoleServerPortTemplate.objects.select_related('device_type__manufacturer')
|
||||
serializer_class = serializers.ConsoleServerPortTemplateSerializer
|
||||
filter_class = filters.ConsoleServerPortTemplateFilter
|
||||
filterset_class = filters.ConsoleServerPortTemplateFilter
|
||||
|
||||
|
||||
class PowerPortTemplateViewSet(ModelViewSet):
|
||||
queryset = PowerPortTemplate.objects.select_related('device_type__manufacturer')
|
||||
serializer_class = serializers.PowerPortTemplateSerializer
|
||||
filter_class = filters.PowerPortTemplateFilter
|
||||
filterset_class = filters.PowerPortTemplateFilter
|
||||
|
||||
|
||||
class PowerOutletTemplateViewSet(ModelViewSet):
|
||||
queryset = PowerOutletTemplate.objects.select_related('device_type__manufacturer')
|
||||
serializer_class = serializers.PowerOutletTemplateSerializer
|
||||
filter_class = filters.PowerOutletTemplateFilter
|
||||
filterset_class = filters.PowerOutletTemplateFilter
|
||||
|
||||
|
||||
class InterfaceTemplateViewSet(ModelViewSet):
|
||||
queryset = InterfaceTemplate.objects.select_related('device_type__manufacturer')
|
||||
serializer_class = serializers.InterfaceTemplateSerializer
|
||||
filter_class = filters.InterfaceTemplateFilter
|
||||
filterset_class = filters.InterfaceTemplateFilter
|
||||
|
||||
|
||||
class FrontPortTemplateViewSet(ModelViewSet):
|
||||
queryset = FrontPortTemplate.objects.select_related('device_type__manufacturer')
|
||||
serializer_class = serializers.FrontPortTemplateSerializer
|
||||
filterset_class = filters.FrontPortTemplateFilter
|
||||
|
||||
|
||||
class RearPortTemplateViewSet(ModelViewSet):
|
||||
queryset = RearPortTemplate.objects.select_related('device_type__manufacturer')
|
||||
serializer_class = serializers.RearPortTemplateSerializer
|
||||
filterset_class = filters.RearPortTemplateFilter
|
||||
|
||||
|
||||
class DeviceBayTemplateViewSet(ModelViewSet):
|
||||
queryset = DeviceBayTemplate.objects.select_related('device_type__manufacturer')
|
||||
serializer_class = serializers.DeviceBayTemplateSerializer
|
||||
filter_class = filters.DeviceBayTemplateFilter
|
||||
filterset_class = filters.DeviceBayTemplateFilter
|
||||
|
||||
|
||||
#
|
||||
@@ -206,7 +252,7 @@ class DeviceBayTemplateViewSet(ModelViewSet):
|
||||
class DeviceRoleViewSet(ModelViewSet):
|
||||
queryset = DeviceRole.objects.all()
|
||||
serializer_class = serializers.DeviceRoleSerializer
|
||||
filter_class = filters.DeviceRoleFilter
|
||||
filterset_class = filters.DeviceRoleFilter
|
||||
|
||||
|
||||
#
|
||||
@@ -216,7 +262,7 @@ class DeviceRoleViewSet(ModelViewSet):
|
||||
class PlatformViewSet(ModelViewSet):
|
||||
queryset = Platform.objects.all()
|
||||
serializer_class = serializers.PlatformSerializer
|
||||
filter_class = filters.PlatformFilter
|
||||
filterset_class = filters.PlatformFilter
|
||||
|
||||
|
||||
#
|
||||
@@ -228,9 +274,9 @@ class DeviceViewSet(CustomFieldModelViewSet):
|
||||
'device_type__manufacturer', 'device_role', 'tenant', 'platform', 'site', 'rack', 'parent_bay',
|
||||
'virtual_chassis__master',
|
||||
).prefetch_related(
|
||||
'primary_ip4__nat_outside', 'primary_ip6__nat_outside',
|
||||
'primary_ip4__nat_outside', 'primary_ip6__nat_outside', 'tags',
|
||||
)
|
||||
filter_class = filters.DeviceFilter
|
||||
filterset_class = filters.DeviceFilter
|
||||
|
||||
def get_serializer_class(self):
|
||||
"""
|
||||
@@ -238,6 +284,11 @@ class DeviceViewSet(CustomFieldModelViewSet):
|
||||
"""
|
||||
if self.action == 'retrieve':
|
||||
return serializers.DeviceWithConfigContextSerializer
|
||||
|
||||
request = self.get_serializer_context()['request']
|
||||
if request.query_params.get('brief', False):
|
||||
return serializers.NestedDeviceSerializer
|
||||
|
||||
return serializers.DeviceSerializer
|
||||
|
||||
@action(detail=True, url_path='napalm')
|
||||
@@ -258,9 +309,9 @@ class DeviceViewSet(CustomFieldModelViewSet):
|
||||
# Check that NAPALM is installed
|
||||
try:
|
||||
import napalm
|
||||
from napalm.base.exceptions import ModuleImportError
|
||||
except ImportError:
|
||||
raise ServiceUnavailable("NAPALM is not installed. Please see the documentation for instructions.")
|
||||
from napalm.base.exceptions import ModuleImportError
|
||||
|
||||
# Validate the configured driver
|
||||
try:
|
||||
@@ -304,7 +355,9 @@ class DeviceViewSet(CustomFieldModelViewSet):
|
||||
try:
|
||||
response[method] = getattr(d, method)()
|
||||
except NotImplementedError:
|
||||
response[method] = {'error': 'Method not implemented for NAPALM driver {}'.format(driver)}
|
||||
response[method] = {'error': 'Method {} not implemented for NAPALM driver {}'.format(method, driver)}
|
||||
except Exception as e:
|
||||
response[method] = {'error': 'Method {} failed: {}'.format(method, e)}
|
||||
d.close()
|
||||
|
||||
return Response(response)
|
||||
@@ -314,34 +367,54 @@ class DeviceViewSet(CustomFieldModelViewSet):
|
||||
# Device components
|
||||
#
|
||||
|
||||
class ConsolePortViewSet(ModelViewSet):
|
||||
queryset = ConsolePort.objects.select_related('device', 'cs_port__device')
|
||||
class ConsolePortViewSet(CableTraceMixin, ModelViewSet):
|
||||
queryset = ConsolePort.objects.select_related(
|
||||
'device', 'connected_endpoint__device', 'cable'
|
||||
).prefetch_related(
|
||||
'tags'
|
||||
)
|
||||
serializer_class = serializers.ConsolePortSerializer
|
||||
filter_class = filters.ConsolePortFilter
|
||||
filterset_class = filters.ConsolePortFilter
|
||||
|
||||
|
||||
class ConsoleServerPortViewSet(ModelViewSet):
|
||||
queryset = ConsoleServerPort.objects.select_related('device', 'connected_console__device')
|
||||
class ConsoleServerPortViewSet(CableTraceMixin, ModelViewSet):
|
||||
queryset = ConsoleServerPort.objects.select_related(
|
||||
'device', 'connected_endpoint__device', 'cable'
|
||||
).prefetch_related(
|
||||
'tags'
|
||||
)
|
||||
serializer_class = serializers.ConsoleServerPortSerializer
|
||||
filter_class = filters.ConsoleServerPortFilter
|
||||
filterset_class = filters.ConsoleServerPortFilter
|
||||
|
||||
|
||||
class PowerPortViewSet(ModelViewSet):
|
||||
queryset = PowerPort.objects.select_related('device', 'power_outlet__device')
|
||||
class PowerPortViewSet(CableTraceMixin, ModelViewSet):
|
||||
queryset = PowerPort.objects.select_related(
|
||||
'device', 'connected_endpoint__device', 'cable'
|
||||
).prefetch_related(
|
||||
'tags'
|
||||
)
|
||||
serializer_class = serializers.PowerPortSerializer
|
||||
filter_class = filters.PowerPortFilter
|
||||
filterset_class = filters.PowerPortFilter
|
||||
|
||||
|
||||
class PowerOutletViewSet(ModelViewSet):
|
||||
queryset = PowerOutlet.objects.select_related('device', 'connected_port__device')
|
||||
class PowerOutletViewSet(CableTraceMixin, ModelViewSet):
|
||||
queryset = PowerOutlet.objects.select_related(
|
||||
'device', 'connected_endpoint__device', 'cable'
|
||||
).prefetch_related(
|
||||
'tags'
|
||||
)
|
||||
serializer_class = serializers.PowerOutletSerializer
|
||||
filter_class = filters.PowerOutletFilter
|
||||
filterset_class = filters.PowerOutletFilter
|
||||
|
||||
|
||||
class InterfaceViewSet(ModelViewSet):
|
||||
queryset = Interface.objects.select_related('device')
|
||||
class InterfaceViewSet(CableTraceMixin, ModelViewSet):
|
||||
queryset = Interface.objects.select_related(
|
||||
'device', '_connected_interface', '_connected_circuittermination', 'cable'
|
||||
).prefetch_related(
|
||||
'ip_addresses', 'tags'
|
||||
)
|
||||
serializer_class = serializers.InterfaceSerializer
|
||||
filter_class = filters.InterfaceFilter
|
||||
filterset_class = filters.InterfaceFilter
|
||||
|
||||
@action(detail=True)
|
||||
def graphs(self, request, pk=None):
|
||||
@@ -354,16 +427,36 @@ class InterfaceViewSet(ModelViewSet):
|
||||
return Response(serializer.data)
|
||||
|
||||
|
||||
class FrontPortViewSet(ModelViewSet):
|
||||
queryset = FrontPort.objects.select_related(
|
||||
'device__device_type__manufacturer', 'rear_port', 'cable'
|
||||
).prefetch_related(
|
||||
'tags'
|
||||
)
|
||||
serializer_class = serializers.FrontPortSerializer
|
||||
filterset_class = filters.FrontPortFilter
|
||||
|
||||
|
||||
class RearPortViewSet(ModelViewSet):
|
||||
queryset = RearPort.objects.select_related(
|
||||
'device__device_type__manufacturer', 'cable'
|
||||
).prefetch_related(
|
||||
'tags'
|
||||
)
|
||||
serializer_class = serializers.RearPortSerializer
|
||||
filterset_class = filters.RearPortFilter
|
||||
|
||||
|
||||
class DeviceBayViewSet(ModelViewSet):
|
||||
queryset = DeviceBay.objects.select_related('installed_device')
|
||||
queryset = DeviceBay.objects.select_related('installed_device').prefetch_related('tags')
|
||||
serializer_class = serializers.DeviceBaySerializer
|
||||
filter_class = filters.DeviceBayFilter
|
||||
filterset_class = filters.DeviceBayFilter
|
||||
|
||||
|
||||
class InventoryItemViewSet(ModelViewSet):
|
||||
queryset = InventoryItem.objects.select_related('device', 'manufacturer')
|
||||
queryset = InventoryItem.objects.select_related('device', 'manufacturer').prefetch_related('tags')
|
||||
serializer_class = serializers.InventoryItemSerializer
|
||||
filter_class = filters.InventoryItemFilter
|
||||
filterset_class = filters.InventoryItemFilter
|
||||
|
||||
|
||||
#
|
||||
@@ -371,21 +464,47 @@ class InventoryItemViewSet(ModelViewSet):
|
||||
#
|
||||
|
||||
class ConsoleConnectionViewSet(ListModelMixin, GenericViewSet):
|
||||
queryset = ConsolePort.objects.select_related('device', 'cs_port__device').filter(cs_port__isnull=False)
|
||||
queryset = ConsolePort.objects.select_related(
|
||||
'device', 'connected_endpoint__device'
|
||||
).filter(
|
||||
connected_endpoint__isnull=False
|
||||
)
|
||||
serializer_class = serializers.ConsolePortSerializer
|
||||
filter_class = filters.ConsoleConnectionFilter
|
||||
filterset_class = filters.ConsoleConnectionFilter
|
||||
|
||||
|
||||
class PowerConnectionViewSet(ListModelMixin, GenericViewSet):
|
||||
queryset = PowerPort.objects.select_related('device', 'power_outlet__device').filter(power_outlet__isnull=False)
|
||||
queryset = PowerPort.objects.select_related(
|
||||
'device', 'connected_endpoint__device'
|
||||
).filter(
|
||||
connected_endpoint__isnull=False
|
||||
)
|
||||
serializer_class = serializers.PowerPortSerializer
|
||||
filter_class = filters.PowerConnectionFilter
|
||||
filterset_class = filters.PowerConnectionFilter
|
||||
|
||||
|
||||
class InterfaceConnectionViewSet(ModelViewSet):
|
||||
queryset = InterfaceConnection.objects.select_related('interface_a__device', 'interface_b__device')
|
||||
queryset = Interface.objects.select_related(
|
||||
'device', '_connected_interface', '_connected_circuittermination'
|
||||
).filter(
|
||||
# Avoid duplicate connections by only selecting the lower PK in a connected pair
|
||||
Q(_connected_interface__isnull=False, pk__lt=F('_connected_interface')) |
|
||||
Q(_connected_circuittermination__isnull=False)
|
||||
)
|
||||
serializer_class = serializers.InterfaceConnectionSerializer
|
||||
filter_class = filters.InterfaceConnectionFilter
|
||||
filterset_class = filters.InterfaceConnectionFilter
|
||||
|
||||
|
||||
#
|
||||
# Cables
|
||||
#
|
||||
|
||||
class CableViewSet(ModelViewSet):
|
||||
queryset = Cable.objects.prefetch_related(
|
||||
'termination_a', 'termination_b'
|
||||
)
|
||||
serializer_class = serializers.CableSerializer
|
||||
filterset_class = filters.CableFilter
|
||||
|
||||
|
||||
#
|
||||
@@ -393,7 +512,7 @@ class InterfaceConnectionViewSet(ModelViewSet):
|
||||
#
|
||||
|
||||
class VirtualChassisViewSet(ModelViewSet):
|
||||
queryset = VirtualChassis.objects.all()
|
||||
queryset = VirtualChassis.objects.prefetch_related('tags')
|
||||
serializer_class = serializers.VirtualChassisSerializer
|
||||
|
||||
|
||||
@@ -407,30 +526,43 @@ class ConnectedDeviceViewSet(ViewSet):
|
||||
interface. This is useful in a situation where a device boots with no configuration, but can detect its neighbors
|
||||
via a protocol such as LLDP. Two query parameters must be included in the request:
|
||||
|
||||
* `peer-device`: The name of the peer device
|
||||
* `peer-interface`: The name of the peer interface
|
||||
* `peer_device`: The name of the peer device
|
||||
* `peer_interface`: The name of the peer interface
|
||||
"""
|
||||
permission_classes = [IsAuthenticatedOrLoginNotRequired]
|
||||
_device_param = Parameter('peer-device', 'query',
|
||||
description='The name of the peer device', required=True, type=openapi.TYPE_STRING)
|
||||
_interface_param = Parameter('peer-interface', 'query',
|
||||
description='The name of the peer interface', required=True, type=openapi.TYPE_STRING)
|
||||
_device_param = Parameter(
|
||||
name='peer_device',
|
||||
in_='query',
|
||||
description='The name of the peer device',
|
||||
required=True,
|
||||
type=openapi.TYPE_STRING
|
||||
)
|
||||
_interface_param = Parameter(
|
||||
name='peer_interface',
|
||||
in_='query',
|
||||
description='The name of the peer interface',
|
||||
required=True,
|
||||
type=openapi.TYPE_STRING
|
||||
)
|
||||
|
||||
def get_view_name(self):
|
||||
return "Connected Device Locator"
|
||||
|
||||
@swagger_auto_schema(
|
||||
manual_parameters=[_device_param, _interface_param], responses={'200': serializers.DeviceSerializer})
|
||||
manual_parameters=[_device_param, _interface_param],
|
||||
responses={'200': serializers.DeviceSerializer}
|
||||
)
|
||||
def list(self, request):
|
||||
|
||||
peer_device_name = request.query_params.get(self._device_param.name)
|
||||
peer_interface_name = request.query_params.get(self._interface_param.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
|
||||
peer_interface = get_object_or_404(Interface, device__name=peer_device_name, name=peer_interface_name)
|
||||
local_interface = peer_interface.connected_interface
|
||||
local_interface = peer_interface._connected_interface
|
||||
|
||||
if local_interface is None:
|
||||
return Response()
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
# Rack types
|
||||
RACK_TYPE_2POST = 100
|
||||
@@ -31,6 +29,20 @@ RACK_FACE_CHOICES = [
|
||||
[RACK_FACE_REAR, 'Rear'],
|
||||
]
|
||||
|
||||
# Rack statuses
|
||||
RACK_STATUS_RESERVED = 0
|
||||
RACK_STATUS_AVAILABLE = 1
|
||||
RACK_STATUS_PLANNED = 2
|
||||
RACK_STATUS_ACTIVE = 3
|
||||
RACK_STATUS_DEPRECATED = 4
|
||||
RACK_STATUS_CHOICES = [
|
||||
[RACK_STATUS_ACTIVE, 'Active'],
|
||||
[RACK_STATUS_PLANNED, 'Planned'],
|
||||
[RACK_STATUS_RESERVED, 'Reserved'],
|
||||
[RACK_STATUS_AVAILABLE, 'Available'],
|
||||
[RACK_STATUS_DEPRECATED, 'Deprecated'],
|
||||
]
|
||||
|
||||
# Parent/child device roles
|
||||
SUBDEVICE_ROLE_PARENT = True
|
||||
SUBDEVICE_ROLE_CHILD = False
|
||||
@@ -93,6 +105,11 @@ IFACE_FF_STACKWISE_PLUS = 5050
|
||||
IFACE_FF_FLEXSTACK = 5100
|
||||
IFACE_FF_FLEXSTACK_PLUS = 5150
|
||||
IFACE_FF_JUNIPER_VCP = 5200
|
||||
IFACE_FF_SUMMITSTACK = 5300
|
||||
IFACE_FF_SUMMITSTACK128 = 5310
|
||||
IFACE_FF_SUMMITSTACK256 = 5320
|
||||
IFACE_FF_SUMMITSTACK512 = 5330
|
||||
|
||||
# Other
|
||||
IFACE_FF_OTHER = 32767
|
||||
|
||||
@@ -168,6 +185,10 @@ IFACE_FF_CHOICES = [
|
||||
[IFACE_FF_FLEXSTACK, 'Cisco FlexStack'],
|
||||
[IFACE_FF_FLEXSTACK_PLUS, 'Cisco FlexStack Plus'],
|
||||
[IFACE_FF_JUNIPER_VCP, 'Juniper VCP'],
|
||||
[IFACE_FF_SUMMITSTACK, 'Extreme SummitStack'],
|
||||
[IFACE_FF_SUMMITSTACK128, 'Extreme SummitStack-128'],
|
||||
[IFACE_FF_SUMMITSTACK256, 'Extreme SummitStack-256'],
|
||||
[IFACE_FF_SUMMITSTACK512, 'Extreme SummitStack-512'],
|
||||
]
|
||||
],
|
||||
[
|
||||
@@ -202,6 +223,36 @@ IFACE_MODE_CHOICES = [
|
||||
[IFACE_MODE_TAGGED_ALL, 'Tagged All'],
|
||||
]
|
||||
|
||||
# Pass-through port types
|
||||
PORT_TYPE_8P8C = 1000
|
||||
PORT_TYPE_ST = 2000
|
||||
PORT_TYPE_SC = 2100
|
||||
PORT_TYPE_FC = 2200
|
||||
PORT_TYPE_LC = 2300
|
||||
PORT_TYPE_MTRJ = 2400
|
||||
PORT_TYPE_MPO = 2500
|
||||
PORT_TYPE_LSH = 2600
|
||||
PORT_TYPE_CHOICES = [
|
||||
[
|
||||
'Copper',
|
||||
[
|
||||
[PORT_TYPE_8P8C, '8P8C'],
|
||||
],
|
||||
],
|
||||
[
|
||||
'Fiber Optic',
|
||||
[
|
||||
[PORT_TYPE_FC, 'FC'],
|
||||
[PORT_TYPE_LC, 'LC'],
|
||||
[PORT_TYPE_LSH, 'LSH'],
|
||||
[PORT_TYPE_MPO, 'MPO'],
|
||||
[PORT_TYPE_MTRJ, 'MTRJ'],
|
||||
[PORT_TYPE_SC, 'SC'],
|
||||
[PORT_TYPE_ST, 'ST'],
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
# Device statuses
|
||||
DEVICE_STATUS_OFFLINE = 0
|
||||
DEVICE_STATUS_ACTIVE = 1
|
||||
@@ -228,7 +279,7 @@ SITE_STATUS_CHOICES = [
|
||||
[SITE_STATUS_RETIRED, 'Retired'],
|
||||
]
|
||||
|
||||
# Bootstrap CSS classes for device statuses
|
||||
# Bootstrap CSS classes for device/rack statuses
|
||||
STATUS_CLASSES = {
|
||||
0: 'warning',
|
||||
1: 'success',
|
||||
@@ -246,12 +297,81 @@ CONNECTION_STATUS_CHOICES = [
|
||||
[CONNECTION_STATUS_CONNECTED, 'Connected'],
|
||||
]
|
||||
|
||||
# Platform -> RPC client mappings
|
||||
RPC_CLIENT_JUNIPER_JUNOS = 'juniper-junos'
|
||||
RPC_CLIENT_CISCO_IOS = 'cisco-ios'
|
||||
RPC_CLIENT_OPENGEAR = 'opengear'
|
||||
RPC_CLIENT_CHOICES = [
|
||||
[RPC_CLIENT_JUNIPER_JUNOS, 'Juniper Junos (NETCONF)'],
|
||||
[RPC_CLIENT_CISCO_IOS, 'Cisco IOS (SSH)'],
|
||||
[RPC_CLIENT_OPENGEAR, 'Opengear (SSH)'],
|
||||
# Cable endpoint types
|
||||
CABLE_TERMINATION_TYPES = [
|
||||
'consoleport', 'consoleserverport', 'interface', 'poweroutlet', 'powerport', 'frontport', 'rearport',
|
||||
]
|
||||
|
||||
# Cable types
|
||||
CABLE_TYPE_CAT3 = 1300
|
||||
CABLE_TYPE_CAT5 = 1500
|
||||
CABLE_TYPE_CAT5E = 1510
|
||||
CABLE_TYPE_CAT6 = 1600
|
||||
CABLE_TYPE_CAT6A = 1610
|
||||
CABLE_TYPE_CAT7 = 1700
|
||||
CABLE_TYPE_MMF_OM1 = 3010
|
||||
CABLE_TYPE_MMF_OM2 = 3020
|
||||
CABLE_TYPE_MMF_OM3 = 3030
|
||||
CABLE_TYPE_MMF_OM4 = 3040
|
||||
CABLE_TYPE_SMF = 3500
|
||||
CABLE_TYPE_POWER = 5000
|
||||
CABLE_TYPE_CHOICES = (
|
||||
(
|
||||
'Copper', (
|
||||
(CABLE_TYPE_CAT3, 'CAT3'),
|
||||
(CABLE_TYPE_CAT5, 'CAT5'),
|
||||
(CABLE_TYPE_CAT5E, 'CAT5e'),
|
||||
(CABLE_TYPE_CAT6, 'CAT6'),
|
||||
(CABLE_TYPE_CAT6A, 'CAT6a'),
|
||||
(CABLE_TYPE_CAT7, 'CAT7'),
|
||||
),
|
||||
),
|
||||
(
|
||||
'Fiber', (
|
||||
(CABLE_TYPE_MMF_OM1, 'Multimode Fiber (OM1)'),
|
||||
(CABLE_TYPE_MMF_OM2, 'Multimode Fiber (OM2)'),
|
||||
(CABLE_TYPE_MMF_OM3, 'Multimode Fiber (OM3)'),
|
||||
(CABLE_TYPE_MMF_OM4, 'Multimode Fiber (OM4)'),
|
||||
(CABLE_TYPE_SMF, 'Singlemode Fiber'),
|
||||
),
|
||||
),
|
||||
(CABLE_TYPE_POWER, 'Power'),
|
||||
)
|
||||
|
||||
CABLE_TERMINATION_TYPE_CHOICES = {
|
||||
# (API endpoint, human-friendly name)
|
||||
'consoleport': ('console-ports', 'Console port'),
|
||||
'consoleserverport': ('console-server-ports', 'Console server port'),
|
||||
'powerport': ('power-ports', 'Power port'),
|
||||
'poweroutlet': ('power-outlets', 'Power outlet'),
|
||||
'interface': ('interfaces', 'Interface'),
|
||||
'frontport': ('front-ports', 'Front panel port'),
|
||||
'rearport': ('rear-ports', 'Rear panel port'),
|
||||
}
|
||||
|
||||
COMPATIBLE_TERMINATION_TYPES = {
|
||||
'consoleport': ['consoleserverport', 'frontport', 'rearport'],
|
||||
'consoleserverport': ['consoleport', 'frontport', 'rearport'],
|
||||
'powerport': ['poweroutlet'],
|
||||
'poweroutlet': ['powerport'],
|
||||
'interface': ['interface', 'circuittermination', 'frontport', 'rearport'],
|
||||
'frontport': ['consoleport', 'consoleserverport', 'interface', 'frontport', 'rearport', 'circuittermination'],
|
||||
'rearport': ['consoleport', 'consoleserverport', 'interface', 'frontport', 'rearport', 'circuittermination'],
|
||||
'circuittermination': ['interface', 'frontport', 'rearport'],
|
||||
}
|
||||
|
||||
LENGTH_UNIT_METER = 'm'
|
||||
LENGTH_UNIT_CENTIMETER = 'cm'
|
||||
LENGTH_UNIT_MILLIMETER = 'mm'
|
||||
LENGTH_UNIT_FOOT = 'ft'
|
||||
LENGTH_UNIT_INCH = 'in'
|
||||
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,12 +1,7 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from netaddr import EUI, mac_unix_expanded
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.validators import MinValueValidator, MaxValueValidator
|
||||
from django.db import models
|
||||
|
||||
from .formfields import MACAddressFormField
|
||||
from netaddr import AddrFormatError, EUI, mac_unix_expanded
|
||||
|
||||
|
||||
class ASNField(models.BigIntegerField):
|
||||
@@ -35,7 +30,7 @@ class MACAddressField(models.Field):
|
||||
return value
|
||||
try:
|
||||
return EUI(value, version=48, dialect=mac_unix_expanded_uppercase)
|
||||
except ValueError as e:
|
||||
except AddrFormatError as e:
|
||||
raise ValidationError(e)
|
||||
|
||||
def db_type(self, connection):
|
||||
@@ -45,11 +40,3 @@ class MACAddressField(models.Field):
|
||||
if not value:
|
||||
return None
|
||||
return str(self.to_python(value))
|
||||
|
||||
def form_class(self):
|
||||
return MACAddressFormField
|
||||
|
||||
def formfield(self, **kwargs):
|
||||
defaults = {'form_class': self.form_class()}
|
||||
defaults.update(kwargs)
|
||||
return super(MACAddressField, self).formfield(**defaults)
|
||||
|
||||
@@ -1,24 +1,20 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import django_filters
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.db.models import Q
|
||||
from netaddr import EUI
|
||||
from netaddr.core import AddrFormatError
|
||||
|
||||
from extras.filters import CustomFieldFilterSet
|
||||
from tenancy.models import Tenant
|
||||
from utilities.filters import NullableCharFieldFilter, NumericInFilter
|
||||
from utilities.filters import NullableCharFieldFilter, NumericInFilter, TagFilter
|
||||
from virtualization.models import Cluster
|
||||
from .constants import (
|
||||
DEVICE_STATUS_CHOICES, IFACE_FF_LAG, NONCONNECTABLE_IFACE_TYPES, SITE_STATUS_CHOICES, VIRTUAL_IFACE_TYPES,
|
||||
WIRELESS_IFACE_TYPES,
|
||||
)
|
||||
from .constants import *
|
||||
from .models import (
|
||||
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
||||
DeviceBayTemplate, DeviceRole, DeviceType, Interface, InterfaceConnection, InterfaceTemplate, Manufacturer,
|
||||
InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup,
|
||||
RackReservation, RackRole, Region, Site, VirtualChassis,
|
||||
Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
||||
DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate,
|
||||
InventoryItem, Manufacturer, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack,
|
||||
RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site, VirtualChassis,
|
||||
)
|
||||
|
||||
|
||||
@@ -32,7 +28,7 @@ class RegionFilter(django_filters.FilterSet):
|
||||
label='Parent region (ID)',
|
||||
)
|
||||
parent = django_filters.ModelMultipleChoiceFilter(
|
||||
name='parent__slug',
|
||||
field_name='parent__slug',
|
||||
queryset=Region.objects.all(),
|
||||
to_field_name='slug',
|
||||
label='Parent region (slug)',
|
||||
@@ -53,7 +49,10 @@ class RegionFilter(django_filters.FilterSet):
|
||||
|
||||
|
||||
class SiteFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
||||
id__in = NumericInFilter(name='id', lookup_expr='in')
|
||||
id__in = NumericInFilter(
|
||||
field_name='id',
|
||||
lookup_expr='in'
|
||||
)
|
||||
q = django_filters.CharFilter(
|
||||
method='search',
|
||||
label='Search',
|
||||
@@ -67,7 +66,7 @@ class SiteFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
||||
label='Region (ID)',
|
||||
)
|
||||
region = django_filters.ModelMultipleChoiceFilter(
|
||||
name='region__slug',
|
||||
field_name='region__slug',
|
||||
queryset=Region.objects.all(),
|
||||
to_field_name='slug',
|
||||
label='Region (slug)',
|
||||
@@ -77,14 +76,12 @@ class SiteFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
||||
label='Tenant (ID)',
|
||||
)
|
||||
tenant = django_filters.ModelMultipleChoiceFilter(
|
||||
name='tenant__slug',
|
||||
field_name='tenant__slug',
|
||||
queryset=Tenant.objects.all(),
|
||||
to_field_name='slug',
|
||||
label='Tenant (slug)',
|
||||
)
|
||||
tag = django_filters.CharFilter(
|
||||
name='tags__slug',
|
||||
)
|
||||
tag = TagFilter()
|
||||
|
||||
class Meta:
|
||||
model = Site
|
||||
@@ -112,12 +109,16 @@ class SiteFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
||||
|
||||
|
||||
class RackGroupFilter(django_filters.FilterSet):
|
||||
q = django_filters.CharFilter(
|
||||
method='search',
|
||||
label='Search',
|
||||
)
|
||||
site_id = django_filters.ModelMultipleChoiceFilter(
|
||||
queryset=Site.objects.all(),
|
||||
label='Site (ID)',
|
||||
)
|
||||
site = django_filters.ModelMultipleChoiceFilter(
|
||||
name='site__slug',
|
||||
field_name='site__slug',
|
||||
queryset=Site.objects.all(),
|
||||
to_field_name='slug',
|
||||
label='Site (slug)',
|
||||
@@ -127,6 +128,15 @@ class RackGroupFilter(django_filters.FilterSet):
|
||||
model = RackGroup
|
||||
fields = ['site_id', 'name', 'slug']
|
||||
|
||||
def search(self, queryset, name, value):
|
||||
if not value.strip():
|
||||
return queryset
|
||||
qs_filter = (
|
||||
Q(name__icontains=value) |
|
||||
Q(slug__icontains=value)
|
||||
)
|
||||
return queryset.filter(qs_filter)
|
||||
|
||||
|
||||
class RackRoleFilter(django_filters.FilterSet):
|
||||
|
||||
@@ -136,7 +146,10 @@ class RackRoleFilter(django_filters.FilterSet):
|
||||
|
||||
|
||||
class RackFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
||||
id__in = NumericInFilter(name='id', lookup_expr='in')
|
||||
id__in = NumericInFilter(
|
||||
field_name='id',
|
||||
lookup_expr='in'
|
||||
)
|
||||
q = django_filters.CharFilter(
|
||||
method='search',
|
||||
label='Search',
|
||||
@@ -147,7 +160,7 @@ class RackFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
||||
label='Site (ID)',
|
||||
)
|
||||
site = django_filters.ModelMultipleChoiceFilter(
|
||||
name='site__slug',
|
||||
field_name='site__slug',
|
||||
queryset=Site.objects.all(),
|
||||
to_field_name='slug',
|
||||
label='Site (slug)',
|
||||
@@ -157,7 +170,7 @@ class RackFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
||||
label='Group (ID)',
|
||||
)
|
||||
group = django_filters.ModelMultipleChoiceFilter(
|
||||
name='group__slug',
|
||||
field_name='group__slug',
|
||||
queryset=RackGroup.objects.all(),
|
||||
to_field_name='slug',
|
||||
label='Group',
|
||||
@@ -167,28 +180,34 @@ class RackFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
||||
label='Tenant (ID)',
|
||||
)
|
||||
tenant = django_filters.ModelMultipleChoiceFilter(
|
||||
name='tenant__slug',
|
||||
field_name='tenant__slug',
|
||||
queryset=Tenant.objects.all(),
|
||||
to_field_name='slug',
|
||||
label='Tenant (slug)',
|
||||
)
|
||||
status = django_filters.MultipleChoiceFilter(
|
||||
choices=RACK_STATUS_CHOICES,
|
||||
null_value=None
|
||||
)
|
||||
role_id = django_filters.ModelMultipleChoiceFilter(
|
||||
queryset=RackRole.objects.all(),
|
||||
label='Role (ID)',
|
||||
)
|
||||
role = django_filters.ModelMultipleChoiceFilter(
|
||||
name='role__slug',
|
||||
field_name='role__slug',
|
||||
queryset=RackRole.objects.all(),
|
||||
to_field_name='slug',
|
||||
label='Role (slug)',
|
||||
)
|
||||
tag = django_filters.CharFilter(
|
||||
name='tags__slug',
|
||||
)
|
||||
asset_tag = NullableCharFieldFilter()
|
||||
tag = TagFilter()
|
||||
|
||||
class Meta:
|
||||
model = Rack
|
||||
fields = ['name', 'serial', 'type', 'width', 'u_height', 'desc_units']
|
||||
fields = [
|
||||
'name', 'serial', 'asset_tag', 'type', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth',
|
||||
'outer_unit',
|
||||
]
|
||||
|
||||
def search(self, queryset, name, value):
|
||||
if not value.strip():
|
||||
@@ -197,12 +216,16 @@ class RackFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
||||
Q(name__icontains=value) |
|
||||
Q(facility_id__icontains=value) |
|
||||
Q(serial__icontains=value.strip()) |
|
||||
Q(asset_tag__icontains=value.strip()) |
|
||||
Q(comments__icontains=value)
|
||||
)
|
||||
|
||||
|
||||
class RackReservationFilter(django_filters.FilterSet):
|
||||
id__in = NumericInFilter(name='id', lookup_expr='in')
|
||||
id__in = NumericInFilter(
|
||||
field_name='id',
|
||||
lookup_expr='in'
|
||||
)
|
||||
q = django_filters.CharFilter(
|
||||
method='search',
|
||||
label='Search',
|
||||
@@ -212,23 +235,23 @@ class RackReservationFilter(django_filters.FilterSet):
|
||||
label='Rack (ID)',
|
||||
)
|
||||
site_id = django_filters.ModelMultipleChoiceFilter(
|
||||
name='rack__site',
|
||||
field_name='rack__site',
|
||||
queryset=Site.objects.all(),
|
||||
label='Site (ID)',
|
||||
)
|
||||
site = django_filters.ModelMultipleChoiceFilter(
|
||||
name='rack__site__slug',
|
||||
field_name='rack__site__slug',
|
||||
queryset=Site.objects.all(),
|
||||
to_field_name='slug',
|
||||
label='Site (slug)',
|
||||
)
|
||||
group_id = django_filters.ModelMultipleChoiceFilter(
|
||||
name='rack__group',
|
||||
field_name='rack__group',
|
||||
queryset=RackGroup.objects.all(),
|
||||
label='Group (ID)',
|
||||
)
|
||||
group = django_filters.ModelMultipleChoiceFilter(
|
||||
name='rack__group__slug',
|
||||
field_name='rack__group__slug',
|
||||
queryset=RackGroup.objects.all(),
|
||||
to_field_name='slug',
|
||||
label='Group',
|
||||
@@ -238,7 +261,7 @@ class RackReservationFilter(django_filters.FilterSet):
|
||||
label='Tenant (ID)',
|
||||
)
|
||||
tenant = django_filters.ModelMultipleChoiceFilter(
|
||||
name='tenant__slug',
|
||||
field_name='tenant__slug',
|
||||
queryset=Tenant.objects.all(),
|
||||
to_field_name='slug',
|
||||
label='Tenant (slug)',
|
||||
@@ -248,7 +271,7 @@ class RackReservationFilter(django_filters.FilterSet):
|
||||
label='User (ID)',
|
||||
)
|
||||
user = django_filters.ModelMultipleChoiceFilter(
|
||||
name='user',
|
||||
field_name='user',
|
||||
queryset=User.objects.all(),
|
||||
to_field_name='username',
|
||||
label='User (name)',
|
||||
@@ -276,8 +299,11 @@ class ManufacturerFilter(django_filters.FilterSet):
|
||||
fields = ['name', 'slug']
|
||||
|
||||
|
||||
class DeviceTypeFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
||||
id__in = NumericInFilter(name='id', lookup_expr='in')
|
||||
class DeviceTypeFilter(CustomFieldFilterSet):
|
||||
id__in = NumericInFilter(
|
||||
field_name='id',
|
||||
lookup_expr='in'
|
||||
)
|
||||
q = django_filters.CharFilter(
|
||||
method='search',
|
||||
label='Search',
|
||||
@@ -287,20 +313,41 @@ class DeviceTypeFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
||||
label='Manufacturer (ID)',
|
||||
)
|
||||
manufacturer = django_filters.ModelMultipleChoiceFilter(
|
||||
name='manufacturer__slug',
|
||||
field_name='manufacturer__slug',
|
||||
queryset=Manufacturer.objects.all(),
|
||||
to_field_name='slug',
|
||||
label='Manufacturer (slug)',
|
||||
)
|
||||
tag = django_filters.CharFilter(
|
||||
name='tags__slug',
|
||||
console_ports = django_filters.BooleanFilter(
|
||||
method='_console_ports',
|
||||
label='Has console ports',
|
||||
)
|
||||
console_server_ports = django_filters.BooleanFilter(
|
||||
method='_console_server_ports',
|
||||
label='Has console server ports',
|
||||
)
|
||||
power_ports = django_filters.BooleanFilter(
|
||||
method='_power_ports',
|
||||
label='Has power ports',
|
||||
)
|
||||
power_outlets = django_filters.BooleanFilter(
|
||||
method='_power_outlets',
|
||||
label='Has power outlets',
|
||||
)
|
||||
interfaces = django_filters.BooleanFilter(
|
||||
method='_interfaces',
|
||||
label='Has interfaces',
|
||||
)
|
||||
pass_through_ports = django_filters.BooleanFilter(
|
||||
method='_pass_through_ports',
|
||||
label='Has pass-through ports',
|
||||
)
|
||||
tag = TagFilter()
|
||||
|
||||
class Meta:
|
||||
model = DeviceType
|
||||
fields = [
|
||||
'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'is_console_server', 'is_pdu',
|
||||
'is_network_device', 'subdevice_role',
|
||||
'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role',
|
||||
]
|
||||
|
||||
def search(self, queryset, name, value):
|
||||
@@ -313,11 +360,32 @@ class DeviceTypeFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
||||
Q(comments__icontains=value)
|
||||
)
|
||||
|
||||
def _console_ports(self, queryset, name, value):
|
||||
return queryset.exclude(consoleport_templates__isnull=value)
|
||||
|
||||
def _console_server_ports(self, queryset, name, value):
|
||||
return queryset.exclude(consoleserverport_templates__isnull=value)
|
||||
|
||||
def _power_ports(self, queryset, name, value):
|
||||
return queryset.exclude(powerport_templates__isnull=value)
|
||||
|
||||
def _power_outlets(self, queryset, name, value):
|
||||
return queryset.exclude(poweroutlet_templates__isnull=value)
|
||||
|
||||
def _interfaces(self, queryset, name, value):
|
||||
return queryset.exclude(interface_templates__isnull=value)
|
||||
|
||||
def _pass_through_ports(self, queryset, name, value):
|
||||
return queryset.exclude(
|
||||
frontport_templates__isnull=value,
|
||||
rearport_templates__isnull=value
|
||||
)
|
||||
|
||||
|
||||
class DeviceTypeComponentFilterSet(django_filters.FilterSet):
|
||||
devicetype_id = django_filters.ModelMultipleChoiceFilter(
|
||||
queryset=DeviceType.objects.all(),
|
||||
name='device_type_id',
|
||||
field_name='device_type_id',
|
||||
label='Device type (ID)',
|
||||
)
|
||||
|
||||
@@ -357,6 +425,20 @@ class InterfaceTemplateFilter(DeviceTypeComponentFilterSet):
|
||||
fields = ['name', 'form_factor', 'mgmt_only']
|
||||
|
||||
|
||||
class FrontPortTemplateFilter(DeviceTypeComponentFilterSet):
|
||||
|
||||
class Meta:
|
||||
model = FrontPortTemplate
|
||||
fields = ['name', 'type']
|
||||
|
||||
|
||||
class RearPortTemplateFilter(DeviceTypeComponentFilterSet):
|
||||
|
||||
class Meta:
|
||||
model = RearPortTemplate
|
||||
fields = ['name', 'type']
|
||||
|
||||
|
||||
class DeviceBayTemplateFilter(DeviceTypeComponentFilterSet):
|
||||
|
||||
class Meta:
|
||||
@@ -373,12 +455,12 @@ class DeviceRoleFilter(django_filters.FilterSet):
|
||||
|
||||
class PlatformFilter(django_filters.FilterSet):
|
||||
manufacturer_id = django_filters.ModelMultipleChoiceFilter(
|
||||
name='manufacturer',
|
||||
field_name='manufacturer',
|
||||
queryset=Manufacturer.objects.all(),
|
||||
label='Manufacturer (ID)',
|
||||
)
|
||||
manufacturer = django_filters.ModelMultipleChoiceFilter(
|
||||
name='manufacturer__slug',
|
||||
field_name='manufacturer__slug',
|
||||
queryset=Manufacturer.objects.all(),
|
||||
to_field_name='slug',
|
||||
label='Manufacturer (slug)',
|
||||
@@ -389,19 +471,22 @@ class PlatformFilter(django_filters.FilterSet):
|
||||
fields = ['name', 'slug']
|
||||
|
||||
|
||||
class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
||||
id__in = NumericInFilter(name='id', lookup_expr='in')
|
||||
class DeviceFilter(CustomFieldFilterSet):
|
||||
id__in = NumericInFilter(
|
||||
field_name='id',
|
||||
lookup_expr='in'
|
||||
)
|
||||
q = django_filters.CharFilter(
|
||||
method='search',
|
||||
label='Search',
|
||||
)
|
||||
manufacturer_id = django_filters.ModelMultipleChoiceFilter(
|
||||
name='device_type__manufacturer',
|
||||
field_name='device_type__manufacturer',
|
||||
queryset=Manufacturer.objects.all(),
|
||||
label='Manufacturer (ID)',
|
||||
)
|
||||
manufacturer = django_filters.ModelMultipleChoiceFilter(
|
||||
name='device_type__manufacturer__slug',
|
||||
field_name='device_type__manufacturer__slug',
|
||||
queryset=Manufacturer.objects.all(),
|
||||
to_field_name='slug',
|
||||
label='Manufacturer (slug)',
|
||||
@@ -411,12 +496,12 @@ class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
||||
label='Device type (ID)',
|
||||
)
|
||||
role_id = django_filters.ModelMultipleChoiceFilter(
|
||||
name='device_role_id',
|
||||
field_name='device_role_id',
|
||||
queryset=DeviceRole.objects.all(),
|
||||
label='Role (ID)',
|
||||
)
|
||||
role = django_filters.ModelMultipleChoiceFilter(
|
||||
name='device_role__slug',
|
||||
field_name='device_role__slug',
|
||||
queryset=DeviceRole.objects.all(),
|
||||
to_field_name='slug',
|
||||
label='Role (slug)',
|
||||
@@ -426,7 +511,7 @@ class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
||||
label='Tenant (ID)',
|
||||
)
|
||||
tenant = django_filters.ModelMultipleChoiceFilter(
|
||||
name='tenant__slug',
|
||||
field_name='tenant__slug',
|
||||
queryset=Tenant.objects.all(),
|
||||
to_field_name='slug',
|
||||
label='Tenant (slug)',
|
||||
@@ -436,30 +521,40 @@ class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
||||
label='Platform (ID)',
|
||||
)
|
||||
platform = django_filters.ModelMultipleChoiceFilter(
|
||||
name='platform__slug',
|
||||
field_name='platform__slug',
|
||||
queryset=Platform.objects.all(),
|
||||
to_field_name='slug',
|
||||
label='Platform (slug)',
|
||||
)
|
||||
name = NullableCharFieldFilter()
|
||||
asset_tag = NullableCharFieldFilter()
|
||||
region_id = django_filters.NumberFilter(
|
||||
method='filter_region',
|
||||
field_name='pk',
|
||||
label='Region (ID)',
|
||||
)
|
||||
region = django_filters.CharFilter(
|
||||
method='filter_region',
|
||||
field_name='slug',
|
||||
label='Region (slug)',
|
||||
)
|
||||
site_id = django_filters.ModelMultipleChoiceFilter(
|
||||
queryset=Site.objects.all(),
|
||||
label='Site (ID)',
|
||||
)
|
||||
site = django_filters.ModelMultipleChoiceFilter(
|
||||
name='site__slug',
|
||||
field_name='site__slug',
|
||||
queryset=Site.objects.all(),
|
||||
to_field_name='slug',
|
||||
label='Site name (slug)',
|
||||
)
|
||||
rack_group_id = django_filters.ModelMultipleChoiceFilter(
|
||||
name='rack__group',
|
||||
field_name='rack__group',
|
||||
queryset=RackGroup.objects.all(),
|
||||
label='Rack group (ID)',
|
||||
)
|
||||
rack_id = django_filters.ModelMultipleChoiceFilter(
|
||||
name='rack',
|
||||
field_name='rack',
|
||||
queryset=Rack.objects.all(),
|
||||
label='Rack (ID)',
|
||||
)
|
||||
@@ -468,7 +563,7 @@ class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
||||
label='VM cluster (ID)',
|
||||
)
|
||||
model = django_filters.ModelMultipleChoiceFilter(
|
||||
name='device_type__slug',
|
||||
field_name='device_type__slug',
|
||||
queryset=DeviceType.objects.all(),
|
||||
to_field_name='slug',
|
||||
label='Device model (slug)',
|
||||
@@ -478,21 +573,9 @@ class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
||||
null_value=None
|
||||
)
|
||||
is_full_depth = django_filters.BooleanFilter(
|
||||
name='device_type__is_full_depth',
|
||||
field_name='device_type__is_full_depth',
|
||||
label='Is full depth',
|
||||
)
|
||||
is_console_server = django_filters.BooleanFilter(
|
||||
name='device_type__is_console_server',
|
||||
label='Is a console server',
|
||||
)
|
||||
is_pdu = django_filters.BooleanFilter(
|
||||
name='device_type__is_pdu',
|
||||
label='Is a PDU',
|
||||
)
|
||||
is_network_device = django_filters.BooleanFilter(
|
||||
name='device_type__is_network_device',
|
||||
label='Is a network device',
|
||||
)
|
||||
mac_address = django_filters.CharFilter(
|
||||
method='_mac_address',
|
||||
label='MAC address',
|
||||
@@ -502,13 +585,35 @@ class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
||||
label='Has a primary IP',
|
||||
)
|
||||
virtual_chassis_id = django_filters.ModelMultipleChoiceFilter(
|
||||
name='virtual_chassis',
|
||||
field_name='virtual_chassis',
|
||||
queryset=VirtualChassis.objects.all(),
|
||||
label='Virtual chassis (ID)',
|
||||
)
|
||||
tag = django_filters.CharFilter(
|
||||
name='tags__slug',
|
||||
console_ports = django_filters.BooleanFilter(
|
||||
method='_console_ports',
|
||||
label='Has console ports',
|
||||
)
|
||||
console_server_ports = django_filters.BooleanFilter(
|
||||
method='_console_server_ports',
|
||||
label='Has console server ports',
|
||||
)
|
||||
power_ports = django_filters.BooleanFilter(
|
||||
method='_power_ports',
|
||||
label='Has power ports',
|
||||
)
|
||||
power_outlets = django_filters.BooleanFilter(
|
||||
method='_power_outlets',
|
||||
label='Has power outlets',
|
||||
)
|
||||
interfaces = django_filters.BooleanFilter(
|
||||
method='_interfaces',
|
||||
label='Has interfaces',
|
||||
)
|
||||
pass_through_ports = django_filters.BooleanFilter(
|
||||
method='_pass_through_ports',
|
||||
label='Has pass-through ports',
|
||||
)
|
||||
tag = TagFilter()
|
||||
|
||||
class Meta:
|
||||
model = Device
|
||||
@@ -525,6 +630,16 @@ class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
||||
Q(comments__icontains=value)
|
||||
).distinct()
|
||||
|
||||
def filter_region(self, queryset, name, value):
|
||||
try:
|
||||
region = Region.objects.get(**{name: value})
|
||||
except ObjectDoesNotExist:
|
||||
return queryset.none()
|
||||
return queryset.filter(
|
||||
Q(site__region=region) |
|
||||
Q(site__region__in=region.get_descendants())
|
||||
)
|
||||
|
||||
def _mac_address(self, queryset, name, value):
|
||||
value = value.strip()
|
||||
if not value:
|
||||
@@ -547,6 +662,27 @@ class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
||||
Q(primary_ip6__isnull=False)
|
||||
)
|
||||
|
||||
def _console_ports(self, queryset, name, value):
|
||||
return queryset.exclude(consoleports__isnull=value)
|
||||
|
||||
def _console_server_ports(self, queryset, name, value):
|
||||
return queryset.exclude(consoleserverports__isnull=value)
|
||||
|
||||
def _power_ports(self, queryset, name, value):
|
||||
return queryset.exclude(powerports__isnull=value)
|
||||
|
||||
def _power_outlets(self, queryset, name, value):
|
||||
return queryset.exclude(poweroutlets_isnull=value)
|
||||
|
||||
def _interfaces(self, queryset, name, value):
|
||||
return queryset.exclude(interfaces__isnull=value)
|
||||
|
||||
def _pass_through_ports(self, queryset, name, value):
|
||||
return queryset.exclude(
|
||||
frontports__isnull=value,
|
||||
rearports__isnull=value
|
||||
)
|
||||
|
||||
|
||||
class DeviceComponentFilterSet(django_filters.FilterSet):
|
||||
device_id = django_filters.ModelChoiceFilter(
|
||||
@@ -558,9 +694,7 @@ class DeviceComponentFilterSet(django_filters.FilterSet):
|
||||
to_field_name='name',
|
||||
label='Device (name)',
|
||||
)
|
||||
tag = django_filters.CharFilter(
|
||||
name='tags__slug',
|
||||
)
|
||||
tag = TagFilter()
|
||||
|
||||
|
||||
class ConsolePortFilter(DeviceComponentFilterSet):
|
||||
@@ -598,12 +732,12 @@ class InterfaceFilter(django_filters.FilterSet):
|
||||
"""
|
||||
device = django_filters.CharFilter(
|
||||
method='filter_device',
|
||||
name='name',
|
||||
field_name='name',
|
||||
label='Device',
|
||||
)
|
||||
device_id = django_filters.NumberFilter(
|
||||
method='filter_device',
|
||||
name='pk',
|
||||
field_name='pk',
|
||||
label='Device (ID)',
|
||||
)
|
||||
type = django_filters.CharFilter(
|
||||
@@ -611,7 +745,7 @@ class InterfaceFilter(django_filters.FilterSet):
|
||||
label='Interface type',
|
||||
)
|
||||
lag_id = django_filters.ModelMultipleChoiceFilter(
|
||||
name='lag',
|
||||
field_name='lag',
|
||||
queryset=Interface.objects.all(),
|
||||
label='LAG interface (ID)',
|
||||
)
|
||||
@@ -619,8 +753,14 @@ class InterfaceFilter(django_filters.FilterSet):
|
||||
method='_mac_address',
|
||||
label='MAC address',
|
||||
)
|
||||
tag = django_filters.CharFilter(
|
||||
name='tags__slug',
|
||||
tag = TagFilter()
|
||||
vlan_id = django_filters.CharFilter(
|
||||
method='filter_vlan_id',
|
||||
label='Assigned VLAN'
|
||||
)
|
||||
vlan = django_filters.CharFilter(
|
||||
method='filter_vlan',
|
||||
label='Assigned VID'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
@@ -629,13 +769,30 @@ class InterfaceFilter(django_filters.FilterSet):
|
||||
|
||||
def filter_device(self, queryset, name, value):
|
||||
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')]
|
||||
ordering = device.device_type.interface_ordering
|
||||
return queryset.filter(pk__in=vc_interface_ids).order_naturally(ordering)
|
||||
return queryset.filter(pk__in=vc_interface_ids)
|
||||
except Device.DoesNotExist:
|
||||
return queryset.none()
|
||||
|
||||
def filter_vlan_id(self, queryset, name, value):
|
||||
value = value.strip()
|
||||
if not value:
|
||||
return queryset
|
||||
return queryset.filter(
|
||||
Q(untagged_vlan_id=value) |
|
||||
Q(tagged_vlans=value)
|
||||
)
|
||||
|
||||
def filter_vlan(self, queryset, name, value):
|
||||
value = value.strip()
|
||||
if not value:
|
||||
return queryset
|
||||
return queryset.filter(
|
||||
Q(untagged_vlan_id__vid=value) |
|
||||
Q(tagged_vlans__vid=value)
|
||||
)
|
||||
|
||||
def filter_type(self, queryset, name, value):
|
||||
value = value.strip().lower()
|
||||
return {
|
||||
@@ -656,6 +813,20 @@ class InterfaceFilter(django_filters.FilterSet):
|
||||
return queryset.none()
|
||||
|
||||
|
||||
class FrontPortFilter(DeviceComponentFilterSet):
|
||||
|
||||
class Meta:
|
||||
model = FrontPort
|
||||
fields = ['name', 'type']
|
||||
|
||||
|
||||
class RearPortFilter(DeviceComponentFilterSet):
|
||||
|
||||
class Meta:
|
||||
model = RearPort
|
||||
fields = ['name', 'type']
|
||||
|
||||
|
||||
class DeviceBayFilter(DeviceComponentFilterSet):
|
||||
|
||||
class Meta:
|
||||
@@ -668,6 +839,15 @@ class InventoryItemFilter(DeviceComponentFilterSet):
|
||||
method='search',
|
||||
label='Search',
|
||||
)
|
||||
device_id = django_filters.ModelChoiceFilter(
|
||||
queryset=Device.objects.all(),
|
||||
label='Device (ID)',
|
||||
)
|
||||
device = django_filters.ModelChoiceFilter(
|
||||
queryset=Device.objects.all(),
|
||||
to_field_name='name',
|
||||
label='Device (name)',
|
||||
)
|
||||
parent_id = django_filters.ModelMultipleChoiceFilter(
|
||||
queryset=InventoryItem.objects.all(),
|
||||
label='Parent inventory item (ID)',
|
||||
@@ -677,7 +857,7 @@ class InventoryItemFilter(DeviceComponentFilterSet):
|
||||
label='Manufacturer (ID)',
|
||||
)
|
||||
manufacturer = django_filters.ModelMultipleChoiceFilter(
|
||||
name='manufacturer__slug',
|
||||
field_name='manufacturer__slug',
|
||||
queryset=Manufacturer.objects.all(),
|
||||
to_field_name='slug',
|
||||
label='Manufacturer (slug)',
|
||||
@@ -707,30 +887,28 @@ class VirtualChassisFilter(django_filters.FilterSet):
|
||||
label='Search',
|
||||
)
|
||||
site_id = django_filters.ModelMultipleChoiceFilter(
|
||||
name='master__site',
|
||||
field_name='master__site',
|
||||
queryset=Site.objects.all(),
|
||||
label='Site (ID)',
|
||||
)
|
||||
site = django_filters.ModelMultipleChoiceFilter(
|
||||
name='master__site__slug',
|
||||
field_name='master__site__slug',
|
||||
queryset=Site.objects.all(),
|
||||
to_field_name='slug',
|
||||
label='Site name (slug)',
|
||||
)
|
||||
tenant_id = django_filters.ModelMultipleChoiceFilter(
|
||||
name='master__tenant',
|
||||
field_name='master__tenant',
|
||||
queryset=Tenant.objects.all(),
|
||||
label='Tenant (ID)',
|
||||
)
|
||||
tenant = django_filters.ModelMultipleChoiceFilter(
|
||||
name='master__tenant__slug',
|
||||
field_name='master__tenant__slug',
|
||||
queryset=Tenant.objects.all(),
|
||||
to_field_name='slug',
|
||||
label='Tenant (slug)',
|
||||
)
|
||||
tag = django_filters.CharFilter(
|
||||
name='tags__slug',
|
||||
)
|
||||
tag = TagFilter()
|
||||
|
||||
class Meta:
|
||||
model = VirtualChassis
|
||||
@@ -746,6 +924,22 @@ class VirtualChassisFilter(django_filters.FilterSet):
|
||||
return queryset.filter(qs_filter)
|
||||
|
||||
|
||||
class CableFilter(django_filters.FilterSet):
|
||||
q = django_filters.CharFilter(
|
||||
method='search',
|
||||
label='Search',
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Cable
|
||||
fields = ['type', 'status', 'color', 'length', 'length_unit']
|
||||
|
||||
def search(self, queryset, name, value):
|
||||
if not value.strip():
|
||||
return queryset
|
||||
return queryset.filter(label__icontains=value)
|
||||
|
||||
|
||||
class ConsoleConnectionFilter(django_filters.FilterSet):
|
||||
site = django_filters.CharFilter(
|
||||
method='filter_site',
|
||||
@@ -763,14 +957,14 @@ class ConsoleConnectionFilter(django_filters.FilterSet):
|
||||
def filter_site(self, queryset, name, value):
|
||||
if not value.strip():
|
||||
return queryset
|
||||
return queryset.filter(cs_port__device__site__slug=value)
|
||||
return queryset.filter(connected_endpoint__device__site__slug=value)
|
||||
|
||||
def filter_device(self, queryset, name, value):
|
||||
if not value.strip():
|
||||
return queryset
|
||||
return queryset.filter(
|
||||
Q(device__name__icontains=value) |
|
||||
Q(cs_port__device__name__icontains=value)
|
||||
Q(connected_endpoint__device__name__icontains=value)
|
||||
)
|
||||
|
||||
|
||||
@@ -791,14 +985,14 @@ class PowerConnectionFilter(django_filters.FilterSet):
|
||||
def filter_site(self, queryset, name, value):
|
||||
if not value.strip():
|
||||
return queryset
|
||||
return queryset.filter(power_outlet__device__site__slug=value)
|
||||
return queryset.filter(connected_endpoint__device__site__slug=value)
|
||||
|
||||
def filter_device(self, queryset, name, value):
|
||||
if not value.strip():
|
||||
return queryset
|
||||
return queryset.filter(
|
||||
Q(device__name__icontains=value) |
|
||||
Q(power_outlet__device__name__icontains=value)
|
||||
Q(connected_endpoint__device__name__icontains=value)
|
||||
)
|
||||
|
||||
|
||||
@@ -813,21 +1007,21 @@ class InterfaceConnectionFilter(django_filters.FilterSet):
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = InterfaceConnection
|
||||
model = Interface
|
||||
fields = ['connection_status']
|
||||
|
||||
def filter_site(self, queryset, name, value):
|
||||
if not value.strip():
|
||||
return queryset
|
||||
return queryset.filter(
|
||||
Q(interface_a__device__site__slug=value) |
|
||||
Q(interface_b__device__site__slug=value)
|
||||
Q(device__site__slug=value) |
|
||||
Q(_connected_interface__device__site__slug=value)
|
||||
)
|
||||
|
||||
def filter_device(self, queryset, name, value):
|
||||
if not value.strip():
|
||||
return queryset
|
||||
return queryset.filter(
|
||||
Q(interface_a__device__name__icontains=value) |
|
||||
Q(interface_b__device__name__icontains=value)
|
||||
Q(device__name__icontains=value) |
|
||||
Q(_connected_interface__device__name__icontains=value)
|
||||
)
|
||||
|
||||
@@ -76,10 +76,7 @@
|
||||
"model": "MX960",
|
||||
"slug": "mx960",
|
||||
"u_height": 16,
|
||||
"is_full_depth": true,
|
||||
"is_console_server": false,
|
||||
"is_pdu": false,
|
||||
"is_network_device": true
|
||||
"is_full_depth": true
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -92,10 +89,7 @@
|
||||
"model": "EX9214",
|
||||
"slug": "ex9214",
|
||||
"u_height": 16,
|
||||
"is_full_depth": true,
|
||||
"is_console_server": false,
|
||||
"is_pdu": false,
|
||||
"is_network_device": true
|
||||
"is_full_depth": true
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -108,10 +102,7 @@
|
||||
"model": "QFX5100-24Q",
|
||||
"slug": "qfx5100-24q",
|
||||
"u_height": 1,
|
||||
"is_full_depth": true,
|
||||
"is_console_server": false,
|
||||
"is_pdu": false,
|
||||
"is_network_device": true
|
||||
"is_full_depth": true
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -124,10 +115,7 @@
|
||||
"model": "QFX5100-48S",
|
||||
"slug": "qfx5100-48s",
|
||||
"u_height": 1,
|
||||
"is_full_depth": true,
|
||||
"is_console_server": false,
|
||||
"is_pdu": false,
|
||||
"is_network_device": true
|
||||
"is_full_depth": true
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -140,10 +128,7 @@
|
||||
"model": "CM4148",
|
||||
"slug": "cm4148",
|
||||
"u_height": 1,
|
||||
"is_full_depth": true,
|
||||
"is_console_server": true,
|
||||
"is_pdu": false,
|
||||
"is_network_device": false
|
||||
"is_full_depth": true
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -156,10 +141,7 @@
|
||||
"model": "CWG-24VYM415C9",
|
||||
"slug": "cwg-24vym415c9",
|
||||
"u_height": 0,
|
||||
"is_full_depth": false,
|
||||
"is_console_server": false,
|
||||
"is_pdu": true,
|
||||
"is_network_device": false
|
||||
"is_full_depth": false
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -1903,8 +1885,7 @@
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"name": "Juniper Junos",
|
||||
"slug": "juniper-junos",
|
||||
"rpc_client": "juniper-junos"
|
||||
"slug": "juniper-junos"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -1912,8 +1893,7 @@
|
||||
"pk": 2,
|
||||
"fields": {
|
||||
"name": "Opengear",
|
||||
"slug": "opengear",
|
||||
"rpc_client": "opengear"
|
||||
"slug": "opengear"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -2153,7 +2133,7 @@
|
||||
"fields": {
|
||||
"device": 1,
|
||||
"name": "Console (RE0)",
|
||||
"cs_port": 27,
|
||||
"connected_endpoint": 27,
|
||||
"connection_status": true
|
||||
}
|
||||
},
|
||||
@@ -2163,7 +2143,7 @@
|
||||
"fields": {
|
||||
"device": 1,
|
||||
"name": "Console (RE1)",
|
||||
"cs_port": 38,
|
||||
"connected_endpoint": 38,
|
||||
"connection_status": true
|
||||
}
|
||||
},
|
||||
@@ -2173,7 +2153,7 @@
|
||||
"fields": {
|
||||
"device": 2,
|
||||
"name": "Console (RE0)",
|
||||
"cs_port": 5,
|
||||
"connected_endpoint": 5,
|
||||
"connection_status": true
|
||||
}
|
||||
},
|
||||
@@ -2183,7 +2163,7 @@
|
||||
"fields": {
|
||||
"device": 2,
|
||||
"name": "Console (RE1)",
|
||||
"cs_port": 16,
|
||||
"connected_endpoint": 16,
|
||||
"connection_status": true
|
||||
}
|
||||
},
|
||||
@@ -2193,7 +2173,7 @@
|
||||
"fields": {
|
||||
"device": 3,
|
||||
"name": "Console",
|
||||
"cs_port": 49,
|
||||
"connected_endpoint": 49,
|
||||
"connection_status": true
|
||||
}
|
||||
},
|
||||
@@ -2203,7 +2183,7 @@
|
||||
"fields": {
|
||||
"device": 4,
|
||||
"name": "Console",
|
||||
"cs_port": 48,
|
||||
"connected_endpoint": 48,
|
||||
"connection_status": true
|
||||
}
|
||||
},
|
||||
@@ -2213,7 +2193,7 @@
|
||||
"fields": {
|
||||
"device": 5,
|
||||
"name": "Console",
|
||||
"cs_port": null,
|
||||
"connected_endpoint": null,
|
||||
"connection_status": true
|
||||
}
|
||||
},
|
||||
@@ -2223,7 +2203,7 @@
|
||||
"fields": {
|
||||
"device": 6,
|
||||
"name": "Console",
|
||||
"cs_port": null,
|
||||
"connected_endpoint": null,
|
||||
"connection_status": true
|
||||
}
|
||||
},
|
||||
@@ -2233,7 +2213,7 @@
|
||||
"fields": {
|
||||
"device": 7,
|
||||
"name": "Console (RE0)",
|
||||
"cs_port": null,
|
||||
"connected_endpoint": null,
|
||||
"connection_status": true
|
||||
}
|
||||
},
|
||||
@@ -2243,7 +2223,7 @@
|
||||
"fields": {
|
||||
"device": 7,
|
||||
"name": "Console (RE1)",
|
||||
"cs_port": null,
|
||||
"connected_endpoint": null,
|
||||
"connection_status": true
|
||||
}
|
||||
},
|
||||
@@ -2253,7 +2233,7 @@
|
||||
"fields": {
|
||||
"device": 8,
|
||||
"name": "Console (RE0)",
|
||||
"cs_port": null,
|
||||
"connected_endpoint": null,
|
||||
"connection_status": true
|
||||
}
|
||||
},
|
||||
@@ -2263,7 +2243,7 @@
|
||||
"fields": {
|
||||
"device": 8,
|
||||
"name": "Console (RE1)",
|
||||
"cs_port": null,
|
||||
"connected_endpoint": null,
|
||||
"connection_status": true
|
||||
}
|
||||
},
|
||||
@@ -2273,7 +2253,7 @@
|
||||
"fields": {
|
||||
"device": 9,
|
||||
"name": "Console",
|
||||
"cs_port": null,
|
||||
"connected_endpoint": null,
|
||||
"connection_status": true
|
||||
}
|
||||
},
|
||||
@@ -2283,7 +2263,7 @@
|
||||
"fields": {
|
||||
"device": 11,
|
||||
"name": "Serial",
|
||||
"cs_port": null,
|
||||
"connected_endpoint": null,
|
||||
"connection_status": true
|
||||
}
|
||||
},
|
||||
@@ -2293,7 +2273,7 @@
|
||||
"fields": {
|
||||
"device": 12,
|
||||
"name": "Serial",
|
||||
"cs_port": null,
|
||||
"connected_endpoint": null,
|
||||
"connection_status": true
|
||||
}
|
||||
},
|
||||
@@ -2687,7 +2667,7 @@
|
||||
"fields": {
|
||||
"device": 1,
|
||||
"name": "PEM0",
|
||||
"power_outlet": 25,
|
||||
"connected_endpoint": 25,
|
||||
"connection_status": true
|
||||
}
|
||||
},
|
||||
@@ -2697,7 +2677,7 @@
|
||||
"fields": {
|
||||
"device": 1,
|
||||
"name": "PEM1",
|
||||
"power_outlet": 49,
|
||||
"connected_endpoint": 49,
|
||||
"connection_status": true
|
||||
}
|
||||
},
|
||||
@@ -2707,7 +2687,7 @@
|
||||
"fields": {
|
||||
"device": 1,
|
||||
"name": "PEM2",
|
||||
"power_outlet": null,
|
||||
"connected_endpoint": null,
|
||||
"connection_status": true
|
||||
}
|
||||
},
|
||||
@@ -2717,7 +2697,7 @@
|
||||
"fields": {
|
||||
"device": 1,
|
||||
"name": "PEM3",
|
||||
"power_outlet": null,
|
||||
"connected_endpoint": null,
|
||||
"connection_status": true
|
||||
}
|
||||
},
|
||||
@@ -2727,7 +2707,7 @@
|
||||
"fields": {
|
||||
"device": 2,
|
||||
"name": "PEM0",
|
||||
"power_outlet": 26,
|
||||
"connected_endpoint": 26,
|
||||
"connection_status": true
|
||||
}
|
||||
},
|
||||
@@ -2737,7 +2717,7 @@
|
||||
"fields": {
|
||||
"device": 2,
|
||||
"name": "PEM1",
|
||||
"power_outlet": 50,
|
||||
"connected_endpoint": 50,
|
||||
"connection_status": true
|
||||
}
|
||||
},
|
||||
@@ -2747,7 +2727,7 @@
|
||||
"fields": {
|
||||
"device": 2,
|
||||
"name": "PEM2",
|
||||
"power_outlet": null,
|
||||
"connected_endpoint": null,
|
||||
"connection_status": true
|
||||
}
|
||||
},
|
||||
@@ -2757,7 +2737,7 @@
|
||||
"fields": {
|
||||
"device": 2,
|
||||
"name": "PEM3",
|
||||
"power_outlet": null,
|
||||
"connected_endpoint": null,
|
||||
"connection_status": true
|
||||
}
|
||||
},
|
||||
@@ -2767,7 +2747,7 @@
|
||||
"fields": {
|
||||
"device": 4,
|
||||
"name": "PSU0",
|
||||
"power_outlet": 28,
|
||||
"connected_endpoint": 28,
|
||||
"connection_status": true
|
||||
}
|
||||
},
|
||||
@@ -2777,7 +2757,7 @@
|
||||
"fields": {
|
||||
"device": 4,
|
||||
"name": "PSU1",
|
||||
"power_outlet": 52,
|
||||
"connected_endpoint": 52,
|
||||
"connection_status": true
|
||||
}
|
||||
},
|
||||
@@ -2787,7 +2767,7 @@
|
||||
"fields": {
|
||||
"device": 5,
|
||||
"name": "PSU0",
|
||||
"power_outlet": 56,
|
||||
"connected_endpoint": 56,
|
||||
"connection_status": true
|
||||
}
|
||||
},
|
||||
@@ -2797,7 +2777,7 @@
|
||||
"fields": {
|
||||
"device": 5,
|
||||
"name": "PSU1",
|
||||
"power_outlet": 32,
|
||||
"connected_endpoint": 32,
|
||||
"connection_status": true
|
||||
}
|
||||
},
|
||||
@@ -2807,7 +2787,7 @@
|
||||
"fields": {
|
||||
"device": 3,
|
||||
"name": "PSU0",
|
||||
"power_outlet": 27,
|
||||
"connected_endpoint": 27,
|
||||
"connection_status": true
|
||||
}
|
||||
},
|
||||
@@ -2817,7 +2797,7 @@
|
||||
"fields": {
|
||||
"device": 3,
|
||||
"name": "PSU1",
|
||||
"power_outlet": 51,
|
||||
"connected_endpoint": 51,
|
||||
"connection_status": true
|
||||
}
|
||||
},
|
||||
@@ -2827,7 +2807,7 @@
|
||||
"fields": {
|
||||
"device": 7,
|
||||
"name": "PEM0",
|
||||
"power_outlet": 53,
|
||||
"connected_endpoint": 53,
|
||||
"connection_status": true
|
||||
}
|
||||
},
|
||||
@@ -2837,7 +2817,7 @@
|
||||
"fields": {
|
||||
"device": 7,
|
||||
"name": "PEM1",
|
||||
"power_outlet": 29,
|
||||
"connected_endpoint": 29,
|
||||
"connection_status": true
|
||||
}
|
||||
},
|
||||
@@ -2847,7 +2827,7 @@
|
||||
"fields": {
|
||||
"device": 7,
|
||||
"name": "PEM2",
|
||||
"power_outlet": null,
|
||||
"connected_endpoint": null,
|
||||
"connection_status": true
|
||||
}
|
||||
},
|
||||
@@ -2857,7 +2837,7 @@
|
||||
"fields": {
|
||||
"device": 7,
|
||||
"name": "PEM3",
|
||||
"power_outlet": null,
|
||||
"connected_endpoint": null,
|
||||
"connection_status": true
|
||||
}
|
||||
},
|
||||
@@ -2867,7 +2847,7 @@
|
||||
"fields": {
|
||||
"device": 8,
|
||||
"name": "PEM0",
|
||||
"power_outlet": 54,
|
||||
"connected_endpoint": 54,
|
||||
"connection_status": true
|
||||
}
|
||||
},
|
||||
@@ -2877,7 +2857,7 @@
|
||||
"fields": {
|
||||
"device": 8,
|
||||
"name": "PEM1",
|
||||
"power_outlet": 30,
|
||||
"connected_endpoint": 30,
|
||||
"connection_status": true
|
||||
}
|
||||
},
|
||||
@@ -2887,7 +2867,7 @@
|
||||
"fields": {
|
||||
"device": 8,
|
||||
"name": "PEM2",
|
||||
"power_outlet": null,
|
||||
"connected_endpoint": null,
|
||||
"connection_status": true
|
||||
}
|
||||
},
|
||||
@@ -2897,7 +2877,7 @@
|
||||
"fields": {
|
||||
"device": 8,
|
||||
"name": "PEM3",
|
||||
"power_outlet": null,
|
||||
"connected_endpoint": null,
|
||||
"connection_status": true
|
||||
}
|
||||
},
|
||||
@@ -2907,7 +2887,7 @@
|
||||
"fields": {
|
||||
"device": 6,
|
||||
"name": "PSU0",
|
||||
"power_outlet": 55,
|
||||
"connected_endpoint": 55,
|
||||
"connection_status": true
|
||||
}
|
||||
},
|
||||
@@ -2917,7 +2897,7 @@
|
||||
"fields": {
|
||||
"device": 6,
|
||||
"name": "PSU1",
|
||||
"power_outlet": 31,
|
||||
"connected_endpoint": 31,
|
||||
"connection_status": true
|
||||
}
|
||||
},
|
||||
@@ -2927,7 +2907,7 @@
|
||||
"fields": {
|
||||
"device": 9,
|
||||
"name": "PSU",
|
||||
"power_outlet": null,
|
||||
"connected_endpoint": null,
|
||||
"connection_status": true
|
||||
}
|
||||
},
|
||||
@@ -5748,158 +5728,5 @@
|
||||
"mgmt_only": true,
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "dcim.interfaceconnection",
|
||||
"pk": 3,
|
||||
"fields": {
|
||||
"interface_a": 99,
|
||||
"interface_b": 15,
|
||||
"connection_status": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "dcim.interfaceconnection",
|
||||
"pk": 4,
|
||||
"fields": {
|
||||
"interface_a": 100,
|
||||
"interface_b": 153,
|
||||
"connection_status": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "dcim.interfaceconnection",
|
||||
"pk": 5,
|
||||
"fields": {
|
||||
"interface_a": 46,
|
||||
"interface_b": 14,
|
||||
"connection_status": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "dcim.interfaceconnection",
|
||||
"pk": 6,
|
||||
"fields": {
|
||||
"interface_a": 47,
|
||||
"interface_b": 152,
|
||||
"connection_status": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "dcim.interfaceconnection",
|
||||
"pk": 7,
|
||||
"fields": {
|
||||
"interface_a": 91,
|
||||
"interface_b": 144,
|
||||
"connection_status": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "dcim.interfaceconnection",
|
||||
"pk": 8,
|
||||
"fields": {
|
||||
"interface_a": 92,
|
||||
"interface_b": 145,
|
||||
"connection_status": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "dcim.interfaceconnection",
|
||||
"pk": 16,
|
||||
"fields": {
|
||||
"interface_a": 189,
|
||||
"interface_b": 37,
|
||||
"connection_status": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "dcim.interfaceconnection",
|
||||
"pk": 17,
|
||||
"fields": {
|
||||
"interface_a": 192,
|
||||
"interface_b": 175,
|
||||
"connection_status": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "dcim.interfaceconnection",
|
||||
"pk": 18,
|
||||
"fields": {
|
||||
"interface_a": 195,
|
||||
"interface_b": 41,
|
||||
"connection_status": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "dcim.interfaceconnection",
|
||||
"pk": 19,
|
||||
"fields": {
|
||||
"interface_a": 198,
|
||||
"interface_b": 179,
|
||||
"connection_status": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "dcim.interfaceconnection",
|
||||
"pk": 20,
|
||||
"fields": {
|
||||
"interface_a": 191,
|
||||
"interface_b": 197,
|
||||
"connection_status": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "dcim.interfaceconnection",
|
||||
"pk": 21,
|
||||
"fields": {
|
||||
"interface_a": 194,
|
||||
"interface_b": 200,
|
||||
"connection_status": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "dcim.interfaceconnection",
|
||||
"pk": 22,
|
||||
"fields": {
|
||||
"interface_a": 9,
|
||||
"interface_b": 218,
|
||||
"connection_status": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "dcim.interfaceconnection",
|
||||
"pk": 23,
|
||||
"fields": {
|
||||
"interface_a": 8,
|
||||
"interface_b": 206,
|
||||
"connection_status": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "dcim.interfaceconnection",
|
||||
"pk": 24,
|
||||
"fields": {
|
||||
"interface_a": 7,
|
||||
"interface_b": 212,
|
||||
"connection_status": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "dcim.interfaceconnection",
|
||||
"pk": 25,
|
||||
"fields": {
|
||||
"interface_a": 217,
|
||||
"interface_b": 205,
|
||||
"connection_status": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "dcim.interfaceconnection",
|
||||
"pk": 26,
|
||||
"fields": {
|
||||
"interface_a": 216,
|
||||
"interface_b": 211,
|
||||
"connection_status": true
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -149,8 +149,7 @@
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"name": "Cisco IOS",
|
||||
"slug": "cisco-ios",
|
||||
"rpc_client": "cisco-ios"
|
||||
"slug": "cisco-ios"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -158,8 +157,7 @@
|
||||
"pk": 2,
|
||||
"fields": {
|
||||
"name": "Cisco NX-OS",
|
||||
"slug": "cisco-nx-os",
|
||||
"rpc_client": ""
|
||||
"slug": "cisco-nx-os"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -167,8 +165,7 @@
|
||||
"pk": 3,
|
||||
"fields": {
|
||||
"name": "Juniper Junos",
|
||||
"slug": "juniper-junos",
|
||||
"rpc_client": "juniper-junos"
|
||||
"slug": "juniper-junos"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -176,8 +173,7 @@
|
||||
"pk": 4,
|
||||
"fields": {
|
||||
"name": "Arista EOS",
|
||||
"slug": "arista-eos",
|
||||
"rpc_client": ""
|
||||
"slug": "arista-eos"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -185,8 +181,7 @@
|
||||
"pk": 5,
|
||||
"fields": {
|
||||
"name": "Linux",
|
||||
"slug": "linux",
|
||||
"rpc_client": ""
|
||||
"slug": "linux"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -194,8 +189,7 @@
|
||||
"pk": 6,
|
||||
"fields": {
|
||||
"name": "Opengear",
|
||||
"slug": "opengear",
|
||||
"rpc_client": "opengear"
|
||||
"slug": "opengear"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django import forms
|
||||
from django.core.exceptions import ValidationError
|
||||
from netaddr import EUI, AddrFormatError
|
||||
|
||||
|
||||
#
|
||||
# Form fields
|
||||
#
|
||||
|
||||
class MACAddressFormField(forms.Field):
|
||||
default_error_messages = {
|
||||
'invalid': "Enter a valid MAC address.",
|
||||
}
|
||||
|
||||
def to_python(self, value):
|
||||
if not value:
|
||||
return None
|
||||
|
||||
if isinstance(value, EUI):
|
||||
return value
|
||||
|
||||
try:
|
||||
return EUI(value, version=48)
|
||||
except AddrFormatError:
|
||||
raise ValidationError("Please specify a valid MAC address.")
|
||||
1249
netbox/dcim/forms.py
1249
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(DeviceComponentManager, self).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 -*-
|
||||
# Generated by Django 1.9.7 on 2016-06-22 18:21
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.7 on 2016-06-22 18:21
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.14 on 2018-07-31 02:06
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import dcim.fields
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.7 on 2016-06-28 17:21
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.7 on 2016-07-01 20:49
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.7 on 2016-07-06 17:22
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import dcim.fields
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.7 on 2016-07-11 18:40
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.7 on 2016-07-11 18:40
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.7 on 2016-07-11 19:01
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.7 on 2016-07-13 19:24
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import dcim.fields
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.7 on 2016-07-14 21:38
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.8 on 2016-07-26 15:05
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.8 on 2016-07-26 21:59
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.8 on 2016-08-06 20:24
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.8 on 2016-08-08 21:11
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.8 on 2016-08-09 21:18
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.8 on 2016-08-10 13:45
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.8 on 2016-08-10 14:58
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10 on 2016-08-11 15:42
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
import utilities.fields
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10 on 2016-09-13 15:20
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10 on 2016-10-28 15:01
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10 on 2016-10-31 18:47
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10 on 2016-12-06 16:35
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
import utilities.fields
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10 on 2016-12-16 16:08
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.14 on 2018-07-31 02:13
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import dcim.fields
|
||||
from django.conf import settings
|
||||
import django.contrib.postgres.fields
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.4 on 2016-12-29 16:23
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.4 on 2017-01-06 16:56
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.4 on 2017-02-16 18:43
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.conf import settings
|
||||
import django.contrib.postgres.fields
|
||||
from django.db import migrations, models
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.4 on 2017-02-16 21:21
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.4 on 2017-02-16 21:23
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.4 on 2017-02-16 21:25
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.4 on 2017-02-27 19:55
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.4 on 2017-02-28 17:14
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import mptt.fields
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.4 on 2017-03-02 15:09
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
import utilities.fields
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.6 on 2017-03-17 18:39
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.6 on 2017-03-21 14:55
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user