mirror of
https://github.com/netbox-community/netbox.git
synced 2026-01-20 17:08:07 +01:00
Compare commits
329 Commits
v2.7-beta1
...
v2.7.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea91e09a1b | ||
|
|
0f1518e4c5 | ||
|
|
4cb1facb6a | ||
|
|
e640f413ab | ||
|
|
fecbb60c36 | ||
|
|
c0f1910493 | ||
|
|
3eb2d45e8d | ||
|
|
4556eac780 | ||
|
|
c955aeebeb | ||
|
|
8bd67b2c17 | ||
|
|
4073dedff8 | ||
|
|
bc696f2e11 | ||
|
|
c28684a8b3 | ||
|
|
215b4d0b3f | ||
|
|
d9437a08f0 | ||
|
|
f81e7d30e2 | ||
|
|
09bee75cb3 | ||
|
|
9c4ab79bea | ||
|
|
f8dad1744c | ||
|
|
b98ac64ac2 | ||
|
|
88267e9d05 | ||
|
|
caf7d02637 | ||
|
|
aefe2b4196 | ||
|
|
fdf8211e9a | ||
|
|
73d1a2df3d | ||
|
|
0893d32665 | ||
|
|
c5ec470a00 | ||
|
|
28350d84f9 | ||
|
|
1055faf734 | ||
|
|
351a6e005e | ||
|
|
e5ebe6cebc | ||
|
|
0053aa2d2e | ||
|
|
dda9a2ee1c | ||
|
|
1ea820a50e | ||
|
|
c202c1325b | ||
|
|
49f027fae7 | ||
|
|
deec10efe7 | ||
|
|
826f4d313d | ||
|
|
685cf50268 | ||
|
|
e0ea5b0e0b | ||
|
|
b538495a29 | ||
|
|
8df53eac91 | ||
|
|
857e04e90b | ||
|
|
3f37cc461d | ||
|
|
823e1280d2 | ||
|
|
a9e1e7fc78 | ||
|
|
f27e06e619 | ||
|
|
c084547dca | ||
|
|
6959785cd1 | ||
|
|
26a257b794 | ||
|
|
2615906526 | ||
|
|
d96f474a5f | ||
|
|
d33e10b4ce | ||
|
|
e536f363f9 | ||
|
|
9d0da0f45a | ||
|
|
7b8e82f321 | ||
|
|
5c047faa1d | ||
|
|
8f636d9636 | ||
|
|
ce0e351d76 | ||
|
|
f170a579de | ||
|
|
0dad9f8901 | ||
|
|
473d67354f | ||
|
|
5d7af0fae9 | ||
|
|
946779000f | ||
|
|
c3c3000a53 | ||
|
|
66daeda85f | ||
|
|
0fd0e76183 | ||
|
|
cf480375f6 | ||
|
|
736cd709d9 | ||
|
|
0f42219b4b | ||
|
|
b64f4b93eb | ||
|
|
32ffc1b54b | ||
|
|
608006ee77 | ||
|
|
3d78a67343 | ||
|
|
2f98f133bb | ||
|
|
fe0fbeab49 | ||
|
|
e3aacb183b | ||
|
|
49fa243b4f | ||
|
|
a2308b9c99 | ||
|
|
422c6bad5b | ||
|
|
db57d3830f | ||
|
|
b7e78028ce | ||
|
|
ca13045515 | ||
|
|
2e9f21e222 | ||
|
|
9f627fd0d3 | ||
|
|
509a115f68 | ||
|
|
69a696a8d6 | ||
|
|
830a51d9f5 | ||
|
|
3c247ac47d | ||
|
|
123a58bf7d | ||
|
|
f20d16f188 | ||
|
|
9399652dd0 | ||
|
|
f4514034b8 | ||
|
|
6bc8f2e50b | ||
|
|
fc1245c49d | ||
|
|
4be7ca0c78 | ||
|
|
cb91c9231d | ||
|
|
03b22594e8 | ||
|
|
a5413a5484 | ||
|
|
71120d9899 | ||
|
|
acb66c7dc0 | ||
|
|
2eba84dad5 | ||
|
|
fe89982d4e | ||
|
|
528b345f57 | ||
|
|
e3807a8937 | ||
|
|
da0ac4ff1e | ||
|
|
49a6a36f4c | ||
|
|
a77fadd114 | ||
|
|
15e1f62919 | ||
|
|
83c0d1ef44 | ||
|
|
97654b7585 | ||
|
|
0767de205e | ||
|
|
847cf9d038 | ||
|
|
0296aa240a | ||
|
|
d88b3456c4 | ||
|
|
789cf827f2 | ||
|
|
6c19c88e99 | ||
|
|
c1ed2b6068 | ||
|
|
790cfd7b5b | ||
|
|
dc5f5efcfe | ||
|
|
4eacc57522 | ||
|
|
0527626709 | ||
|
|
a2ead6af94 | ||
|
|
1e740a70f7 | ||
|
|
94a7d8e493 | ||
|
|
883655ce71 | ||
|
|
1d3651e255 | ||
|
|
fe490d144a | ||
|
|
40fe6666e3 | ||
|
|
2a2bc66841 | ||
|
|
6019260374 | ||
|
|
e5c5a1a101 | ||
|
|
c13b9d8798 | ||
|
|
03b10b6f73 | ||
|
|
67f4d8fab5 | ||
|
|
7e0073d6f5 | ||
|
|
c66dca399b | ||
|
|
9d085ad83a | ||
|
|
ad565e55f1 | ||
|
|
46c712e735 | ||
|
|
989d6f5af3 | ||
|
|
86865b91f8 | ||
|
|
b53480dd6a | ||
|
|
581ed52b24 | ||
|
|
472486acd6 | ||
|
|
92ec4bd22f | ||
|
|
959a0da0ed | ||
|
|
b23eaeca54 | ||
|
|
4f9271e9ff | ||
|
|
094553dbe7 | ||
|
|
60e4812b32 | ||
|
|
40625d1299 | ||
|
|
c9ec8b71e0 | ||
|
|
73e456495f | ||
|
|
54227ca9c7 | ||
|
|
f3b323536e | ||
|
|
6537f35176 | ||
|
|
b36d0ca3fc | ||
|
|
99809109ab | ||
|
|
1cdbfd6d60 | ||
|
|
4030e5ec24 | ||
|
|
4151e52802 | ||
|
|
b1e8145ffb | ||
|
|
c04d8ca5a7 | ||
|
|
e312c30822 | ||
|
|
40c30baffa | ||
|
|
bca7435a5a | ||
|
|
396bb28967 | ||
|
|
eb40275427 | ||
|
|
8519f546a6 | ||
|
|
e9b2ad9f5c | ||
|
|
39fba4f05d | ||
|
|
38c16d71b4 | ||
|
|
8fef6edb27 | ||
|
|
5cac900380 | ||
|
|
f49467bcb5 | ||
|
|
98a66f7fbe | ||
|
|
ce8d470860 | ||
|
|
dc475f4755 | ||
|
|
a7982bb0e1 | ||
|
|
ea05b5b606 | ||
|
|
996d49de67 | ||
|
|
74997a18a5 | ||
|
|
832fd49339 | ||
|
|
acb2f32304 | ||
|
|
32f39e10c9 | ||
|
|
190e683654 | ||
|
|
770f4c962c | ||
|
|
227921e0a0 | ||
|
|
39d0261d8a | ||
|
|
f267a532f6 | ||
|
|
e7ee4486a5 | ||
|
|
6a3cd83efc | ||
|
|
a7ec0c14f7 | ||
|
|
05bfe94d3e | ||
|
|
8d0aaa4ec1 | ||
|
|
4b5c4b7be5 | ||
|
|
18333973aa | ||
|
|
07a1baef13 | ||
|
|
15545b70d6 | ||
|
|
cfb8b3cf56 | ||
|
|
206732eb62 | ||
|
|
258cc4b50e | ||
|
|
d1f81783ef | ||
|
|
9bd2af48a3 | ||
|
|
d4df965f46 | ||
|
|
7dddd4734c | ||
|
|
c45daca5f2 | ||
|
|
067af26892 | ||
|
|
792f38334a | ||
|
|
dba40cd6bc | ||
|
|
4b19073b8b | ||
|
|
28eca9a026 | ||
|
|
3556051d14 | ||
|
|
e1d1f522ff | ||
|
|
04f3e58ab4 | ||
|
|
1f175031bd | ||
|
|
e1c61c5019 | ||
|
|
1c0de0093b | ||
|
|
9d8ab81e3a | ||
|
|
6e49a0ba6e | ||
|
|
fa55571503 | ||
|
|
feb04f0401 | ||
|
|
5c07b6dc1d | ||
|
|
e3b448b7ad | ||
|
|
57f199f899 | ||
|
|
b38bb64c81 | ||
|
|
1d63a30b7a | ||
|
|
c2dc243c7c | ||
|
|
25c3c1b431 | ||
|
|
156fbae7d3 | ||
|
|
a0ae7a227d | ||
|
|
9ef3e68479 | ||
|
|
435d248645 | ||
|
|
53db5090c1 | ||
|
|
caa062c8ba | ||
|
|
240bbc2944 | ||
|
|
a3861ed492 | ||
|
|
82c70302fd | ||
|
|
80d1f80b61 | ||
|
|
d3c6caf8a8 | ||
|
|
a707204f98 | ||
|
|
523a1388db | ||
|
|
970586b07b | ||
|
|
28ae6849b4 | ||
|
|
8e3a371688 | ||
|
|
2a219eff23 | ||
|
|
f81641ae96 | ||
|
|
4f0d3e6b32 | ||
|
|
77d1ac8b07 | ||
|
|
5d0ac02704 | ||
|
|
fc5d07bb13 | ||
|
|
395f23e1d3 | ||
|
|
dd85448451 | ||
|
|
6a7af22dea | ||
|
|
37bc17d3a2 | ||
|
|
ca131e5b2a | ||
|
|
c75795ceda | ||
|
|
7dc0591e3e | ||
|
|
cfa078c929 | ||
|
|
57ea73db46 | ||
|
|
7ad9e8a2fb | ||
|
|
1a57120b78 | ||
|
|
d267aeb621 | ||
|
|
a110b0badb | ||
|
|
242ae9eb91 | ||
|
|
53625e0dea | ||
|
|
aa4f73ffbf | ||
|
|
8c7b0cf670 | ||
|
|
05570ae4ad | ||
|
|
0cf94cff16 | ||
|
|
b5455ed882 | ||
|
|
8a4293a4cc | ||
|
|
f649b9f04f | ||
|
|
5caa04ef2b | ||
|
|
f2c49063f8 | ||
|
|
ea2351e902 | ||
|
|
8effb54c89 | ||
|
|
66c99acb9e | ||
|
|
2e5a326315 | ||
|
|
b5177c608d | ||
|
|
be7d5a2310 | ||
|
|
65444899af | ||
|
|
5230127fde | ||
|
|
405320d8ab | ||
|
|
2f2e193cf9 | ||
|
|
1fc206b9c5 | ||
|
|
44e1a477f2 | ||
|
|
e62302c979 | ||
|
|
51b0fe4596 | ||
|
|
7399aa0c5e | ||
|
|
aa4588f9ba | ||
|
|
4c2e2e0fa3 | ||
|
|
6dea8ddbce | ||
|
|
e6623a6ca8 | ||
|
|
020e485196 | ||
|
|
0c5f535689 | ||
|
|
ae9d0d894a | ||
|
|
14401c30b6 | ||
|
|
fbb93c72d0 | ||
|
|
280f31955a | ||
|
|
5fedcd1f4e | ||
|
|
adeee0bf5c | ||
|
|
84a2b726f5 | ||
|
|
407a60dcc4 | ||
|
|
d31507985b | ||
|
|
0174c9747b | ||
|
|
55b503da5b | ||
|
|
aff4ad0f97 | ||
|
|
50df3acd26 | ||
|
|
e53e1e31de | ||
|
|
70b09446b6 | ||
|
|
3a18d1df8c | ||
|
|
01883f92d9 | ||
|
|
1acdf58a4b | ||
|
|
8acd3d0a72 | ||
|
|
1d1cb867cd | ||
|
|
b46bfaebc1 | ||
|
|
a22c7c1539 | ||
|
|
ea51aa97b7 | ||
|
|
6a6959d041 | ||
|
|
462cede863 | ||
|
|
85c11bbd83 | ||
|
|
77e0564d13 | ||
|
|
3b03d68ac7 | ||
|
|
b57d64c72d | ||
|
|
3b76e0203a | ||
|
|
425670f52a | ||
|
|
9f7313e492 |
23
.github/lock.yml
vendored
Normal file
23
.github/lock.yml
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
# Configuration for Lock (https://github.com/apps/lock)
|
||||
|
||||
# Number of days of inactivity before a closed issue or pull request is locked
|
||||
daysUntilLock: 90
|
||||
|
||||
# Skip issues and pull requests created before a given timestamp. Timestamp must
|
||||
# follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable
|
||||
skipCreatedBefore: false
|
||||
|
||||
# Issues and pull requests with these labels will be ignored. Set to `[]` to disable
|
||||
exemptLabels: []
|
||||
|
||||
# Label to add before locking, such as `outdated`. Set to `false` to disable
|
||||
lockLabel: false
|
||||
|
||||
# Comment to post before locking. Set to `false` to disable
|
||||
lockComment: false
|
||||
|
||||
# Assign `resolved` as the reason for locking. Set to `false` to disable
|
||||
setLockReason: true
|
||||
|
||||
# Limit to only `issues` or `pulls`
|
||||
# only: issues
|
||||
7
.github/stale.yml
vendored
7
.github/stale.yml
vendored
@@ -1,20 +1,27 @@
|
||||
# Configuration for Stale (https://github.com/apps/stale)
|
||||
|
||||
# Number of days of inactivity before an issue becomes stale
|
||||
daysUntilStale: 14
|
||||
|
||||
# Number of days of inactivity before a stale issue is closed
|
||||
daysUntilClose: 7
|
||||
|
||||
# Issues with these labels will never be considered stale
|
||||
exemptLabels:
|
||||
- "status: accepted"
|
||||
- "status: gathering feedback"
|
||||
- "status: blocked"
|
||||
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: wontfix
|
||||
|
||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs. NetBox
|
||||
is governed by a small group of core maintainers which means not all opened
|
||||
issues may receive direct feedback. Please see our [contributing guide](https://github.com/netbox-community/netbox/blob/develop/CONTRIBUTING.md).
|
||||
|
||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||
closeComment: >
|
||||
This issue has been automatically closed due to lack of activity. In an
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -12,7 +12,7 @@
|
||||
fabfile.py
|
||||
*.swp
|
||||
gunicorn_config.py
|
||||
gunicorn.conf
|
||||
gunicorn.py
|
||||
netbox.log
|
||||
netbox.pid
|
||||
.DS_Store
|
||||
|
||||
@@ -16,7 +16,7 @@ For real-time discussion, you can join the #netbox Slack channel on [NetworkToCo
|
||||
|
||||
## Reporting Bugs
|
||||
|
||||
* First, ensure that you've installed the [latest stable version](https://github.com/netbox-community/netbox/releases)
|
||||
* First, ensure that you're running the [latest stable version](https://github.com/netbox-community/netbox/releases)
|
||||
of NetBox. If you're running an older version, it's possible that the bug has
|
||||
already been fixed.
|
||||
|
||||
@@ -28,27 +28,26 @@ up (+1). You might also want to add a comment describing how it's affecting your
|
||||
installation. This will allow us to prioritize bugs based on how many users are
|
||||
affected.
|
||||
|
||||
* If you haven't found an existing issue that describes your suspected bug,
|
||||
please inquire about it on the mailing list. **Do not** file an issue until you
|
||||
have received confirmation that it is in fact a bug. Invalid issues are very
|
||||
distracting and slow the pace at which NetBox is developed.
|
||||
|
||||
* When submitting an issue, please be as descriptive as possible. Be sure to
|
||||
include:
|
||||
provide all information request in the issue template, including:
|
||||
|
||||
* The environment in which NetBox is running
|
||||
* The exact steps that can be taken to reproduce the issue (if applicable)
|
||||
* The exact steps that can be taken to reproduce the issue
|
||||
* Expected and observed behavior
|
||||
* Any error messages generated
|
||||
* Screenshots (if applicable)
|
||||
|
||||
* Please avoid prepending any sort of tag (e.g. "[Bug]") to the issue title.
|
||||
The issue will be reviewed by a moderator after submission and the appropriate
|
||||
The issue will be reviewed by a maintainer after submission and the appropriate
|
||||
labels will be applied for categorization.
|
||||
|
||||
* Keep in mind that we prioritize bugs based on their severity and how much
|
||||
work is required to resolve them. It may take some time for someone to address
|
||||
your issue.
|
||||
|
||||
* For more information on how bug reports are handled, please see our [issue
|
||||
intake policy](https://github.com/netbox-community/netbox/wiki/Issue-Intake-Policy).
|
||||
|
||||
## Feature Requests
|
||||
|
||||
* First, check the GitHub [issues list](https://github.com/netbox-community/netbox/issues)
|
||||
@@ -61,10 +60,10 @@ free to add a comment with any additional justification for the feature.
|
||||
(However, note that comments with no substance other than a "+1" will be
|
||||
deleted. Please use GitHub's reactions feature to indicate your support.)
|
||||
|
||||
* Due to an excessive backlog of feature requests, we are not currently
|
||||
accepting any proposals which substantially extend NetBox's functionality
|
||||
beyond its current feature set. This includes the introduction of any new views
|
||||
or models which have not already been proposed in an existing feature request.
|
||||
* Due to a large backlog of feature requests, we are not currently accepting
|
||||
any proposals which substantially extend NetBox's functionality beyond its
|
||||
current feature set. This includes the introduction of any new views or models
|
||||
which have not already been proposed in an existing feature request.
|
||||
|
||||
* Before filing a new feature request, consider raising your idea on the
|
||||
mailing list first. Feedback you receive there will help validate and shape the
|
||||
@@ -75,8 +74,8 @@ describe the functionality and data model(s) being proposed. The more effort
|
||||
you put into writing a feature request, the better its chance is of being
|
||||
implemented. Overly broad feature requests will be closed.
|
||||
|
||||
* When submitting a feature request on GitHub, be sure to include the
|
||||
following:
|
||||
* When submitting a feature request on GitHub, be sure to include all
|
||||
information requested by the issue template, including:
|
||||
|
||||
* A detailed description of the proposed functionality
|
||||
* A use case for the feature; who would use it and what value it would add
|
||||
@@ -89,6 +88,9 @@ following:
|
||||
title. The issue will be reviewed by a moderator after submission and the
|
||||
appropriate labels will be applied for categorization.
|
||||
|
||||
* For more information on how feature requests are handled, please see our
|
||||
[issue intake policy](https://github.com/netbox-community/netbox/wiki/Issue-Intake-Policy).
|
||||
|
||||
## Submitting Pull Requests
|
||||
|
||||
* Be sure to open an issue **before** starting work on a pull request, and
|
||||
@@ -103,7 +105,7 @@ any work that's already in progress.
|
||||
|
||||
* When submitting a pull request, please be sure to work off of the `develop`
|
||||
branch, rather than `master`. The `develop` branch is used for ongoing
|
||||
development, while `master` is used for tagging new stable releases.
|
||||
development, while `master` is used for tagging stable releases.
|
||||
|
||||
* All code submissions should meet the following criteria (CI will enforce
|
||||
these checks):
|
||||
@@ -122,27 +124,26 @@ reduce noise in the discussion.
|
||||
|
||||
## Issue Lifecycle
|
||||
|
||||
When a correctly formatted issue is submitted it is evaluated by a moderator
|
||||
who may elect to immediately label the issue as accepted in addition to another
|
||||
issue type label. In other cases, the issue may be labeled as "status: gathering feedback"
|
||||
which will often be accompanied by a comment from a moderator asking for further dialog from the community.
|
||||
If an issue is labeled as "status: revisions needed" a moderator has identified a problem with
|
||||
the issue itself and is asking for the submitter himself to update the original post with
|
||||
the requested information. If the original post is not updated in a reasonable amount of time,
|
||||
the issue will be closed as invalid.
|
||||
New issues are handled according to our [issue intake policy](https://github.com/netbox-community/netbox/wiki/Issue-Intake-Policy).
|
||||
Maintainers will assign label(s) and/or close new issues as the policy
|
||||
dictates. This helps ensure a productive development environment and avoid
|
||||
accumulating a large backlog of work.
|
||||
|
||||
The core maintainers group has chosen to make use of the GitHub Stale bot to aid in issue management.
|
||||
The core maintainers group has chosen to make use of GitHub's [Stale bot](https://github.com/apps/stale)
|
||||
to aid in issue management.
|
||||
|
||||
* Issues will be marked as stale after 14 days of no activity.
|
||||
* Then after 7 more days of inactivity, the issue will be closed.
|
||||
* Any issue bearing one of the following labels will be exempt from all Stale bot actions:
|
||||
* Any issue bearing one of the following labels will be exempt from all Stale
|
||||
bot actions:
|
||||
* `status: accepted`
|
||||
* `status: gathering feedback`
|
||||
* `status: blocked`
|
||||
|
||||
It is natural that some new issues get more attention than others. Often this is a metric of an issues's
|
||||
overall usefulness to the project. In other cases in which issues merely get lost in the shuffle,
|
||||
notifications from Stale bot can bring renewed attention to potentially meaningful issues.
|
||||
It is natural that some new issues get more attention than others. Often this
|
||||
is a metric of an issues's overall value to the project. In other cases in
|
||||
which issues merely get lost in the shuffle, notifications from Stale bot can
|
||||
bring renewed attention to potentially meaningful issues.
|
||||
|
||||
## Maintainer Guidance
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
NetBox is an IP address management (IPAM) and data center infrastructure
|
||||
management (DCIM) tool. Initially conceived by the network engineering team at
|
||||
|
||||
@@ -54,6 +54,10 @@ djangorestframework
|
||||
# https://github.com/axnsan12/drf-yasg
|
||||
drf-yasg[validation]
|
||||
|
||||
# Platform-agnostic template rendering engine
|
||||
# https://github.com/pallets/jinja
|
||||
Jinja2
|
||||
|
||||
# Simple markup language for rendering HTML
|
||||
# https://github.com/Python-Markdown/markdown
|
||||
# py-gfm requires Markdown<3.0
|
||||
@@ -79,10 +83,14 @@ py-gfm
|
||||
# https://github.com/Legrandin/pycryptodome
|
||||
pycryptodome
|
||||
|
||||
# YAML rendering library
|
||||
# https://github.com/yaml/pyyaml
|
||||
PyYAML
|
||||
|
||||
# In-memory key/value store used for caching and queuing
|
||||
# https://github.com/andymccurdy/redis-py
|
||||
redis
|
||||
|
||||
# Python Package to write SVG files - used for rack elevations
|
||||
# SVG image rendering (used for rack elevations)
|
||||
# https://github.com/mozman/svgwrite
|
||||
svgwrite
|
||||
|
||||
@@ -71,6 +71,18 @@ The checkbox to commit database changes when executing a script is checked by de
|
||||
commit_default = False
|
||||
```
|
||||
|
||||
## Accessing Request Data
|
||||
|
||||
Details of the current HTTP request (the one being made to execute the script) are available as the instance attribute `self.request`. This can be used to infer, for example, the user executing the script and the client IP address:
|
||||
|
||||
```python
|
||||
username = self.request.user.username
|
||||
ip_address = self.request.META.get('HTTP_X_FORWARDED_FOR') or self.request.META.get('REMOTE_ADDR')
|
||||
self.log_info("Running as user {} (IP: {})...".format(username, ip_address))
|
||||
```
|
||||
|
||||
For a complete list of available request parameters, please see the [Django documentation](https://docs.djangoproject.com/en/stable/ref/request-response/).
|
||||
|
||||
## Reading Data from Files
|
||||
|
||||
The Script class provides two convenience methods for reading data from files:
|
||||
|
||||
@@ -8,6 +8,11 @@ NetBox does not have the ability to generate graphs natively, but this feature a
|
||||
* **Source URL:** The source of the image to be embedded. The associated object will be available as a template variable named `obj`.
|
||||
* **Link URL (optional):** A URL to which the graph will be linked. The associated object will be available as a template variable named `obj`.
|
||||
|
||||
Graph names and links can be rendered using the Django or Jinja2 template languages.
|
||||
|
||||
!!! warning
|
||||
Support for the Django templating language will be removed in NetBox v2.8. Jinja2 is recommended.
|
||||
|
||||
## Examples
|
||||
|
||||
You only need to define one graph object for each graph you want to include when viewing an object. For example, if you want to include a graph of traffic through an interface over the past five minutes, your graph source might looks like this:
|
||||
|
||||
65
docs/additional-features/napalm.md
Normal file
65
docs/additional-features/napalm.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# NAPALM
|
||||
|
||||
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.
|
||||
|
||||
!!! info
|
||||
To enable the integration, the NAPALM library must be installed. See [installation steps](../../installation/2-netbox/#napalm-automation-optional) for more information.
|
||||
|
||||
```
|
||||
GET /api/dcim/devices/1/napalm/?method=get_environment
|
||||
|
||||
{
|
||||
"get_environment": {
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
||||
By default, the [`NAPALM_USERNAME`](../../configuration/optional-settings/#napalm_username) and [`NAPALM_PASSWORD`](../../configuration/optional-settings/#napalm_password) are used for NAPALM authentication. They can be overridden for an individual API call through the `X-NAPALM-Username` and `X-NAPALM-Password` headers.
|
||||
|
||||
```
|
||||
$ curl "http://localhost/api/dcim/devices/1/napalm/?method=get_environment" \
|
||||
-H "Authorization: Token f4b378553dacfcfd44c5a0b9ae49b57e29c552b5" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Accept: application/json; indent=4" \
|
||||
-H "X-NAPALM-Username: foo" \
|
||||
-H "X-NAPALM-Password: bar"
|
||||
```
|
||||
|
||||
## Method Support
|
||||
|
||||
The list of supported NAPALM methods depends on the [NAPALM driver](https://napalm.readthedocs.io/en/latest/support/index.html#general-support-matrix) configured for the platform of a device. NetBox only supports [get](https://napalm.readthedocs.io/en/latest/support/index.html#getters-support-matrix) methods.
|
||||
|
||||
## Multiple Methods
|
||||
|
||||
More than one method in an API call can be invoked by adding multiple `method` parameters. For example:
|
||||
|
||||
```
|
||||
GET /api/dcim/devices/1/napalm/?method=get_ntp_servers&method=get_ntp_peers
|
||||
|
||||
{
|
||||
"get_ntp_servers": {
|
||||
...
|
||||
},
|
||||
"get_ntp_peers": {
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Optional Arguments
|
||||
|
||||
The behavior of NAPALM drivers can be adjusted according to the [optional arguments](https://napalm.readthedocs.io/en/latest/support/index.html#optional-arguments). NetBox exposes those arguments using headers prefixed with `X-NAPALM-`.
|
||||
|
||||
|
||||
For instance, the SSH port is changed to 2222 in this API call:
|
||||
|
||||
```
|
||||
$ curl "http://localhost/api/dcim/devices/1/napalm/?method=get_environment" \
|
||||
-H "Authorization: Token f4b378553dacfcfd44c5a0b9ae49b57e29c552b5" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Accept: application/json; indent=4" \
|
||||
-H "X-NAPALM-port: 2222"
|
||||
```
|
||||
@@ -40,6 +40,8 @@ Racks can be arranged into groups. As with sites, how you choose to designate ra
|
||||
|
||||
Each rack group must be assigned to a parent site. Hierarchical recursion of rack groups is not currently supported.
|
||||
|
||||
The name and facility ID of each rack within a group must be unique. (Racks not assigned to the same rack group may have identical names and/or facility IDs.)
|
||||
|
||||
## Rack Roles
|
||||
|
||||
Each rack can optionally be assigned a functional role. For example, you might designate a rack for compute or storage resources, or to house colocated customer devices. Rack roles are fully customizable.
|
||||
|
||||
@@ -69,6 +69,14 @@ If the new field will be included in the object list view, add a column to the m
|
||||
|
||||
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.
|
||||
|
||||
### 11. Adjust API and model tests
|
||||
### 11. Create/extend test cases
|
||||
|
||||
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.
|
||||
Create or extend the relevant test cases to verify that the new field and any accompanying validation logic perform as expected. This is especially important for relational fields. NetBox incorporates various test suites, including:
|
||||
|
||||
* API serializer/view tests
|
||||
* Filter tests
|
||||
* Form tests
|
||||
* Model tests
|
||||
* View tests
|
||||
|
||||
Be diligent to ensure all of the relevant test suites are adapted or extended as necessary to test any new functionality.
|
||||
|
||||
@@ -33,6 +33,10 @@ Update the following static libraries to their most recent stable release:
|
||||
* jQuery
|
||||
* jQuery UI
|
||||
|
||||
## Squash Schema Migrations
|
||||
|
||||
Database schema migrations should be squashed for each new minor release. See the [squashing guide](squashing-migrations.md) for the detailed process.
|
||||
|
||||
## Create a new Release Notes Page
|
||||
|
||||
Create a file at `/docs/release-notes/X.Y.md` to establish the release notes for the new release. Add the file to the table of contents within `mkdocs.yml`.
|
||||
|
||||
168
docs/development/squashing-migrations.md
Normal file
168
docs/development/squashing-migrations.md
Normal file
@@ -0,0 +1,168 @@
|
||||
# Squashing Database Schema Migrations
|
||||
|
||||
## What are Squashed Migrations?
|
||||
|
||||
The Django framework on which NetBox is built utilizes [migration files](https://docs.djangoproject.com/en/stable/topics/migrations/) to keep track of changes to the PostgreSQL database schema. Each time a model is altered, the resulting schema change is captured in a migration file, which can then be applied to effect the new schema.
|
||||
|
||||
As changes are made over time, more and more migration files are created. Although not necessarily problematic, it can be beneficial to merge and compress these files occasionally to reduce the total number of migrations that need to be applied upon installation of NetBox. This merging process is called _squashing_ in Django vernacular, and results in two parallel migration paths: individual and squashed.
|
||||
|
||||
Below is an example showing both individual and squashed migration files within an app:
|
||||
|
||||
| Individual | Squashed |
|
||||
|------------|----------|
|
||||
| 0001_initial | 0001_initial_squashed_0004_add_field |
|
||||
| 0002_alter_field | . |
|
||||
| 0003_remove_field | . |
|
||||
| 0004_add_field | . |
|
||||
| 0005_another_field | 0005_another_field |
|
||||
|
||||
In the example above, a new installation can leverage the squashed migrations to apply only two migrations:
|
||||
|
||||
* `0001_initial_squashed_0004_add_field`
|
||||
* `0005_another_field`
|
||||
|
||||
This is because the squash file contains all of the operations performed by files `0001` through `0004`.
|
||||
|
||||
However, an existing installation that has already applied some of the individual migrations contained within the squash file must continue applying individual migrations. For instance, an installation which currently has up to `0002_alter_field` applied must apply the following migrations to become current:
|
||||
|
||||
* `0003_remove_field`
|
||||
* `0004_add_field`
|
||||
* `0005_another_field`
|
||||
|
||||
Squashed migrations are opportunistic: They are used only if applicable to the current environment. Django will fall back to using individual migrations if the squashed migrations do not agree with the current database schema at any point.
|
||||
|
||||
## Squashing Migrations
|
||||
|
||||
During every minor (i.e. 2.x) release, migrations should be squashed to help simplify the migration process for new installations. The process below describes how to squash migrations efficiently and with minimal room for error.
|
||||
|
||||
### 1. Create a New Branch
|
||||
|
||||
Create a new branch off of the `develop-2.x` branch. (Migrations should be squashed _only_ in preparation for a new minor release.)
|
||||
|
||||
```
|
||||
git checkout -B squash-migrations
|
||||
```
|
||||
|
||||
### 2. Delete Existing Squash Files
|
||||
|
||||
Delete the most recent squash file within each NetBox app. This allows us to extend squash files where the opportunity exists. For example, we might be able to replace `0005_to_0008` with `0005_to_0011`.
|
||||
|
||||
### 3. Generate the Current Migration Plan
|
||||
|
||||
Use Django's `showmigrations` utility to display the order in which all migrations would be applied for a new installation.
|
||||
|
||||
```
|
||||
manage.py showmigrations --plan
|
||||
```
|
||||
|
||||
From the resulting output, delete all lines which reference an external migration. Any migrations imposed by Django itself on an external package are not relevant.
|
||||
|
||||
### 4. Create Squash Files
|
||||
|
||||
Begin iterating through the migration plan, looking for successive sets of migrations within an app. These are candidates for squashing. For example:
|
||||
|
||||
```
|
||||
[X] extras.0014_configcontexts
|
||||
[X] extras.0015_remove_useraction
|
||||
[X] extras.0016_exporttemplate_add_cable
|
||||
[X] extras.0017_exporttemplate_mime_type_length
|
||||
[ ] extras.0018_exporttemplate_add_jinja2
|
||||
[ ] extras.0019_tag_taggeditem
|
||||
[X] dcim.0062_interface_mtu
|
||||
[X] dcim.0063_device_local_context_data
|
||||
[X] dcim.0064_remove_platform_rpc_client
|
||||
[ ] dcim.0065_front_rear_ports
|
||||
[X] circuits.0001_initial_squashed_0010_circuit_status
|
||||
[ ] dcim.0066_cables
|
||||
...
|
||||
```
|
||||
|
||||
Migrations `0014` through `0019` in `extras` can be squashed, as can migrations `0062` through `0065` in `dcim`. Migration `0066` cannot be included in the same squash file, because the `circuits` migration must be applied before it. (Note that whether or not each migration is currently applied to the database does not matter.)
|
||||
|
||||
Squash files are created using Django's `squashmigrations` utility:
|
||||
|
||||
```
|
||||
manage.py squashmigrations <app> <start> <end>
|
||||
```
|
||||
|
||||
For example, our first step in the example would be to run `manage.py squashmigrations extras 0014 0019`.
|
||||
|
||||
!!! note
|
||||
Specifying a migration file's numeric index is enough to uniquely identify it within an app. There is no need to specify the full filename.
|
||||
|
||||
This will create a new squash file within the app's `migrations` directory, named as a concatenation of its beginning and ending migration. Some manual editing is necessary for each new squash file for housekeeping purposes:
|
||||
|
||||
* Remove the "automatically generated" comment at top (to indicate that a human has reviewed the file).
|
||||
* Reorder `import` statements as necessary per PEP8.
|
||||
* It may be necessary to copy over custom functions from the original migration files (this will be indicated by a comment near the top of the squash file). It is safe to remove any functions that exist solely to accomodate reverse migrations (which we no longer support).
|
||||
|
||||
Repeat this process for each candidate set of migrations until you reach the end of the migration plan.
|
||||
|
||||
### 5. Check for Missing Migrations
|
||||
|
||||
If everything went well, at this point we should have a completed squashed path. Perform a dry run to check for any missing migrations:
|
||||
|
||||
```
|
||||
manage.py migrate --dry-run
|
||||
```
|
||||
|
||||
### 5. Run Migrations
|
||||
|
||||
Next, we'll apply the entire migration path to an empty database. Begin by dropping and creating your development database.
|
||||
|
||||
!!! warning
|
||||
Obviously, first back up any data you don't want to lose.
|
||||
|
||||
```
|
||||
sudo -u postgres psql -c 'drop database netbox'
|
||||
sudo -u postgres psql -c 'create database netbox'
|
||||
```
|
||||
|
||||
Apply the migrations with the `migrate` management command. It is not necessary to specify a particular migration path; Django will detect and use the squashed migrations automatically. You can verify the exact migrations being applied by enabling verboes output with `-v 2`.
|
||||
|
||||
```
|
||||
manage.py migrate -v 2
|
||||
```
|
||||
|
||||
### 6. Commit the New Migrations
|
||||
|
||||
If everything is successful to this point, commit your changes to the `squash-migrations` branch.
|
||||
|
||||
### 7. Validate Resulting Schema
|
||||
|
||||
To ensure our new squashed migrations do not result in a deviation from the original schema, we'll compare the two. With the new migration file safely commit, check out the `develop-2.x` branch, which still contains only the individual migrations.
|
||||
|
||||
```
|
||||
git checkout develop-2.x
|
||||
```
|
||||
|
||||
Temporarily install the [django-extensions](https://django-extensions.readthedocs.io/) package, which provides the `sqldiff utility`:
|
||||
|
||||
```
|
||||
pip install django-extensions
|
||||
```
|
||||
|
||||
Also add `django_extensions` to `INSTALLED_APPS` in `netbox/netbox/settings.py`.
|
||||
|
||||
At this point, our database schema has been defined by using the squashed migrations. We can run `sqldiff` to see if it differs any from what the current (non-squashed) migrations would generate. `sqldiff` accepts a list of apps against which to run:
|
||||
|
||||
```
|
||||
manage.py sqldiff circuits dcim extras ipam secrets tenancy users virtualization
|
||||
```
|
||||
|
||||
It is safe to ignore errors indicating an "unknown database type" for the following fields:
|
||||
|
||||
* `dcim_interface.mac_address`
|
||||
* `ipam_aggregate.prefix`
|
||||
* `ipam_prefix.prefix`
|
||||
|
||||
It is also safe to ignore the message "Table missing: extras_script".
|
||||
|
||||
Resolve any differences by correcting migration files in the `squash-migrations` branch.
|
||||
|
||||
!!! warning
|
||||
Don't forget to remove `django_extension` from `INSTALLED_APPS` before committing your changes.
|
||||
|
||||
### 8. Merge the Squashed Migrations
|
||||
|
||||
Once all squashed migrations have been validated and all tests run successfully, merge the `squash-migrations` branch into `develop-2.x`. This completes the squashing process.
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
# What is NetBox?
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ This section of the documentation discusses installing and configuring the NetBo
|
||||
# yum install -y epel-release
|
||||
# yum install -y gcc python36 python36-devel python36-setuptools libxml2-devel libxslt-devel libffi-devel openssl-devel redhat-rpm-config redis
|
||||
# easy_install-3.6 pip
|
||||
# ln -s /usr/bin/python36 /usr/bin/python3
|
||||
# ln -s /usr/bin/python3.6 /usr/bin/python3
|
||||
```
|
||||
|
||||
You may opt to install NetBox either from a numbered release or by cloning the master branch of its repository on GitHub.
|
||||
|
||||
@@ -107,7 +107,7 @@ Install gunicorn:
|
||||
# pip3 install gunicorn
|
||||
```
|
||||
|
||||
Copy `contrib/gunicorn.conf` to `/opt/netbox/gunicorn.conf`. We make a copy of this file to ensure that any changes to it do not get overwritten by a future upgrade.
|
||||
Copy `contrib/gunicorn.py` to `/opt/netbox/gunicorn.py`. We make a copy of this file to ensure that any changes to it do not get overwritten by a future upgrade.
|
||||
|
||||
```no-highlight
|
||||
# cp contrib/gunicorn.py /opt/netbox/gunicorn.py
|
||||
|
||||
@@ -80,6 +80,7 @@ AUTH_LDAP_USER_ATTR_MAP = {
|
||||
```
|
||||
|
||||
# User Groups for Permissions
|
||||
|
||||
!!! info
|
||||
When using Microsoft Active Directory, support for nested groups can be activated by using `NestedGroupOfNamesType()` instead of `GroupOfNamesType()` for `AUTH_LDAP_GROUP_TYPE`. You will also need to modify the import line to use `NestedGroupOfNamesType` instead of `GroupOfNamesType` .
|
||||
|
||||
@@ -117,6 +118,9 @@ AUTH_LDAP_GROUP_CACHE_TIMEOUT = 3600
|
||||
* `is_staff` - Users mapped to this group are enabled for access to the administration tools; this is the equivalent of checking the "staff status" box on a manually created user. This doesn't grant any specific permissions.
|
||||
* `is_superuser` - Users mapped to this group will be granted superuser status. Superusers are implicitly granted all permissions.
|
||||
|
||||
!!! warning
|
||||
Authentication will fail if the groups (the distinguished names) do not exist in the LDAP directory.
|
||||
|
||||
# Troubleshooting LDAP
|
||||
|
||||
`supervisorctl restart netbox` restarts the Netbox service, and initiates any changes made to `ldap_config.py`. If there are syntax errors present, the NetBox process will not spawn an instance, and errors should be logged to `/var/log/supervisor/`.
|
||||
|
||||
21
docs/netbox_logo.svg
Normal file
21
docs/netbox_logo.svg
Normal file
@@ -0,0 +1,21 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1100 320">
|
||||
<g fill="#9cc8f8" stroke="#9cc8f8">
|
||||
<circle cx="37" cy="284" r="23"/>
|
||||
<circle cx="101" cy="37" r="23"/>
|
||||
<circle cx="101" cy="220" r="23"/>
|
||||
<circle cx="284" cy="220" r="23"/>
|
||||
<rect x="93" y="37" width="16" height="180"/>
|
||||
<rect x="101" y="212" width="180" height="16"/>
|
||||
<rect x="93" y="212" width="16" height="90" transform="rotate(45 101 220)"/>
|
||||
</g>
|
||||
<g fill="#1685fc" stroke="#1685fc">
|
||||
<circle cx="284" cy="37" r="23"/>
|
||||
<circle cx="37" cy="101" r="23"/>
|
||||
<circle cx="220" cy="101" r="23"/>
|
||||
<circle cx="220" cy="284" r="23"/>
|
||||
<rect x="37" y="93" width="180" height="16"/>
|
||||
<rect x="212" y="101" width="16" height="180"/>
|
||||
<rect x="212" y="93" width="16" height="90" transform="rotate(225 220 101)"/>
|
||||
<path transform="translate(380, 8)" d="M13.60 200L13.60 104L36.40 104L36.40 119.40L36.80 119.40Q40.20 112.20 47.20 106.90Q54.20 101.60 66.20 101.60L66.20 101.60Q75.80 101.60 82.50 104.80Q89.20 108 93.40 113.20Q97.60 118.40 99.40 125.20Q101.20 132 101.20 139.40L101.20 139.40L101.20 200L77.20 200L77.20 151.40Q77.20 147.40 76.80 142.50Q76.40 137.60 74.70 133.30Q73 129 69.40 126.10Q65.80 123.20 59.60 123.20L59.60 123.20Q53.60 123.20 49.50 125.20Q45.40 127.20 42.70 130.60Q40 134 38.80 138.40Q37.60 142.80 37.60 147.60L37.60 147.60L37.60 200L13.60 200ZM224.80 160.40L151.60 160.40Q152.80 171.20 160 177.20Q167.20 183.20 177.40 183.20L177.40 183.20Q186.40 183.20 192.50 179.50Q198.60 175.80 203.20 170.20L203.20 170.20L220.40 183.20Q212 193.60 201.60 198Q191.20 202.40 179.80 202.40L179.80 202.40Q169 202.40 159.40 198.80Q149.80 195.20 142.80 188.60Q135.80 182 131.70 172.70Q127.60 163.40 127.60 152L127.60 152Q127.60 140.60 131.70 131.30Q135.80 122 142.80 115.40Q149.80 108.80 159.40 105.20Q169 101.60 179.80 101.60L179.80 101.60Q189.80 101.60 198.10 105.10Q206.40 108.60 212.30 115.20Q218.20 121.80 221.50 131.50Q224.80 141.20 224.80 153.80L224.80 153.80L224.80 160.40ZM151.60 142.40L200.80 142.40Q200.60 131.80 194.20 125.70Q187.80 119.60 176.40 119.60L176.40 119.60Q165.60 119.60 159.30 125.80Q153 132 151.60 142.40L151.60 142.40ZM259.80 124.40L240.00 124.40L240.00 104L259.80 104L259.80 76.20L283.80 76.20L283.80 104L310.20 104L310.20 124.40L283.80 124.40L283.80 166.40Q283.80 173.60 286.50 177.80Q289.20 182 297.20 182L297.20 182Q300.40 182 304.20 181.30Q308 180.60 310.20 179L310.20 179L310.20 199.20Q306.40 201 300.90 201.70Q295.40 202.40 291.20 202.40L291.20 202.40Q281.60 202.40 275.50 200.30Q269.40 198.20 265.90 193.90Q262.40 189.60 261.10 183.20Q259.80 176.80 259.80 168.40L259.80 168.40L259.80 124.40ZM333.20 200L333.20 48.80L357.20 48.80L357.20 116.20L357.80 116.20Q359.60 113.80 362.40 111.30Q365.20 108.80 369.20 106.60Q373.20 104.40 378.40 103Q383.60 101.60 390.40 101.60L390.40 101.60Q400.60 101.60 409.20 105.50Q417.80 109.40 423.90 116.20Q430 123 433.40 132.20Q436.80 141.40 436.80 152L436.80 152Q436.80 162.60 433.60 171.80Q430.40 181 424.20 187.80Q418 194.60 409.20 198.50Q400.40 202.40 389.40 202.40L389.40 202.40Q379.20 202.40 370.40 198.40Q361.60 194.40 356.40 185.60L356.40 185.60L356 185.60L356 200L333.20 200ZM412.80 152L412.80 152Q412.80 146.40 410.90 141.20Q409 136 405.30 132Q401.60 128 396.40 125.60Q391.20 123.20 384.60 123.20L384.60 123.20Q378 123.20 372.80 125.60Q367.60 128 363.90 132Q360.20 136 358.30 141.20Q356.40 146.40 356.40 152L356.40 152Q356.40 157.60 358.30 162.80Q360.20 168 363.90 172Q367.60 176 372.80 178.40Q378 180.80 384.60 180.80L384.60 180.80Q391.20 180.80 396.40 178.40Q401.60 176 405.30 172Q409 168 410.90 162.80Q412.80 157.60 412.80 152ZM458.40 152L458.40 152Q458.40 140.60 462.50 131.30Q466.60 122 473.60 115.40Q480.60 108.80 490.20 105.20Q499.80 101.60 510.60 101.60L510.60 101.60Q521.40 101.60 531 105.20Q540.60 108.80 547.60 115.40Q554.60 122 558.70 131.30Q562.80 140.60 562.80 152L562.80 152Q562.80 163.40 558.70 172.70Q554.60 182 547.60 188.60Q540.60 195.20 531 198.80Q521.40 202.40 510.60 202.40L510.60 202.40Q499.80 202.40 490.20 198.80Q480.60 195.20 473.60 188.60Q466.60 182 462.50 172.70Q458.40 163.40 458.40 152ZM482.40 152L482.40 152Q482.40 157.60 484.30 162.80Q486.20 168 489.90 172Q493.60 176 498.80 178.40Q504 180.80 510.60 180.80L510.60 180.80Q517.20 180.80 522.40 178.40Q527.60 176 531.30 172Q535 168 536.90 162.80Q538.80 157.60 538.80 152L538.80 152Q538.80 146.40 536.90 141.20Q535 136 531.30 132Q527.60 128 522.40 125.60Q517.20 123.20 510.60 123.20L510.60 123.20Q504 123.20 498.80 125.60Q493.60 128 489.90 132Q486.20 136 484.30 141.20Q482.40 146.40 482.40 152ZM575.40 200L614 148.40L580.80 104L610 104L629.20 132.80L650 104L677.40 104L644.60 148.40L683.20 200L654 200L629 165.60L603.80 200L575.40 200Z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.6 KiB |
@@ -1,3 +1,92 @@
|
||||
# v2.6.12 (2020-01-13)
|
||||
|
||||
## Enhancements
|
||||
|
||||
* [#1982](https://github.com/netbox-community/netbox/issues/1982) - Improved NAPALM method documentation in Swagger (OpenAPI)
|
||||
* [#2050](https://github.com/netbox-community/netbox/issues/2050) - Preview image attachments when hovering over the link
|
||||
* [#2113](https://github.com/netbox-community/netbox/issues/2113) - Allow NAPALM driver settings to be changed with request headers
|
||||
* [#2598](https://github.com/netbox-community/netbox/issues/2598) - Toggle the display of child prefixes/IP addresses
|
||||
* [#3009](https://github.com/netbox-community/netbox/issues/3009) - Search by description when assigning IP address to interfaces
|
||||
* [#3021](https://github.com/netbox-community/netbox/issues/3021) - Add `tenant` filter field for cables
|
||||
* [#3090](https://github.com/netbox-community/netbox/issues/3090) - Enable filtering of interfaces by name on the device view
|
||||
* [#3187](https://github.com/netbox-community/netbox/issues/3187) - Add rack selection field to rack elevations view
|
||||
* [#3393](https://github.com/netbox-community/netbox/issues/3393) - Paginate assigned circuits at the provider details view
|
||||
* [#3440](https://github.com/netbox-community/netbox/issues/3440) - Add total path length to cable trace
|
||||
* [#3491](https://github.com/netbox-community/netbox/issues/3491) - Include content of response on webhook error
|
||||
* [#3623](https://github.com/netbox-community/netbox/issues/3623) - Enable word expansion during interface creation
|
||||
* [#3668](https://github.com/netbox-community/netbox/issues/3668) - Enable searching by DNS name when assigning IP address
|
||||
* [#3851](https://github.com/netbox-community/netbox/issues/3851) - Allow passing initial data to custom script forms
|
||||
* [#3891](https://github.com/netbox-community/netbox/issues/3891) - Add `local_context_data` filter for virtual machines
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
* [#3589](https://github.com/netbox-community/netbox/issues/3589) - Fix validation on tagged VLANs of an interface
|
||||
* [#3849](https://github.com/netbox-community/netbox/issues/3849) - Fix ordering of models when dumping data to JSON
|
||||
* [#3853](https://github.com/netbox-community/netbox/issues/3853) - Fix device role link on config context view
|
||||
* [#3856](https://github.com/netbox-community/netbox/issues/3856) - Allow filtering VM interfaces by multiple MAC addresses
|
||||
* [#3857](https://github.com/netbox-community/netbox/issues/3857) - Fix rendering of grouped custom links
|
||||
* [#3862](https://github.com/netbox-community/netbox/issues/3862) - Allow filtering device components by multiple device names
|
||||
* [#3864](https://github.com/netbox-community/netbox/issues/3864) - Disallow /0 masks for prefixes and IP addresses
|
||||
* [#3872](https://github.com/netbox-community/netbox/issues/3872) - Paginate related IPs on the IP address view
|
||||
* [#3876](https://github.com/netbox-community/netbox/issues/3876) - Fix minimum/maximum value rendering for site ASN field
|
||||
* [#3882](https://github.com/netbox-community/netbox/issues/3882) - Fix filtering of devices by rack group
|
||||
* [#3898](https://github.com/netbox-community/netbox/issues/3898) - Fix references to deleted cables without a label
|
||||
* [#3905](https://github.com/netbox-community/netbox/issues/3905) - Fix divide-by-zero on power feeds with low power values
|
||||
|
||||
---
|
||||
|
||||
# v2.6.11 (2020-01-03)
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
* [#3831](https://github.com/netbox-community/netbox/issues/3831) - Fix API-driven filter field rendering (#3812 regression)
|
||||
* [#3833](https://github.com/netbox-community/netbox/issues/3833) - Add missing region filters for multiple objects
|
||||
|
||||
---
|
||||
|
||||
# v2.6.10 (2020-01-02)
|
||||
|
||||
## Enhancements
|
||||
|
||||
* [#2233](https://github.com/netbox-community/netbox/issues/2233) - Add ability to move inventory items between devices
|
||||
* [#2892](https://github.com/netbox-community/netbox/issues/2892) - Extend admin UI to allow deleting old report results
|
||||
* [#3062](https://github.com/netbox-community/netbox/issues/3062) - Add `assigned_to_interface` filter for IP addresses
|
||||
* [#3461](https://github.com/netbox-community/netbox/issues/3461) - Fail gracefully on custom link rendering exception
|
||||
* [#3705](https://github.com/netbox-community/netbox/issues/3705) - Provide request context when executing custom scripts
|
||||
* [#3762](https://github.com/netbox-community/netbox/issues/3762) - Add date/time picker widgets
|
||||
* [#3788](https://github.com/netbox-community/netbox/issues/3788) - Enable partial search for inventory items
|
||||
* [#3812](https://github.com/netbox-community/netbox/issues/3812) - Optimize size of pages containing a dynamic selection field
|
||||
* [#3827](https://github.com/netbox-community/netbox/issues/3827) - Allow filtering console/power/interface connections by device ID
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
* [#3106](https://github.com/netbox-community/netbox/issues/3106) - Restrict queryset of chained fields when form validation fails
|
||||
* [#3695](https://github.com/netbox-community/netbox/issues/3695) - Include A/Z termination sites for circuits in global search
|
||||
* [#3712](https://github.com/netbox-community/netbox/issues/3712) - Scrolling to target (hash) did not account for the header size
|
||||
* [#3780](https://github.com/netbox-community/netbox/issues/3780) - Fix AttributeError exception in API docs
|
||||
* [#3809](https://github.com/netbox-community/netbox/issues/3809) - Filter platform by manufacturer when editing devices
|
||||
* [#3811](https://github.com/netbox-community/netbox/issues/3811) - Fix filtering of racks by group on device list
|
||||
* [#3822](https://github.com/netbox-community/netbox/issues/3822) - Fix exception when editing a device bay (regression from #3596)
|
||||
|
||||
---
|
||||
|
||||
# v2.6.9 (2019-12-16)
|
||||
|
||||
## Enhancements
|
||||
|
||||
* [#3152](https://github.com/netbox-community/netbox/issues/3152) - Include direct link to rack elevations on site view
|
||||
* [#3441](https://github.com/netbox-community/netbox/issues/3441) - Move virtual machine results near devices in global search
|
||||
* [#3761](https://github.com/netbox-community/netbox/issues/3761) - Added copy button for API tokens
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
* [#2170](https://github.com/netbox-community/netbox/issues/2170) - Prevent the deletion of a virtual chassis when a cross-member LAG is present
|
||||
* [#2358](https://github.com/netbox-community/netbox/issues/2358) - Respect custom field default values when creating objects via the REST API
|
||||
* [#3749](https://github.com/netbox-community/netbox/issues/3749) - Fix exception on password change page for local users
|
||||
* [#3757](https://github.com/netbox-community/netbox/issues/3757) - Fix unable to assign IP to interface
|
||||
|
||||
---
|
||||
|
||||
# v2.6.8 (2019-12-10)
|
||||
|
||||
## Enhancements
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
# v2.7.0 (FUTURE)
|
||||
# v2.7.0 (2020-01-16)
|
||||
|
||||
**Note:** This release completely removes the topology map feature ([#2745](https://github.com/netbox-community/netbox/issues/2745)).
|
||||
|
||||
**Note:** NetBox v2.7 is the last major release that will support Python 3.5. Beginning with NetBox v2.8, Python 3.6 or
|
||||
higher will be required.
|
||||
@@ -7,7 +9,7 @@ higher will be required.
|
||||
|
||||
### Enhanced Device Type Import ([#451](https://github.com/netbox-community/netbox/issues/451))
|
||||
|
||||
NetBox now supports the import of device types and related component templates using a definition written in YAML or
|
||||
NetBox now supports the import of device types and related component templates using definitions written in YAML or
|
||||
JSON. For example, the following will create a new device type with four network interfaces, two power ports, and a
|
||||
console port:
|
||||
|
||||
@@ -32,14 +34,13 @@ console-ports:
|
||||
- name: Console
|
||||
```
|
||||
|
||||
This new functionality replaces the existing CSV-based import form, which did not allow for component template import.
|
||||
This new functionality replaces the old CSV-based import form, which did not allow for bulk import of component
|
||||
templates.
|
||||
|
||||
### Bulk Import of Device Components ([#822](https://github.com/netbox-community/netbox/issues/822))
|
||||
|
||||
NetBox now supports the bulk import of device components such as console ports, power ports, and interfaces across
|
||||
multiple devices. Device components can be imported in CSV-format.
|
||||
|
||||
Here's an example bulk import of interfaces to several devices:
|
||||
Device components such as console ports, power ports, and interfaces can now be imported in bulk to multiple devices in
|
||||
CSV format. Here's an example showing the bulk import of interfaces to several devices:
|
||||
|
||||
```
|
||||
device,name,type
|
||||
@@ -49,6 +50,8 @@ Switch2,Vlan100,Virtual
|
||||
Switch2,Vlan200,Virtual
|
||||
```
|
||||
|
||||
The import form for each type of device component is available under the "Devices" item in the navigation menu.
|
||||
|
||||
### External File Storage ([#1814](https://github.com/netbox-community/netbox/issues/1814))
|
||||
|
||||
In prior releases, the only option for storing uploaded files (e.g. image attachments) was to save them to the local
|
||||
@@ -58,13 +61,13 @@ filesystem on the NetBox server. This release introduces support for several rem
|
||||
* Amazon S3
|
||||
* ApacheLibcloud
|
||||
* Azure Storage
|
||||
* DigitalOcean Spaces
|
||||
* netbox-community Spaces
|
||||
* Dropbox
|
||||
* FTP
|
||||
* Google Cloud Storage
|
||||
* SFTP
|
||||
|
||||
To enable remote file storage, first install `django-storages`:
|
||||
To enable remote file storage, first install the `django-storages` package:
|
||||
|
||||
```
|
||||
pip install django-storages
|
||||
@@ -85,15 +88,13 @@ STORAGE_CONFIG = {
|
||||
|
||||
Thanks to [@steffann](https://github.com/steffann) for contributing this work!
|
||||
|
||||
## Changes
|
||||
|
||||
### Rack Elevations Rendered via SVG ([#2248](https://github.com/netbox-community/netbox/issues/2248))
|
||||
|
||||
NetBox v2.7 introduces a new method of rendering rack elevations as an
|
||||
[SVG](https://en.wikipedia.org/wiki/Scalable_Vector_Graphics) via a REST API endpoint. This replaces the prior method of
|
||||
rendering elevations using pure HTML which was cumbersome and had several shortcomings. Allowing elevations to be
|
||||
rendered as an SVG image in the API allows users to retrieve and make use of the drawings in their own tooling. This
|
||||
also opens the door to other feature requests related to rack elevations in the NetBox backlog.
|
||||
[SVG image](https://en.wikipedia.org/wiki/Scalable_Vector_Graphics) via a REST API endpoint. This replaces the prior
|
||||
method of rendering elevations using pure HTML and CSS, which was cumbersome and had several shortcomings. Rendering
|
||||
rack elevations as SVG images via the REST API allows users to retrieve and make use of the drawings in their own
|
||||
tooling. This also opens the door to other feature requests related to rack elevations in the NetBox backlog.
|
||||
|
||||
This feature implements a new REST API endpoint:
|
||||
|
||||
@@ -102,28 +103,13 @@ This feature implements a new REST API endpoint:
|
||||
```
|
||||
|
||||
By default, this endpoint returns a paginated JSON response representing each rack unit in the given elevation. This is
|
||||
the same response returned by the rack units detail endpoint and for this reason the rack units endpoint has been
|
||||
deprecated and will be removed in v2.8 (see [#3753](https://github.com/netbox-community/netbox/issues/3753)):
|
||||
the same response returned by the existing rack units detail endpoint at `/api/dcim/racks/<id>/units/`, which will be
|
||||
removed in v2.8 (see [#3753](https://github.com/netbox-community/netbox/issues/3753)).
|
||||
|
||||
```
|
||||
/api/dcim/racks/<id>/units/
|
||||
```
|
||||
|
||||
In order to render the elevation as an SVG, include the `render=svg` query parameter in the request. You may also
|
||||
control the width of the elevation drawing in pixels with `unit_width=<width in pixels>` and the height of each rack
|
||||
unit with `unit_height=<height in pixels>`. The `unit_width` defaults to `230` and the `unit_height` default to `20`
|
||||
which produces elevations the same size as those that appear in the NetBox Web UI. The query parameter `face` is used to
|
||||
request either the `front` or `rear` of the elevation and defaults to `front`.
|
||||
|
||||
Here is an example of the request url for an SVG rendering using the default parameters to render the front of the
|
||||
elevation:
|
||||
|
||||
```
|
||||
/api/dcim/racks/<id>/elevation/?render=svg
|
||||
```
|
||||
|
||||
Here is an example of the request url for an SVG rendering of the rear of the elevation having a width of 300 pixels and
|
||||
per unit height of 35 pixels:
|
||||
To render the elevation as an SVG image, include the `render=svg` query parameter in the request. You may also control
|
||||
the width and height of the elevation drawing (in pixels) by passing the `unit_width` and `unit_height` parameters. (The
|
||||
default values for these parameters are 230 and 20, respectively.) Additionally, the `face` parameter may be used to
|
||||
request either the `front` or `rear` of the elevation. Below is in example request:
|
||||
|
||||
```
|
||||
/api/dcim/racks/<id>/elevation/?render=svg&face=rear&unit_width=300&unit_height=35
|
||||
@@ -131,18 +117,28 @@ per unit height of 35 pixels:
|
||||
|
||||
Thanks to [@hellerve](https://github.com/hellerve) for doing the heavy lifting on this!
|
||||
|
||||
## Changes
|
||||
|
||||
### Topology Maps Removed ([#2745](https://github.com/netbox-community/netbox/issues/2745))
|
||||
|
||||
The topology maps feature has been removed to help focus NetBox development efforts.
|
||||
The topology maps feature has been removed to help focus NetBox development efforts. Please replicate any required data
|
||||
to another source before upgrading NetBox to v2.7, as any existing topology maps will be deleted.
|
||||
|
||||
### Supervisor Replaced with systemd ([#2902](https://github.com/netbox-community/netbox/issues/2902))
|
||||
|
||||
The NetBox [installation documentation](https://netbox.readthedocs.io/en/stable/installation/) has been updated to
|
||||
provide instructions for managing the WSGI and RQ services using systemd instead of supervisor. This removes the need to
|
||||
install supervisor and simplifies administration of the processes.
|
||||
|
||||
### Redis Configuration ([#3282](https://github.com/netbox-community/netbox/issues/3282))
|
||||
|
||||
v2.6.0 introduced caching and added the `CACHE_DATABASE` option to the existing `REDIS` database configuration section.
|
||||
This did not however, allow for using two different Redis connections for the seperate caching and webhooks features.
|
||||
This change separates the Redis connection configurations in the `REDIS` section into distinct `webhooks` and `caching`
|
||||
subsections. This requires modification of the `REDIS` section of the `configuration.py` file as follows:
|
||||
NetBox v2.6 introduced request caching and added the `CACHE_DATABASE` option to the existing `REDIS` database
|
||||
configuration parameter. This did not, however, allow for using two different Redis connections for the separate caching
|
||||
and webhook queuing functions. This release modifies the `REDIS` parameter to accept two discrete subsections named
|
||||
`webhooks` and `caching`. This requires modification of the `REDIS` parameter in `configuration.py` as follows:
|
||||
|
||||
Old Redis configuration:
|
||||
|
||||
```python
|
||||
REDIS = {
|
||||
'HOST': 'localhost',
|
||||
@@ -156,6 +152,7 @@ REDIS = {
|
||||
```
|
||||
|
||||
New Redis configuration:
|
||||
|
||||
```python
|
||||
REDIS = {
|
||||
'webhooks': {
|
||||
@@ -175,11 +172,11 @@ REDIS = {
|
||||
'SSL': False,
|
||||
}
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
Note that `CACHE_DATABASE` has been removed and the connection settings have been duplicated for both `webhooks` and
|
||||
`caching`. This allows the user to make use of separate Redis instances and/or databases if desired. Full connection
|
||||
details are required in both sections, even if they are the same.
|
||||
Note that the `CACHE_DATABASE` parameter has been removed and the connection settings have been duplicated for both
|
||||
`webhooks` and `caching`. This allows the user to make use of separate Redis instances if desired. It is fine to use the
|
||||
same Redis service for both functions, although the database identifiers should be different.
|
||||
|
||||
### WEBHOOKS_ENABLED Configuration Setting Removed ([#3408](https://github.com/netbox-community/netbox/issues/3408))
|
||||
|
||||
@@ -191,10 +188,10 @@ installations.
|
||||
### API Choice Fields Now Use String Values ([#3569](https://github.com/netbox-community/netbox/issues/3569))
|
||||
|
||||
NetBox's REST API presents fields which reference a particular choice as a dictionary with two keys: `value` and
|
||||
`label`. In previous versions, `value` was an integer which represented the particular choice in the database. This has
|
||||
`label`. In previous versions, `value` was an integer which represented a particular choice in the database. This has
|
||||
been changed to a more human-friendly "slug" string, which is essentially a simplified version of the choice's `label`.
|
||||
|
||||
For example, The site status field was previously represented as:
|
||||
For example, The site model's `status` field was previously represented as:
|
||||
|
||||
```json
|
||||
"status": {
|
||||
@@ -203,43 +200,68 @@ For example, The site status field was previously represented as:
|
||||
},
|
||||
```
|
||||
|
||||
Beginning with v2.7.0, it now looks like this:
|
||||
In NetBox v2.7, it now looks like this:
|
||||
|
||||
```json
|
||||
"status": {
|
||||
"value": "active",
|
||||
"label": "Active"
|
||||
"label": "Active",
|
||||
"id": 1
|
||||
},
|
||||
```
|
||||
|
||||
This change allows for much more intuitive representation of values, and obviates the need for API consumers to maintain
|
||||
a mapping of static integer values.
|
||||
This change allows for much more intuitive representation and manipulation of values, and removes the need for API
|
||||
consumers to maintain local mappings of static integer values.
|
||||
|
||||
Note that that all v2.7 releases will continue to accept the legacy integer values in write requests (POST, PUT, and
|
||||
PATCH) to maintain backward compatibility. This behavior will be discontinued beginning in v2.8.0.
|
||||
Note that that all v2.7 releases will continue to accept the legacy integer values in write requests (`POST`, `PUT`, and
|
||||
`PATCH`) to maintain backward compatibility. Additionally, the legacy numeric identifier is conveyed in the `id` field
|
||||
for convenient reference as consumers adopt to the new string values. This behavior will be discontinued in NetBox v2.8.
|
||||
|
||||
## Enhancements
|
||||
|
||||
* [#33](https://github.com/digitalocean/netbox/issues/33) - Add ability to clone objects (pre-populate form fields)
|
||||
* [#648](https://github.com/digitalocean/netbox/issues/648) - Pre-populate forms when selecting "create and add another"
|
||||
* [#792](https://github.com/digitalocean/netbox/issues/792) - Add power port and power outlet types
|
||||
* [#1865](https://github.com/digitalocean/netbox/issues/1865) - Add console port and console server port types
|
||||
* [#2669](https://github.com/digitalocean/netbox/issues/2669) - Relax uniqueness constraint on device and VM names
|
||||
* [#2902](https://github.com/digitalocean/netbox/issues/2902) - Replace `supervisord` with `systemd`
|
||||
* [#3455](https://github.com/digitalocean/netbox/issues/3455) - Add tenant assignment to cluster
|
||||
* [#3564](https://github.com/digitalocean/netbox/issues/3564) - Add list views for device components
|
||||
* [#3538](https://github.com/digitalocean/netbox/issues/3538) - Introduce a REST API endpoint for executing custom
|
||||
* [#33](https://github.com/netbox-community/netbox/issues/33) - Add ability to clone objects (pre-populate form fields)
|
||||
* [#648](https://github.com/netbox-community/netbox/issues/648) - Pre-populate form fields when selecting "create and
|
||||
add another"
|
||||
* [#792](https://github.com/netbox-community/netbox/issues/792) - Add power port and power outlet types
|
||||
* [#1865](https://github.com/netbox-community/netbox/issues/1865) - Add console port and console server port types
|
||||
* [#2669](https://github.com/netbox-community/netbox/issues/2669) - Relax uniqueness constraint on device and VM names
|
||||
* [#2902](https://github.com/netbox-community/netbox/issues/2902) - Replace `supervisord` with `systemd`
|
||||
* [#3455](https://github.com/netbox-community/netbox/issues/3455) - Add tenant assignment to virtual machine clusters
|
||||
* [#3520](https://github.com/netbox-community/netbox/issues/3520) - Add Jinja2 template support for graphs
|
||||
* [#3525](https://github.com/netbox-community/netbox/issues/3525) - Enable IP address filtering using multiple address
|
||||
parameters
|
||||
* [#3564](https://github.com/netbox-community/netbox/issues/3564) - Add list views for all device components
|
||||
* [#3538](https://github.com/netbox-community/netbox/issues/3538) - Introduce a REST API endpoint for executing custom
|
||||
scripts
|
||||
* [#3655](https://github.com/digitalocean/netbox/issues/3655) - Add `description` field to organizational models
|
||||
* [#3664](https://github.com/digitalocean/netbox/issues/3664) - Enable applying configuration contexts by tags
|
||||
* [#3706](https://github.com/digitalocean/netbox/issues/3706) - Increase `available_power` maximum value on PowerFeed
|
||||
* [#3731](https://github.com/digitalocean/netbox/issues/3731) - Change Graph.type to a ContentType foreign key field
|
||||
* [#3655](https://github.com/netbox-community/netbox/issues/3655) - Add `description` field to organizational models
|
||||
* [#3664](https://github.com/netbox-community/netbox/issues/3664) - Enable applying configuration contexts by tags
|
||||
* [#3706](https://github.com/netbox-community/netbox/issues/3706) - Increase `available_power` maximum value on
|
||||
PowerFeed
|
||||
* [#3731](https://github.com/netbox-community/netbox/issues/3731) - Change Graph.type to a ContentType foreign key field
|
||||
* [#3801](https://github.com/netbox-community/netbox/issues/3801) - Use YAML for export of device types
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
* [#3830](https://github.com/netbox-community/netbox/issues/3830) - Ensure deterministic ordering for all models
|
||||
* [#3900](https://github.com/netbox-community/netbox/issues/3900) - Fix exception when deleting device types
|
||||
* [#3914](https://github.com/netbox-community/netbox/issues/3914) - Fix interface filter field when unauthenticated
|
||||
* [#3919](https://github.com/netbox-community/netbox/issues/3919) - Fix utilization graph extending out of bounds when
|
||||
utilization > 100%
|
||||
* [#3927](https://github.com/netbox-community/netbox/issues/3927) - Fix exception when deleting devices with secrets
|
||||
assigned
|
||||
* [#3930](https://github.com/netbox-community/netbox/issues/3930) - Fix API rendering of the `family` field for
|
||||
aggregates
|
||||
|
||||
## Bug Fixes (From Beta)
|
||||
|
||||
* [#3868](https://github.com/netbox-community/netbox/issues/3868) - Fix creation of interfaces for virtual machines
|
||||
* [#3878](https://github.com/netbox-community/netbox/issues/3878) - Fix database migration for cable status field
|
||||
|
||||
## API Changes
|
||||
|
||||
* Choice fields now use human-friendly strings for their values instead of integers (see
|
||||
[#3569](https://github.com/netbox-community/netbox/issues/3569)).
|
||||
* Introduced `/api/extras/scripts/` endpoint for retrieving and executing custom scripts
|
||||
* Introduced the `/api/extras/scripts/` endpoint for retrieving and executing custom scripts
|
||||
* circuits.CircuitType: Added field `description`
|
||||
* dcim.ConsolePort: Added field `type`
|
||||
* dcim.ConsolePortTemplate: Added field `type`
|
||||
@@ -251,6 +273,7 @@ PATCH) to maintain backward compatibility. This behavior will be discontinued be
|
||||
* dcim.PowerOutlet: Added field `type`
|
||||
* dcim.PowerOutletTemplate: Added field `type`
|
||||
* dcim.RackRole: Added field `description`
|
||||
* extras.Graph: Added field `template_language` (to indicate `django` or `jinja2`)
|
||||
* extras.Graph: The `type` field has been changed to a content type foreign key. Models are specified as
|
||||
`<app>.<model>`; e.g. `dcim.site`.
|
||||
* ipam.Role: Added field `description`
|
||||
|
||||
@@ -35,6 +35,7 @@ pages:
|
||||
- Custom Scripts: 'additional-features/custom-scripts.md'
|
||||
- Export Templates: 'additional-features/export-templates.md'
|
||||
- Graphs: 'additional-features/graphs.md'
|
||||
- NAPALM: 'additional-features/napalm.md'
|
||||
- Prometheus Metrics: 'additional-features/prometheus-metrics.md'
|
||||
- Reports: 'additional-features/reports.md'
|
||||
- Tags: 'additional-features/tags.md'
|
||||
@@ -54,7 +55,9 @@ pages:
|
||||
- Utility Views: 'development/utility-views.md'
|
||||
- Extending Models: 'development/extending-models.md'
|
||||
- Release Checklist: 'development/release-checklist.md'
|
||||
- Squashing Migrations: 'development/squashing-migrations.md'
|
||||
- Release Notes:
|
||||
- Version 2.7: 'release-notes/version-2.7.md'
|
||||
- Version 2.6: 'release-notes/version-2.6.md'
|
||||
- Version 2.5: 'release-notes/version-2.5.md'
|
||||
- Version 2.4: 'release-notes/version-2.4.md'
|
||||
|
||||
@@ -18,8 +18,8 @@ from . import serializers
|
||||
|
||||
class CircuitsFieldChoicesViewSet(FieldChoicesViewSet):
|
||||
fields = (
|
||||
(Circuit, ['status']),
|
||||
(CircuitTermination, ['term_side']),
|
||||
(serializers.CircuitSerializer, ['status']),
|
||||
(serializers.CircuitTerminationSerializer, ['term_side']),
|
||||
)
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ class ProviderViewSet(CustomFieldModelViewSet):
|
||||
circuit_count=Count('circuits')
|
||||
)
|
||||
serializer_class = serializers.ProviderSerializer
|
||||
filterset_class = filters.ProviderFilter
|
||||
filterset_class = filters.ProviderFilterSet
|
||||
|
||||
@action(detail=True)
|
||||
def graphs(self, request, pk):
|
||||
@@ -54,7 +54,7 @@ class CircuitTypeViewSet(ModelViewSet):
|
||||
circuit_count=Count('circuits')
|
||||
)
|
||||
serializer_class = serializers.CircuitTypeSerializer
|
||||
filterset_class = filters.CircuitTypeFilter
|
||||
filterset_class = filters.CircuitTypeFilterSet
|
||||
|
||||
|
||||
#
|
||||
@@ -64,7 +64,7 @@ class CircuitTypeViewSet(ModelViewSet):
|
||||
class CircuitViewSet(CustomFieldModelViewSet):
|
||||
queryset = Circuit.objects.prefetch_related('type', 'tenant', 'provider').prefetch_related('tags')
|
||||
serializer_class = serializers.CircuitSerializer
|
||||
filterset_class = filters.CircuitFilter
|
||||
filterset_class = filters.CircuitFilterSet
|
||||
|
||||
|
||||
#
|
||||
@@ -76,4 +76,4 @@ class CircuitTerminationViewSet(ModelViewSet):
|
||||
'circuit', 'site', 'connected_endpoint__device', 'cable'
|
||||
)
|
||||
serializer_class = serializers.CircuitTerminationSerializer
|
||||
filterset_class = filters.CircuitTerminationFilter
|
||||
filterset_class = filters.CircuitTerminationFilterSet
|
||||
|
||||
@@ -3,13 +3,20 @@ from django.db.models import Q
|
||||
|
||||
from dcim.models import Region, Site
|
||||
from extras.filters import CustomFieldFilterSet, CreatedUpdatedFilterSet
|
||||
from tenancy.filtersets import TenancyFilterSet
|
||||
from tenancy.filters import TenancyFilterSet
|
||||
from utilities.filters import NameSlugSearchFilterSet, NumericInFilter, TagFilter, TreeNodeMultipleChoiceFilter
|
||||
from .choices import *
|
||||
from .models import Circuit, CircuitTermination, CircuitType, Provider
|
||||
|
||||
__all__ = (
|
||||
'CircuitFilterSet',
|
||||
'CircuitTerminationFilterSet',
|
||||
'CircuitTypeFilterSet',
|
||||
'ProviderFilterSet',
|
||||
)
|
||||
|
||||
class ProviderFilter(CustomFieldFilterSet, CreatedUpdatedFilterSet):
|
||||
|
||||
class ProviderFilterSet(CustomFieldFilterSet, CreatedUpdatedFilterSet):
|
||||
id__in = NumericInFilter(
|
||||
field_name='id',
|
||||
lookup_expr='in'
|
||||
@@ -18,6 +25,17 @@ class ProviderFilter(CustomFieldFilterSet, CreatedUpdatedFilterSet):
|
||||
method='search',
|
||||
label='Search',
|
||||
)
|
||||
region_id = TreeNodeMultipleChoiceFilter(
|
||||
queryset=Region.objects.all(),
|
||||
field_name='circuits__terminations__site__region__in',
|
||||
label='Region (ID)',
|
||||
)
|
||||
region = TreeNodeMultipleChoiceFilter(
|
||||
queryset=Region.objects.all(),
|
||||
field_name='circuits__terminations__site__region__in',
|
||||
to_field_name='slug',
|
||||
label='Region (slug)',
|
||||
)
|
||||
site_id = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='circuits__terminations__site',
|
||||
queryset=Site.objects.all(),
|
||||
@@ -47,14 +65,14 @@ class ProviderFilter(CustomFieldFilterSet, CreatedUpdatedFilterSet):
|
||||
)
|
||||
|
||||
|
||||
class CircuitTypeFilter(NameSlugSearchFilterSet):
|
||||
class CircuitTypeFilterSet(NameSlugSearchFilterSet):
|
||||
|
||||
class Meta:
|
||||
model = CircuitType
|
||||
fields = ['id', 'name', 'slug']
|
||||
|
||||
|
||||
class CircuitFilter(CustomFieldFilterSet, TenancyFilterSet, CreatedUpdatedFilterSet):
|
||||
class CircuitFilterSet(CustomFieldFilterSet, TenancyFilterSet, CreatedUpdatedFilterSet):
|
||||
id__in = NumericInFilter(
|
||||
field_name='id',
|
||||
lookup_expr='in'
|
||||
@@ -128,7 +146,7 @@ class CircuitFilter(CustomFieldFilterSet, TenancyFilterSet, CreatedUpdatedFilter
|
||||
).distinct()
|
||||
|
||||
|
||||
class CircuitTerminationFilter(django_filters.FilterSet):
|
||||
class CircuitTerminationFilterSet(django_filters.FilterSet):
|
||||
q = django_filters.CharFilter(
|
||||
method='search',
|
||||
label='Search',
|
||||
|
||||
@@ -7,7 +7,7 @@ from tenancy.forms import TenancyFilterForm, TenancyForm
|
||||
from tenancy.models import Tenant
|
||||
from utilities.forms import (
|
||||
APISelect, APISelectMultiple, add_blank_choice, BootstrapMixin, CommentField, CSVChoiceField,
|
||||
FilterChoiceField, SmallTextarea, SlugField, StaticSelect2, StaticSelect2Multiple
|
||||
DatePicker, FilterChoiceField, SmallTextarea, SlugField, StaticSelect2, StaticSelect2Multiple
|
||||
)
|
||||
from .choices import CircuitStatusChoices
|
||||
from .models import Circuit, CircuitTermination, CircuitType, Provider
|
||||
@@ -104,6 +104,18 @@ class ProviderFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
||||
required=False,
|
||||
label='Search'
|
||||
)
|
||||
region = FilterChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
to_field_name='slug',
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
api_url="/api/dcim/regions/",
|
||||
value_field="slug",
|
||||
filter_for={
|
||||
'site': 'region'
|
||||
}
|
||||
)
|
||||
)
|
||||
site = FilterChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
to_field_name='slug',
|
||||
@@ -161,7 +173,6 @@ class CircuitForm(BootstrapMixin, TenancyForm, CustomFieldForm):
|
||||
]
|
||||
help_texts = {
|
||||
'cid': "Unique circuit ID",
|
||||
'install_date': "Format: YYYY-MM-DD",
|
||||
'commit_rate': "Committed rate",
|
||||
}
|
||||
widgets = {
|
||||
@@ -172,7 +183,7 @@ class CircuitForm(BootstrapMixin, TenancyForm, CustomFieldForm):
|
||||
api_url="/api/circuits/circuit-types/"
|
||||
),
|
||||
'status': StaticSelect2(),
|
||||
|
||||
'install_date': DatePicker(),
|
||||
}
|
||||
|
||||
|
||||
@@ -303,6 +314,9 @@ class CircuitFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm
|
||||
widget=APISelectMultiple(
|
||||
api_url="/api/dcim/regions/",
|
||||
value_field="slug",
|
||||
filter_for={
|
||||
'site': 'region'
|
||||
}
|
||||
)
|
||||
)
|
||||
site = FilterChoiceField(
|
||||
|
||||
@@ -1,40 +1,36 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.14 on 2018-07-31 02:25
|
||||
import dcim.fields
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
import dcim.fields
|
||||
|
||||
|
||||
def circuits_to_terms(apps, schema_editor):
|
||||
Circuit = apps.get_model('circuits', 'Circuit')
|
||||
CircuitTermination = apps.get_model('circuits', 'CircuitTermination')
|
||||
for c in Circuit.objects.all():
|
||||
CircuitTermination(
|
||||
circuit=c,
|
||||
term_side=b'A',
|
||||
site=c.site,
|
||||
interface=c.interface,
|
||||
port_speed=c.port_speed,
|
||||
upstream_speed=c.upstream_speed,
|
||||
xconnect_id=c.xconnect_id,
|
||||
pp_info=c.pp_info,
|
||||
).save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
replaces = [('circuits', '0001_initial'), ('circuits', '0002_auto_20160622_1821'), ('circuits', '0003_provider_32bit_asn_support'), ('circuits', '0004_circuit_add_tenant'), ('circuits', '0005_circuit_add_upstream_speed'), ('circuits', '0006_terminations'), ('circuits', '0007_circuit_add_description'), ('circuits', '0008_circuittermination_interface_protect_on_delete'), ('circuits', '0009_unicode_literals'), ('circuits', '0010_circuit_status')]
|
||||
replaces = [('circuits', '0001_initial'), ('circuits', '0002_auto_20160622_1821'), ('circuits', '0003_provider_32bit_asn_support'), ('circuits', '0004_circuit_add_tenant'), ('circuits', '0005_circuit_add_upstream_speed'), ('circuits', '0006_terminations')]
|
||||
|
||||
dependencies = [
|
||||
('tenancy', '0001_initial'),
|
||||
('dcim', '0001_initial'),
|
||||
('dcim', '0022_color_names_to_rgb'),
|
||||
('tenancy', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Provider',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created', models.DateField(auto_now_add=True)),
|
||||
('last_updated', models.DateTimeField(auto_now=True)),
|
||||
('name', models.CharField(max_length=50, unique=True)),
|
||||
('slug', models.SlugField(unique=True)),
|
||||
('asn', dcim.fields.ASNField(blank=True, null=True, verbose_name='ASN')),
|
||||
('account', models.CharField(blank=True, max_length=30, verbose_name='Account number')),
|
||||
('portal_url', models.URLField(blank=True, verbose_name='Portal')),
|
||||
('noc_contact', models.TextField(blank=True, verbose_name='NOC contact')),
|
||||
('admin_contact', models.TextField(blank=True, verbose_name='Admin contact')),
|
||||
('comments', models.TextField(blank=True)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['name'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CircuitType',
|
||||
fields=[
|
||||
@@ -46,49 +42,93 @@ class Migration(migrations.Migration):
|
||||
'ordering': ['name'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Provider',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created', models.DateField(auto_now_add=True)),
|
||||
('last_updated', models.DateTimeField(auto_now=True)),
|
||||
('name', models.CharField(max_length=50, unique=True)),
|
||||
('slug', models.SlugField(unique=True)),
|
||||
('asn', dcim.fields.ASNField(blank=True, null=True, verbose_name=b'ASN')),
|
||||
('account', models.CharField(blank=True, max_length=30, verbose_name=b'Account number')),
|
||||
('portal_url', models.URLField(blank=True, verbose_name=b'Portal')),
|
||||
('noc_contact', models.TextField(blank=True, verbose_name=b'NOC contact')),
|
||||
('admin_contact', models.TextField(blank=True, verbose_name=b'Admin contact')),
|
||||
('comments', models.TextField(blank=True)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['name'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Circuit',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created', models.DateField(auto_now_add=True)),
|
||||
('last_updated', models.DateTimeField(auto_now=True)),
|
||||
('cid', models.CharField(max_length=50, verbose_name='Circuit ID')),
|
||||
('install_date', models.DateField(blank=True, null=True, verbose_name='Date installed')),
|
||||
('commit_rate', models.PositiveIntegerField(blank=True, null=True, verbose_name='Commit rate (Kbps)')),
|
||||
('cid', models.CharField(max_length=50, verbose_name=b'Circuit ID')),
|
||||
('install_date', models.DateField(blank=True, null=True, verbose_name=b'Date installed')),
|
||||
('port_speed', models.PositiveIntegerField(verbose_name=b'Port speed (Kbps)')),
|
||||
('commit_rate', models.PositiveIntegerField(blank=True, null=True, verbose_name=b'Commit rate (Kbps)')),
|
||||
('xconnect_id', models.CharField(blank=True, max_length=50, verbose_name=b'Cross-connect ID')),
|
||||
('pp_info', models.CharField(blank=True, max_length=100, verbose_name=b'Patch panel/port(s)')),
|
||||
('comments', models.TextField(blank=True)),
|
||||
('interface', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='circuit', to='dcim.Interface')),
|
||||
('provider', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='circuits', to='circuits.Provider')),
|
||||
('site', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='circuits', to='dcim.Site')),
|
||||
('type', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='circuits', to='circuits.CircuitType')),
|
||||
('tenant', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='circuits', to='tenancy.Tenant')),
|
||||
('description', models.CharField(blank=True, max_length=100)),
|
||||
('status', models.PositiveSmallIntegerField(choices=[[2, 'Planned'], [3, 'Provisioning'], [1, 'Active'], [4, 'Offline'], [0, 'Deprovisioning'], [5, 'Decommissioned']], default=1))
|
||||
('upstream_speed', models.PositiveIntegerField(blank=True, help_text=b'Upstream speed, if different from port speed', null=True, verbose_name=b'Upstream speed (Kbps)')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['provider', 'cid'],
|
||||
'unique_together': {('provider', 'cid')},
|
||||
},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='circuit',
|
||||
unique_together=set([('provider', 'cid')]),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CircuitTermination',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('term_side', models.CharField(choices=[('A', 'A'), ('Z', 'Z')], max_length=1, verbose_name='Termination')),
|
||||
('port_speed', models.PositiveIntegerField(verbose_name='Port speed (Kbps)')),
|
||||
('upstream_speed', models.PositiveIntegerField(blank=True, help_text='Upstream speed, if different from port speed', null=True, verbose_name='Upstream speed (Kbps)')),
|
||||
('xconnect_id', models.CharField(blank=True, max_length=50, verbose_name='Cross-connect ID')),
|
||||
('pp_info', models.CharField(blank=True, max_length=100, verbose_name='Patch panel/port(s)')),
|
||||
('term_side', models.CharField(choices=[(b'A', b'A'), (b'Z', b'Z')], max_length=1, verbose_name='Termination')),
|
||||
('port_speed', models.PositiveIntegerField(verbose_name=b'Port speed (Kbps)')),
|
||||
('upstream_speed', models.PositiveIntegerField(blank=True, help_text=b'Upstream speed, if different from port speed', null=True, verbose_name=b'Upstream speed (Kbps)')),
|
||||
('xconnect_id', models.CharField(blank=True, max_length=50, verbose_name=b'Cross-connect ID')),
|
||||
('pp_info', models.CharField(blank=True, max_length=100, verbose_name=b'Patch panel/port(s)')),
|
||||
('circuit', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='terminations', to='circuits.Circuit')),
|
||||
('interface', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='circuit_termination', to='dcim.Interface')),
|
||||
('interface', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='circuit_termination', to='dcim.Interface')),
|
||||
('site', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='circuit_terminations', to='dcim.Site')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['circuit', 'term_side'],
|
||||
'unique_together': {('circuit', 'term_side')},
|
||||
},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='circuittermination',
|
||||
unique_together=set([('circuit', 'term_side')]),
|
||||
migrations.RunPython(
|
||||
code=circuits_to_terms,
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='circuit',
|
||||
name='interface',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='circuit',
|
||||
name='port_speed',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='circuit',
|
||||
name='pp_info',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='circuit',
|
||||
name='site',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='circuit',
|
||||
name='upstream_speed',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='circuit',
|
||||
name='xconnect_id',
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,254 @@
|
||||
import sys
|
||||
|
||||
import django.db.models.deletion
|
||||
import taggit.managers
|
||||
from django.db import migrations, models
|
||||
|
||||
import dcim.fields
|
||||
|
||||
CONNECTION_STATUS_CONNECTED = True
|
||||
|
||||
CIRCUIT_STATUS_CHOICES = (
|
||||
(0, 'deprovisioning'),
|
||||
(1, 'active'),
|
||||
(2, 'planned'),
|
||||
(3, 'provisioning'),
|
||||
(4, 'offline'),
|
||||
(5, 'decommissioned')
|
||||
)
|
||||
|
||||
|
||||
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))
|
||||
|
||||
|
||||
def circuit_status_to_slug(apps, schema_editor):
|
||||
Circuit = apps.get_model('circuits', 'Circuit')
|
||||
for id, slug in CIRCUIT_STATUS_CHOICES:
|
||||
Circuit.objects.filter(status=str(id)).update(status=slug)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
replaces = [('circuits', '0007_circuit_add_description'), ('circuits', '0008_circuittermination_interface_protect_on_delete'), ('circuits', '0009_unicode_literals'), ('circuits', '0010_circuit_status'), ('circuits', '0011_tags'), ('circuits', '0012_change_logging'), ('circuits', '0013_cables'), ('circuits', '0014_circuittermination_description'), ('circuits', '0015_custom_tag_models'), ('circuits', '0016_3569_circuit_fields'), ('circuits', '0017_circuittype_description')]
|
||||
|
||||
dependencies = [
|
||||
('circuits', '0006_terminations'),
|
||||
('extras', '0019_tag_taggeditem'),
|
||||
('taggit', '0002_auto_20150616_2121'),
|
||||
('dcim', '0066_cables'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='circuit',
|
||||
name='description',
|
||||
field=models.CharField(blank=True, max_length=100),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='circuittermination',
|
||||
name='interface',
|
||||
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='circuit_termination', to='dcim.Interface'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='circuit',
|
||||
name='cid',
|
||||
field=models.CharField(max_length=50, verbose_name='Circuit ID'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='circuit',
|
||||
name='commit_rate',
|
||||
field=models.PositiveIntegerField(blank=True, null=True, verbose_name='Commit rate (Kbps)'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='circuit',
|
||||
name='install_date',
|
||||
field=models.DateField(blank=True, null=True, verbose_name='Date installed'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='circuittermination',
|
||||
name='port_speed',
|
||||
field=models.PositiveIntegerField(verbose_name='Port speed (Kbps)'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='circuittermination',
|
||||
name='pp_info',
|
||||
field=models.CharField(blank=True, max_length=100, verbose_name='Patch panel/port(s)'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='circuittermination',
|
||||
name='term_side',
|
||||
field=models.CharField(choices=[('A', 'A'), ('Z', 'Z')], max_length=1, verbose_name='Termination'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='circuittermination',
|
||||
name='upstream_speed',
|
||||
field=models.PositiveIntegerField(blank=True, help_text='Upstream speed, if different from port speed', null=True, verbose_name='Upstream speed (Kbps)'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='circuittermination',
|
||||
name='xconnect_id',
|
||||
field=models.CharField(blank=True, max_length=50, verbose_name='Cross-connect ID'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='provider',
|
||||
name='account',
|
||||
field=models.CharField(blank=True, max_length=30, verbose_name='Account number'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='provider',
|
||||
name='admin_contact',
|
||||
field=models.TextField(blank=True, verbose_name='Admin contact'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='provider',
|
||||
name='asn',
|
||||
field=dcim.fields.ASNField(blank=True, null=True, verbose_name='ASN'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='provider',
|
||||
name='noc_contact',
|
||||
field=models.TextField(blank=True, verbose_name='NOC contact'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='provider',
|
||||
name='portal_url',
|
||||
field=models.URLField(blank=True, verbose_name='Portal'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='circuit',
|
||||
name='status',
|
||||
field=models.PositiveSmallIntegerField(choices=[[2, 'Planned'], [3, 'Provisioning'], [1, 'Active'], [4, 'Offline'], [0, 'Deprovisioning'], [5, 'Decommissioned']], default=1),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='circuit',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='provider',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='circuittype',
|
||||
name='created',
|
||||
field=models.DateField(auto_now_add=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='circuittype',
|
||||
name='last_updated',
|
||||
field=models.DateTimeField(auto_now=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='circuit',
|
||||
name='created',
|
||||
field=models.DateField(auto_now_add=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='circuit',
|
||||
name='last_updated',
|
||||
field=models.DateTimeField(auto_now=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='provider',
|
||||
name='created',
|
||||
field=models.DateField(auto_now_add=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='provider',
|
||||
name='last_updated',
|
||||
field=models.DateTimeField(auto_now=True, null=True),
|
||||
),
|
||||
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'),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=circuit_terminations_to_cables,
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='circuittermination',
|
||||
name='interface',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='circuittermination',
|
||||
name='description',
|
||||
field=models.CharField(blank=True, max_length=100),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='circuit',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='extras.TaggedItem', to='extras.Tag', verbose_name='Tags'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='provider',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='extras.TaggedItem', to='extras.Tag', verbose_name='Tags'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='circuit',
|
||||
name='status',
|
||||
field=models.CharField(default='active', max_length=50),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=circuit_status_to_slug,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='circuittype',
|
||||
name='description',
|
||||
field=models.CharField(blank=True, max_length=100),
|
||||
),
|
||||
]
|
||||
@@ -3,7 +3,7 @@ import sys
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
from dcim.constants import CONNECTION_STATUS_CONNECTED
|
||||
CONNECTION_STATUS_CONNECTED = True
|
||||
|
||||
|
||||
def circuit_terminations_to_cables(apps, schema_editor):
|
||||
|
||||
@@ -12,6 +12,14 @@ from utilities.utils import serialize_object
|
||||
from .choices import *
|
||||
|
||||
|
||||
__all__ = (
|
||||
'Circuit',
|
||||
'CircuitTermination',
|
||||
'CircuitType',
|
||||
'Provider',
|
||||
)
|
||||
|
||||
|
||||
class Provider(ChangeLoggedModel, CustomFieldModel):
|
||||
"""
|
||||
Each Circuit belongs to a Provider. This is usually a telecommunications company or similar organization. This model
|
||||
|
||||
@@ -6,7 +6,30 @@ from circuits.choices import *
|
||||
from circuits.models import Circuit, CircuitTermination, CircuitType, Provider
|
||||
from dcim.models import Site
|
||||
from extras.models import Graph
|
||||
from utilities.testing import APITestCase
|
||||
from utilities.testing import APITestCase, choices_to_dict
|
||||
|
||||
|
||||
class AppTest(APITestCase):
|
||||
|
||||
def test_root(self):
|
||||
|
||||
url = reverse('circuits-api:api-root')
|
||||
response = self.client.get('{}?format=api'.format(url), **self.header)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_choices(self):
|
||||
|
||||
url = reverse('circuits-api:field-choice-list')
|
||||
response = self.client.get(url, **self.header)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
# Circuit
|
||||
self.assertEqual(choices_to_dict(response.data.get('circuit:status')), CircuitStatusChoices.as_dict())
|
||||
|
||||
# CircuitTermination
|
||||
self.assertEqual(choices_to_dict(response.data.get('circuit-termination:term_side')), CircuitTerminationSideChoices.as_dict())
|
||||
|
||||
|
||||
class ProviderTest(APITestCase):
|
||||
|
||||
287
netbox/circuits/tests/test_filters.py
Normal file
287
netbox/circuits/tests/test_filters.py
Normal file
@@ -0,0 +1,287 @@
|
||||
from django.test import TestCase
|
||||
|
||||
from circuits.choices import *
|
||||
from circuits.filters import *
|
||||
from circuits.models import Circuit, CircuitTermination, CircuitType, Provider
|
||||
from dcim.models import Region, Site
|
||||
|
||||
|
||||
class ProviderTestCase(TestCase):
|
||||
queryset = Provider.objects.all()
|
||||
filterset = ProviderFilterSet
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
|
||||
providers = (
|
||||
Provider(name='Provider 1', slug='provider-1', asn=65001, account='1234'),
|
||||
Provider(name='Provider 2', slug='provider-2', asn=65002, account='2345'),
|
||||
Provider(name='Provider 3', slug='provider-3', asn=65003, account='3456'),
|
||||
Provider(name='Provider 4', slug='provider-4', asn=65004, account='4567'),
|
||||
Provider(name='Provider 5', slug='provider-5', asn=65005, account='5678'),
|
||||
)
|
||||
Provider.objects.bulk_create(providers)
|
||||
|
||||
regions = (
|
||||
Region(name='Test Region 1', slug='test-region-1'),
|
||||
Region(name='Test Region 2', slug='test-region-2'),
|
||||
)
|
||||
# Can't use bulk_create for models with MPTT fields
|
||||
for r in regions:
|
||||
r.save()
|
||||
|
||||
sites = (
|
||||
Site(name='Test Site 1', slug='test-site-1', region=regions[0]),
|
||||
Site(name='Test Site 2', slug='test-site-2', region=regions[1]),
|
||||
)
|
||||
Site.objects.bulk_create(sites)
|
||||
|
||||
circuit_types = (
|
||||
CircuitType(name='Test Circuit Type 1', slug='test-circuit-type-1'),
|
||||
CircuitType(name='Test Circuit Type 2', slug='test-circuit-type-2'),
|
||||
)
|
||||
CircuitType.objects.bulk_create(circuit_types)
|
||||
|
||||
circuits = (
|
||||
Circuit(provider=providers[0], type=circuit_types[0], cid='Test Circuit 1'),
|
||||
Circuit(provider=providers[1], type=circuit_types[1], cid='Test Circuit 1'),
|
||||
)
|
||||
Circuit.objects.bulk_create(circuits)
|
||||
|
||||
CircuitTermination.objects.bulk_create((
|
||||
CircuitTermination(circuit=circuits[0], site=sites[0], term_side='A', port_speed=1000),
|
||||
CircuitTermination(circuit=circuits[1], site=sites[0], term_side='A', port_speed=1000),
|
||||
))
|
||||
|
||||
def test_name(self):
|
||||
params = {'name': ['Provider 1', 'Provider 2']}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_slug(self):
|
||||
params = {'slug': ['provider-1', 'provider-2']}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_asn(self):
|
||||
params = {'asn': ['65001', '65002']}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_account(self):
|
||||
params = {'account': ['1234', '2345']}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_id__in(self):
|
||||
id_list = self.queryset.values_list('id', flat=True)[:3]
|
||||
params = {'id__in': ','.join([str(id) for id in id_list])}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
||||
|
||||
def test_site(self):
|
||||
sites = Site.objects.all()[:2]
|
||||
params = {'site_id': [sites[0].pk, sites[1].pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
params = {'site': [sites[0].slug, sites[1].slug]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_region(self):
|
||||
regions = Region.objects.all()[:2]
|
||||
params = {'region_id': [regions[0].pk, regions[1].pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
params = {'region': [regions[0].slug, regions[1].slug]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
|
||||
class CircuitTypeTestCase(TestCase):
|
||||
queryset = CircuitType.objects.all()
|
||||
filterset = CircuitTypeFilterSet
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
|
||||
CircuitType.objects.bulk_create((
|
||||
CircuitType(name='Circuit Type 1', slug='circuit-type-1'),
|
||||
CircuitType(name='Circuit Type 2', slug='circuit-type-2'),
|
||||
CircuitType(name='Circuit Type 3', slug='circuit-type-3'),
|
||||
))
|
||||
|
||||
def test_id(self):
|
||||
params = {'id': [self.queryset.first().pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||
|
||||
def test_name(self):
|
||||
params = {'name': ['Circuit Type 1']}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||
|
||||
def test_slug(self):
|
||||
params = {'slug': ['circuit-type-1']}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||
|
||||
|
||||
class CircuitTestCase(TestCase):
|
||||
queryset = Circuit.objects.all()
|
||||
filterset = CircuitFilterSet
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
|
||||
regions = (
|
||||
Region(name='Test Region 1', slug='test-region-1'),
|
||||
Region(name='Test Region 2', slug='test-region-2'),
|
||||
Region(name='Test Region 3', slug='test-region-3'),
|
||||
)
|
||||
# Can't use bulk_create for models with MPTT fields
|
||||
for r in regions:
|
||||
r.save()
|
||||
|
||||
sites = (
|
||||
Site(name='Test Site 1', slug='test-site-1', region=regions[0]),
|
||||
Site(name='Test Site 2', slug='test-site-2', region=regions[1]),
|
||||
Site(name='Test Site 3', slug='test-site-3', region=regions[2]),
|
||||
)
|
||||
Site.objects.bulk_create(sites)
|
||||
|
||||
circuit_types = (
|
||||
CircuitType(name='Test Circuit Type 1', slug='test-circuit-type-1'),
|
||||
CircuitType(name='Test Circuit Type 2', slug='test-circuit-type-2'),
|
||||
)
|
||||
CircuitType.objects.bulk_create(circuit_types)
|
||||
|
||||
providers = (
|
||||
Provider(name='Provider 1', slug='provider-1'),
|
||||
Provider(name='Provider 2', slug='provider-2'),
|
||||
)
|
||||
Provider.objects.bulk_create(providers)
|
||||
|
||||
circuits = (
|
||||
Circuit(provider=providers[0], type=circuit_types[0], cid='Test Circuit 1', install_date='2020-01-01', commit_rate=1000, status=CircuitStatusChoices.STATUS_ACTIVE),
|
||||
Circuit(provider=providers[0], type=circuit_types[0], cid='Test Circuit 2', install_date='2020-01-02', commit_rate=2000, status=CircuitStatusChoices.STATUS_ACTIVE),
|
||||
Circuit(provider=providers[0], type=circuit_types[0], cid='Test Circuit 3', install_date='2020-01-03', commit_rate=3000, status=CircuitStatusChoices.STATUS_PLANNED),
|
||||
Circuit(provider=providers[1], type=circuit_types[1], cid='Test Circuit 4', install_date='2020-01-04', commit_rate=4000, status=CircuitStatusChoices.STATUS_PLANNED),
|
||||
Circuit(provider=providers[1], type=circuit_types[1], cid='Test Circuit 5', install_date='2020-01-05', commit_rate=5000, status=CircuitStatusChoices.STATUS_OFFLINE),
|
||||
Circuit(provider=providers[1], type=circuit_types[1], cid='Test Circuit 6', install_date='2020-01-06', commit_rate=6000, status=CircuitStatusChoices.STATUS_OFFLINE),
|
||||
)
|
||||
Circuit.objects.bulk_create(circuits)
|
||||
|
||||
circuit_terminations = ((
|
||||
CircuitTermination(circuit=circuits[0], site=sites[0], term_side='A', port_speed=1000),
|
||||
CircuitTermination(circuit=circuits[1], site=sites[1], term_side='A', port_speed=1000),
|
||||
CircuitTermination(circuit=circuits[2], site=sites[2], term_side='A', port_speed=1000),
|
||||
))
|
||||
CircuitTermination.objects.bulk_create(circuit_terminations)
|
||||
|
||||
def test_cid(self):
|
||||
params = {'cid': ['Test Circuit 1', 'Test Circuit 2']}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_install_date(self):
|
||||
params = {'install_date': ['2020-01-01', '2020-01-02']}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_commit_rate(self):
|
||||
params = {'commit_rate': ['1000', '2000']}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_id__in(self):
|
||||
id_list = self.queryset.values_list('id', flat=True)[:3]
|
||||
params = {'id__in': ','.join([str(id) for id in id_list])}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
||||
|
||||
def test_provider(self):
|
||||
provider = Provider.objects.first()
|
||||
params = {'provider_id': [provider.pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
||||
params = {'provider': [provider.slug]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
||||
|
||||
def test_type(self):
|
||||
circuit_type = CircuitType.objects.first()
|
||||
params = {'type_id': [circuit_type.pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
||||
params = {'type': [circuit_type.slug]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
||||
|
||||
def test_status(self):
|
||||
params = {'status': [CircuitStatusChoices.STATUS_ACTIVE, CircuitStatusChoices.STATUS_PLANNED]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
||||
|
||||
def test_region(self):
|
||||
regions = Region.objects.all()[:2]
|
||||
params = {'region_id': [regions[0].pk, regions[1].pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
params = {'region': [regions[0].slug, regions[1].slug]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_site(self):
|
||||
sites = Site.objects.all()[:2]
|
||||
params = {'site_id': [sites[0].pk, sites[1].pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
params = {'site': [sites[0].slug, sites[1].slug]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
|
||||
class CircuitTerminationTestCase(TestCase):
|
||||
queryset = CircuitTermination.objects.all()
|
||||
filterset = CircuitTerminationFilterSet
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
|
||||
sites = (
|
||||
Site(name='Test Site 1', slug='test-site-1'),
|
||||
Site(name='Test Site 2', slug='test-site-2'),
|
||||
Site(name='Test Site 3', slug='test-site-3'),
|
||||
)
|
||||
Site.objects.bulk_create(sites)
|
||||
|
||||
circuit_types = (
|
||||
CircuitType(name='Test Circuit Type 1', slug='test-circuit-type-1'),
|
||||
)
|
||||
CircuitType.objects.bulk_create(circuit_types)
|
||||
|
||||
providers = (
|
||||
Provider(name='Provider 1', slug='provider-1'),
|
||||
)
|
||||
Provider.objects.bulk_create(providers)
|
||||
|
||||
circuits = (
|
||||
Circuit(provider=providers[0], type=circuit_types[0], cid='Test Circuit 1'),
|
||||
Circuit(provider=providers[0], type=circuit_types[0], cid='Test Circuit 2'),
|
||||
Circuit(provider=providers[0], type=circuit_types[0], cid='Test Circuit 3'),
|
||||
)
|
||||
Circuit.objects.bulk_create(circuits)
|
||||
|
||||
circuit_terminations = ((
|
||||
CircuitTermination(circuit=circuits[0], site=sites[0], term_side='A', port_speed=1000, upstream_speed=1000, xconnect_id='ABC'),
|
||||
CircuitTermination(circuit=circuits[0], site=sites[1], term_side='Z', port_speed=1000, upstream_speed=1000, xconnect_id='DEF'),
|
||||
CircuitTermination(circuit=circuits[1], site=sites[1], term_side='A', port_speed=2000, upstream_speed=2000, xconnect_id='GHI'),
|
||||
CircuitTermination(circuit=circuits[1], site=sites[2], term_side='Z', port_speed=2000, upstream_speed=2000, xconnect_id='JKL'),
|
||||
CircuitTermination(circuit=circuits[2], site=sites[2], term_side='A', port_speed=3000, upstream_speed=3000, xconnect_id='MNO'),
|
||||
CircuitTermination(circuit=circuits[2], site=sites[0], term_side='Z', port_speed=3000, upstream_speed=3000, xconnect_id='PQR'),
|
||||
))
|
||||
CircuitTermination.objects.bulk_create(circuit_terminations)
|
||||
|
||||
def test_term_side(self):
|
||||
params = {'term_side': 'A'}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
||||
|
||||
def test_port_speed(self):
|
||||
params = {'port_speed': ['1000', '2000']}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
||||
|
||||
def test_upstream_speed(self):
|
||||
params = {'upstream_speed': ['1000', '2000']}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
||||
|
||||
def test_xconnect_id(self):
|
||||
params = {'xconnect_id': ['ABC', 'DEF']}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_circuit_id(self):
|
||||
circuits = Circuit.objects.all()[:2]
|
||||
params = {'circuit_id': [circuits[0].pk, circuits[1].pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
||||
|
||||
def test_site(self):
|
||||
sites = Site.objects.all()[:2]
|
||||
params = {'site_id': [sites[0].pk, sites[1].pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
||||
params = {'site': [sites[0].slug, sites[1].slug]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
||||
@@ -1,3 +1,4 @@
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import permission_required
|
||||
from django.contrib.auth.mixins import PermissionRequiredMixin
|
||||
@@ -5,9 +6,11 @@ from django.db import transaction
|
||||
from django.db.models import Count, OuterRef, Subquery
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.views.generic import View
|
||||
from django_tables2 import RequestConfig
|
||||
|
||||
from extras.models import Graph
|
||||
from utilities.forms import ConfirmationForm
|
||||
from utilities.paginator import EnhancedPaginator
|
||||
from utilities.views import (
|
||||
BulkDeleteView, BulkEditView, BulkImportView, ObjectDeleteView, ObjectEditView, ObjectListView,
|
||||
)
|
||||
@@ -23,8 +26,8 @@ from .models import Circuit, CircuitTermination, CircuitType, Provider
|
||||
class ProviderListView(PermissionRequiredMixin, ObjectListView):
|
||||
permission_required = 'circuits.view_provider'
|
||||
queryset = Provider.objects.annotate(count_circuits=Count('circuits'))
|
||||
filter = filters.ProviderFilter
|
||||
filter_form = forms.ProviderFilterForm
|
||||
filterset = filters.ProviderFilterSet
|
||||
filterset_form = forms.ProviderFilterForm
|
||||
table = tables.ProviderDetailTable
|
||||
template_name = 'circuits/provider_list.html'
|
||||
|
||||
@@ -38,9 +41,18 @@ class ProviderView(PermissionRequiredMixin, View):
|
||||
circuits = Circuit.objects.filter(provider=provider).prefetch_related('type', 'tenant', 'terminations__site')
|
||||
show_graphs = Graph.objects.filter(type__model='provider').exists()
|
||||
|
||||
circuits_table = tables.CircuitTable(circuits, orderable=False)
|
||||
circuits_table.columns.hide('provider')
|
||||
|
||||
paginate = {
|
||||
'paginator_class': EnhancedPaginator,
|
||||
'per_page': request.GET.get('per_page', settings.PAGINATE_COUNT)
|
||||
}
|
||||
RequestConfig(request, paginate).configure(circuits_table)
|
||||
|
||||
return render(request, 'circuits/provider.html', {
|
||||
'provider': provider,
|
||||
'circuits': circuits,
|
||||
'circuits_table': circuits_table,
|
||||
'show_graphs': show_graphs,
|
||||
})
|
||||
|
||||
@@ -73,7 +85,7 @@ class ProviderBulkImportView(PermissionRequiredMixin, BulkImportView):
|
||||
class ProviderBulkEditView(PermissionRequiredMixin, BulkEditView):
|
||||
permission_required = 'circuits.change_provider'
|
||||
queryset = Provider.objects.all()
|
||||
filter = filters.ProviderFilter
|
||||
filterset = filters.ProviderFilterSet
|
||||
table = tables.ProviderTable
|
||||
form = forms.ProviderBulkEditForm
|
||||
default_return_url = 'circuits:provider_list'
|
||||
@@ -82,7 +94,7 @@ class ProviderBulkEditView(PermissionRequiredMixin, BulkEditView):
|
||||
class ProviderBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
permission_required = 'circuits.delete_provider'
|
||||
queryset = Provider.objects.all()
|
||||
filter = filters.ProviderFilter
|
||||
filterset = filters.ProviderFilterSet
|
||||
table = tables.ProviderTable
|
||||
default_return_url = 'circuits:provider_list'
|
||||
|
||||
@@ -136,8 +148,8 @@ class CircuitListView(PermissionRequiredMixin, ObjectListView):
|
||||
a_side=Subquery(_terminations.filter(term_side='A').values('site__name')[:1]),
|
||||
z_side=Subquery(_terminations.filter(term_side='Z').values('site__name')[:1]),
|
||||
)
|
||||
filter = filters.CircuitFilter
|
||||
filter_form = forms.CircuitFilterForm
|
||||
filterset = filters.CircuitFilterSet
|
||||
filterset_form = forms.CircuitFilterForm
|
||||
table = tables.CircuitTable
|
||||
template_name = 'circuits/circuit_list.html'
|
||||
|
||||
@@ -194,7 +206,7 @@ class CircuitBulkImportView(PermissionRequiredMixin, BulkImportView):
|
||||
class CircuitBulkEditView(PermissionRequiredMixin, BulkEditView):
|
||||
permission_required = 'circuits.change_circuit'
|
||||
queryset = Circuit.objects.prefetch_related('provider', 'type', 'tenant').prefetch_related('terminations__site')
|
||||
filter = filters.CircuitFilter
|
||||
filterset = filters.CircuitFilterSet
|
||||
table = tables.CircuitTable
|
||||
form = forms.CircuitBulkEditForm
|
||||
default_return_url = 'circuits:circuit_list'
|
||||
@@ -203,7 +215,7 @@ class CircuitBulkEditView(PermissionRequiredMixin, BulkEditView):
|
||||
class CircuitBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
permission_required = 'circuits.delete_circuit'
|
||||
queryset = Circuit.objects.prefetch_related('provider', 'type', 'tenant').prefetch_related('terminations__site')
|
||||
filter = filters.CircuitFilter
|
||||
filterset = filters.CircuitFilterSet
|
||||
table = tables.CircuitTable
|
||||
default_return_url = 'circuits:circuit_list'
|
||||
|
||||
|
||||
@@ -412,6 +412,10 @@ class DeviceWithConfigContextSerializer(DeviceSerializer):
|
||||
return obj.get_config_context()
|
||||
|
||||
|
||||
class DeviceNAPALMSerializer(serializers.Serializer):
|
||||
method = serializers.DictField()
|
||||
|
||||
|
||||
class ConsoleServerPortSerializer(TaggitSerializer, ConnectedEndpointSerializer):
|
||||
device = NestedDeviceSerializer()
|
||||
type = ChoiceField(
|
||||
@@ -605,10 +609,10 @@ class InventoryItemSerializer(TaggitSerializer, ValidatedModelSerializer):
|
||||
|
||||
class CableSerializer(ValidatedModelSerializer):
|
||||
termination_a_type = ContentTypeField(
|
||||
queryset=ContentType.objects.filter(model__in=CABLE_TERMINATION_TYPES)
|
||||
queryset=ContentType.objects.filter(CABLE_TERMINATION_MODELS)
|
||||
)
|
||||
termination_b_type = ContentTypeField(
|
||||
queryset=ContentType.objects.filter(model__in=CABLE_TERMINATION_TYPES)
|
||||
queryset=ContentType.objects.filter(CABLE_TERMINATION_MODELS)
|
||||
)
|
||||
termination_a = serializers.SerializerMethodField(read_only=True)
|
||||
termination_b = serializers.SerializerMethodField(read_only=True)
|
||||
|
||||
@@ -2,8 +2,8 @@ from collections import OrderedDict
|
||||
|
||||
from django.conf import settings
|
||||
from django.db.models import Count, F
|
||||
from django.http import HttpResponseForbidden, HttpResponseBadRequest, HttpResponse
|
||||
from django.shortcuts import get_object_or_404, reverse
|
||||
from django.http import HttpResponseForbidden, HttpResponse
|
||||
from django.shortcuts import get_object_or_404
|
||||
from drf_yasg import openapi
|
||||
from drf_yasg.openapi import Parameter
|
||||
from drf_yasg.utils import swagger_auto_schema
|
||||
@@ -13,7 +13,7 @@ from rest_framework.response import Response
|
||||
from rest_framework.viewsets import GenericViewSet, ViewSet
|
||||
|
||||
from circuits.models import Circuit
|
||||
from dcim import constants, filters
|
||||
from dcim import filters
|
||||
from dcim.models import (
|
||||
Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
|
||||
DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate,
|
||||
@@ -28,7 +28,6 @@ from ipam.models import Prefix, VLAN
|
||||
from utilities.api import (
|
||||
get_serializer_for_model, IsAuthenticatedOrLoginNotRequired, FieldChoicesViewSet, ModelViewSet, ServiceUnavailable,
|
||||
)
|
||||
from utilities.custom_inspectors import NullablePaginatorInspector
|
||||
from utilities.utils import get_subquery
|
||||
from virtualization.models import VirtualMachine
|
||||
from . import serializers
|
||||
@@ -41,25 +40,26 @@ from .exceptions import MissingFilterException
|
||||
|
||||
class DCIMFieldChoicesViewSet(FieldChoicesViewSet):
|
||||
fields = (
|
||||
(Cable, ['length_unit', 'status', 'termination_a_type', 'termination_b_type', 'type']),
|
||||
(ConsolePort, ['type', 'connection_status']),
|
||||
(ConsolePortTemplate, ['type']),
|
||||
(ConsoleServerPort, ['type']),
|
||||
(ConsoleServerPortTemplate, ['type']),
|
||||
(Device, ['face', 'status']),
|
||||
(DeviceType, ['subdevice_role']),
|
||||
(FrontPort, ['type']),
|
||||
(FrontPortTemplate, ['type']),
|
||||
(Interface, ['type', 'mode']),
|
||||
(InterfaceTemplate, ['type']),
|
||||
(PowerOutlet, ['type', 'feed_leg']),
|
||||
(PowerOutletTemplate, ['type', 'feed_leg']),
|
||||
(PowerPort, ['type', 'connection_status']),
|
||||
(PowerPortTemplate, ['type']),
|
||||
(Rack, ['outer_unit', 'status', 'type', 'width']),
|
||||
(RearPort, ['type']),
|
||||
(RearPortTemplate, ['type']),
|
||||
(Site, ['status']),
|
||||
(serializers.CableSerializer, ['length_unit', 'status', 'termination_a_type', 'termination_b_type', 'type']),
|
||||
(serializers.ConsolePortSerializer, ['type', 'connection_status']),
|
||||
(serializers.ConsolePortTemplateSerializer, ['type']),
|
||||
(serializers.ConsoleServerPortSerializer, ['type']),
|
||||
(serializers.ConsoleServerPortTemplateSerializer, ['type']),
|
||||
(serializers.DeviceSerializer, ['face', 'status']),
|
||||
(serializers.DeviceTypeSerializer, ['subdevice_role']),
|
||||
(serializers.FrontPortSerializer, ['type']),
|
||||
(serializers.FrontPortTemplateSerializer, ['type']),
|
||||
(serializers.InterfaceSerializer, ['type', 'mode']),
|
||||
(serializers.InterfaceTemplateSerializer, ['type']),
|
||||
(serializers.PowerFeedSerializer, ['phase', 'status', 'supply', 'type']),
|
||||
(serializers.PowerOutletSerializer, ['type', 'feed_leg']),
|
||||
(serializers.PowerOutletTemplateSerializer, ['type', 'feed_leg']),
|
||||
(serializers.PowerPortSerializer, ['type', 'connection_status']),
|
||||
(serializers.PowerPortTemplateSerializer, ['type']),
|
||||
(serializers.RackSerializer, ['outer_unit', 'status', 'type', 'width']),
|
||||
(serializers.RearPortSerializer, ['type']),
|
||||
(serializers.RearPortTemplateSerializer, ['type']),
|
||||
(serializers.SiteSerializer, ['status']),
|
||||
)
|
||||
|
||||
|
||||
@@ -106,7 +106,7 @@ class RegionViewSet(ModelViewSet):
|
||||
site_count=Count('sites')
|
||||
)
|
||||
serializer_class = serializers.RegionSerializer
|
||||
filterset_class = filters.RegionFilter
|
||||
filterset_class = filters.RegionFilterSet
|
||||
|
||||
|
||||
#
|
||||
@@ -125,7 +125,7 @@ class SiteViewSet(CustomFieldModelViewSet):
|
||||
virtualmachine_count=get_subquery(VirtualMachine, 'cluster__site'),
|
||||
)
|
||||
serializer_class = serializers.SiteSerializer
|
||||
filterset_class = filters.SiteFilter
|
||||
filterset_class = filters.SiteFilterSet
|
||||
|
||||
@action(detail=True)
|
||||
def graphs(self, request, pk):
|
||||
@@ -147,7 +147,7 @@ class RackGroupViewSet(ModelViewSet):
|
||||
rack_count=Count('racks')
|
||||
)
|
||||
serializer_class = serializers.RackGroupSerializer
|
||||
filterset_class = filters.RackGroupFilter
|
||||
filterset_class = filters.RackGroupFilterSet
|
||||
|
||||
|
||||
#
|
||||
@@ -159,7 +159,7 @@ class RackRoleViewSet(ModelViewSet):
|
||||
rack_count=Count('racks')
|
||||
)
|
||||
serializer_class = serializers.RackRoleSerializer
|
||||
filterset_class = filters.RackRoleFilter
|
||||
filterset_class = filters.RackRoleFilterSet
|
||||
|
||||
|
||||
#
|
||||
@@ -174,7 +174,7 @@ class RackViewSet(CustomFieldModelViewSet):
|
||||
powerfeed_count=get_subquery(PowerFeed, 'rack')
|
||||
)
|
||||
serializer_class = serializers.RackSerializer
|
||||
filterset_class = filters.RackFilter
|
||||
filterset_class = filters.RackFilterSet
|
||||
|
||||
@swagger_auto_schema(deprecated=True)
|
||||
@action(detail=True)
|
||||
@@ -244,7 +244,7 @@ class RackViewSet(CustomFieldModelViewSet):
|
||||
class RackReservationViewSet(ModelViewSet):
|
||||
queryset = RackReservation.objects.prefetch_related('rack', 'user', 'tenant')
|
||||
serializer_class = serializers.RackReservationSerializer
|
||||
filterset_class = filters.RackReservationFilter
|
||||
filterset_class = filters.RackReservationFilterSet
|
||||
|
||||
# Assign user from request
|
||||
def perform_create(self, serializer):
|
||||
@@ -262,7 +262,7 @@ class ManufacturerViewSet(ModelViewSet):
|
||||
platform_count=get_subquery(Platform, 'manufacturer')
|
||||
)
|
||||
serializer_class = serializers.ManufacturerSerializer
|
||||
filterset_class = filters.ManufacturerFilter
|
||||
filterset_class = filters.ManufacturerFilterSet
|
||||
|
||||
|
||||
#
|
||||
@@ -274,7 +274,7 @@ class DeviceTypeViewSet(CustomFieldModelViewSet):
|
||||
device_count=Count('instances')
|
||||
)
|
||||
serializer_class = serializers.DeviceTypeSerializer
|
||||
filterset_class = filters.DeviceTypeFilter
|
||||
filterset_class = filters.DeviceTypeFilterSet
|
||||
|
||||
|
||||
#
|
||||
@@ -284,49 +284,49 @@ class DeviceTypeViewSet(CustomFieldModelViewSet):
|
||||
class ConsolePortTemplateViewSet(ModelViewSet):
|
||||
queryset = ConsolePortTemplate.objects.prefetch_related('device_type__manufacturer')
|
||||
serializer_class = serializers.ConsolePortTemplateSerializer
|
||||
filterset_class = filters.ConsolePortTemplateFilter
|
||||
filterset_class = filters.ConsolePortTemplateFilterSet
|
||||
|
||||
|
||||
class ConsoleServerPortTemplateViewSet(ModelViewSet):
|
||||
queryset = ConsoleServerPortTemplate.objects.prefetch_related('device_type__manufacturer')
|
||||
serializer_class = serializers.ConsoleServerPortTemplateSerializer
|
||||
filterset_class = filters.ConsoleServerPortTemplateFilter
|
||||
filterset_class = filters.ConsoleServerPortTemplateFilterSet
|
||||
|
||||
|
||||
class PowerPortTemplateViewSet(ModelViewSet):
|
||||
queryset = PowerPortTemplate.objects.prefetch_related('device_type__manufacturer')
|
||||
serializer_class = serializers.PowerPortTemplateSerializer
|
||||
filterset_class = filters.PowerPortTemplateFilter
|
||||
filterset_class = filters.PowerPortTemplateFilterSet
|
||||
|
||||
|
||||
class PowerOutletTemplateViewSet(ModelViewSet):
|
||||
queryset = PowerOutletTemplate.objects.prefetch_related('device_type__manufacturer')
|
||||
serializer_class = serializers.PowerOutletTemplateSerializer
|
||||
filterset_class = filters.PowerOutletTemplateFilter
|
||||
filterset_class = filters.PowerOutletTemplateFilterSet
|
||||
|
||||
|
||||
class InterfaceTemplateViewSet(ModelViewSet):
|
||||
queryset = InterfaceTemplate.objects.prefetch_related('device_type__manufacturer')
|
||||
serializer_class = serializers.InterfaceTemplateSerializer
|
||||
filterset_class = filters.InterfaceTemplateFilter
|
||||
filterset_class = filters.InterfaceTemplateFilterSet
|
||||
|
||||
|
||||
class FrontPortTemplateViewSet(ModelViewSet):
|
||||
queryset = FrontPortTemplate.objects.prefetch_related('device_type__manufacturer')
|
||||
serializer_class = serializers.FrontPortTemplateSerializer
|
||||
filterset_class = filters.FrontPortTemplateFilter
|
||||
filterset_class = filters.FrontPortTemplateFilterSet
|
||||
|
||||
|
||||
class RearPortTemplateViewSet(ModelViewSet):
|
||||
queryset = RearPortTemplate.objects.prefetch_related('device_type__manufacturer')
|
||||
serializer_class = serializers.RearPortTemplateSerializer
|
||||
filterset_class = filters.RearPortTemplateFilter
|
||||
filterset_class = filters.RearPortTemplateFilterSet
|
||||
|
||||
|
||||
class DeviceBayTemplateViewSet(ModelViewSet):
|
||||
queryset = DeviceBayTemplate.objects.prefetch_related('device_type__manufacturer')
|
||||
serializer_class = serializers.DeviceBayTemplateSerializer
|
||||
filterset_class = filters.DeviceBayTemplateFilter
|
||||
filterset_class = filters.DeviceBayTemplateFilterSet
|
||||
|
||||
|
||||
#
|
||||
@@ -339,7 +339,7 @@ class DeviceRoleViewSet(ModelViewSet):
|
||||
virtualmachine_count=get_subquery(VirtualMachine, 'role')
|
||||
)
|
||||
serializer_class = serializers.DeviceRoleSerializer
|
||||
filterset_class = filters.DeviceRoleFilter
|
||||
filterset_class = filters.DeviceRoleFilterSet
|
||||
|
||||
|
||||
#
|
||||
@@ -352,7 +352,7 @@ class PlatformViewSet(ModelViewSet):
|
||||
virtualmachine_count=get_subquery(VirtualMachine, 'platform')
|
||||
)
|
||||
serializer_class = serializers.PlatformSerializer
|
||||
filterset_class = filters.PlatformFilter
|
||||
filterset_class = filters.PlatformFilterSet
|
||||
|
||||
|
||||
#
|
||||
@@ -364,7 +364,7 @@ class DeviceViewSet(CustomFieldModelViewSet):
|
||||
'device_type__manufacturer', 'device_role', 'tenant', 'platform', 'site', 'rack', 'parent_bay',
|
||||
'virtual_chassis__master', 'primary_ip4__nat_outside', 'primary_ip6__nat_outside', 'tags',
|
||||
)
|
||||
filterset_class = filters.DeviceFilter
|
||||
filterset_class = filters.DeviceFilterSet
|
||||
|
||||
def get_serializer_class(self):
|
||||
"""
|
||||
@@ -397,6 +397,17 @@ class DeviceViewSet(CustomFieldModelViewSet):
|
||||
|
||||
return Response(serializer.data)
|
||||
|
||||
@swagger_auto_schema(
|
||||
manual_parameters=[
|
||||
Parameter(
|
||||
name='method',
|
||||
in_='query',
|
||||
required=True,
|
||||
type=openapi.TYPE_STRING
|
||||
)
|
||||
],
|
||||
responses={'200': serializers.DeviceNAPALMSerializer}
|
||||
)
|
||||
@action(detail=True, url_path='napalm')
|
||||
def napalm(self, request, pk):
|
||||
"""
|
||||
@@ -435,13 +446,29 @@ class DeviceViewSet(CustomFieldModelViewSet):
|
||||
napalm_methods = request.GET.getlist('method')
|
||||
response = OrderedDict([(m, None) for m in napalm_methods])
|
||||
ip_address = str(device.primary_ip.address.ip)
|
||||
username = settings.NAPALM_USERNAME
|
||||
password = settings.NAPALM_PASSWORD
|
||||
optional_args = settings.NAPALM_ARGS.copy()
|
||||
if device.platform.napalm_args is not None:
|
||||
optional_args.update(device.platform.napalm_args)
|
||||
|
||||
# Update NAPALM parameters according to the request headers
|
||||
for header in request.headers:
|
||||
if header[:9].lower() != 'x-napalm-':
|
||||
continue
|
||||
|
||||
key = header[9:]
|
||||
if key.lower() == 'username':
|
||||
username = request.headers[header]
|
||||
elif key.lower() == 'password':
|
||||
password = request.headers[header]
|
||||
elif key:
|
||||
optional_args[key.lower()] = request.headers[header]
|
||||
|
||||
d = driver(
|
||||
hostname=ip_address,
|
||||
username=settings.NAPALM_USERNAME,
|
||||
password=settings.NAPALM_PASSWORD,
|
||||
username=username,
|
||||
password=password,
|
||||
timeout=settings.NAPALM_TIMEOUT,
|
||||
optional_args=optional_args
|
||||
)
|
||||
@@ -476,13 +503,13 @@ class DeviceViewSet(CustomFieldModelViewSet):
|
||||
class ConsolePortViewSet(CableTraceMixin, ModelViewSet):
|
||||
queryset = ConsolePort.objects.prefetch_related('device', 'connected_endpoint__device', 'cable', 'tags')
|
||||
serializer_class = serializers.ConsolePortSerializer
|
||||
filterset_class = filters.ConsolePortFilter
|
||||
filterset_class = filters.ConsolePortFilterSet
|
||||
|
||||
|
||||
class ConsoleServerPortViewSet(CableTraceMixin, ModelViewSet):
|
||||
queryset = ConsoleServerPort.objects.prefetch_related('device', 'connected_endpoint__device', 'cable', 'tags')
|
||||
serializer_class = serializers.ConsoleServerPortSerializer
|
||||
filterset_class = filters.ConsoleServerPortFilter
|
||||
filterset_class = filters.ConsoleServerPortFilterSet
|
||||
|
||||
|
||||
class PowerPortViewSet(CableTraceMixin, ModelViewSet):
|
||||
@@ -490,13 +517,13 @@ class PowerPortViewSet(CableTraceMixin, ModelViewSet):
|
||||
'device', '_connected_poweroutlet__device', '_connected_powerfeed', 'cable', 'tags'
|
||||
)
|
||||
serializer_class = serializers.PowerPortSerializer
|
||||
filterset_class = filters.PowerPortFilter
|
||||
filterset_class = filters.PowerPortFilterSet
|
||||
|
||||
|
||||
class PowerOutletViewSet(CableTraceMixin, ModelViewSet):
|
||||
queryset = PowerOutlet.objects.prefetch_related('device', 'connected_endpoint__device', 'cable', 'tags')
|
||||
serializer_class = serializers.PowerOutletSerializer
|
||||
filterset_class = filters.PowerOutletFilter
|
||||
filterset_class = filters.PowerOutletFilterSet
|
||||
|
||||
|
||||
class InterfaceViewSet(CableTraceMixin, ModelViewSet):
|
||||
@@ -506,7 +533,7 @@ class InterfaceViewSet(CableTraceMixin, ModelViewSet):
|
||||
device__isnull=False
|
||||
)
|
||||
serializer_class = serializers.InterfaceSerializer
|
||||
filterset_class = filters.InterfaceFilter
|
||||
filterset_class = filters.InterfaceFilterSet
|
||||
|
||||
@action(detail=True)
|
||||
def graphs(self, request, pk):
|
||||
@@ -522,25 +549,25 @@ class InterfaceViewSet(CableTraceMixin, ModelViewSet):
|
||||
class FrontPortViewSet(ModelViewSet):
|
||||
queryset = FrontPort.objects.prefetch_related('device__device_type__manufacturer', 'rear_port', 'cable', 'tags')
|
||||
serializer_class = serializers.FrontPortSerializer
|
||||
filterset_class = filters.FrontPortFilter
|
||||
filterset_class = filters.FrontPortFilterSet
|
||||
|
||||
|
||||
class RearPortViewSet(ModelViewSet):
|
||||
queryset = RearPort.objects.prefetch_related('device__device_type__manufacturer', 'cable', 'tags')
|
||||
serializer_class = serializers.RearPortSerializer
|
||||
filterset_class = filters.RearPortFilter
|
||||
filterset_class = filters.RearPortFilterSet
|
||||
|
||||
|
||||
class DeviceBayViewSet(ModelViewSet):
|
||||
queryset = DeviceBay.objects.prefetch_related('installed_device').prefetch_related('tags')
|
||||
serializer_class = serializers.DeviceBaySerializer
|
||||
filterset_class = filters.DeviceBayFilter
|
||||
filterset_class = filters.DeviceBayFilterSet
|
||||
|
||||
|
||||
class InventoryItemViewSet(ModelViewSet):
|
||||
queryset = InventoryItem.objects.prefetch_related('device', 'manufacturer').prefetch_related('tags')
|
||||
serializer_class = serializers.InventoryItemSerializer
|
||||
filterset_class = filters.InventoryItemFilter
|
||||
filterset_class = filters.InventoryItemFilterSet
|
||||
|
||||
|
||||
#
|
||||
@@ -554,7 +581,7 @@ class ConsoleConnectionViewSet(ListModelMixin, GenericViewSet):
|
||||
connected_endpoint__isnull=False
|
||||
)
|
||||
serializer_class = serializers.ConsolePortSerializer
|
||||
filterset_class = filters.ConsoleConnectionFilter
|
||||
filterset_class = filters.ConsoleConnectionFilterSet
|
||||
|
||||
|
||||
class PowerConnectionViewSet(ListModelMixin, GenericViewSet):
|
||||
@@ -564,7 +591,7 @@ class PowerConnectionViewSet(ListModelMixin, GenericViewSet):
|
||||
_connected_poweroutlet__isnull=False
|
||||
)
|
||||
serializer_class = serializers.PowerPortSerializer
|
||||
filterset_class = filters.PowerConnectionFilter
|
||||
filterset_class = filters.PowerConnectionFilterSet
|
||||
|
||||
|
||||
class InterfaceConnectionViewSet(ListModelMixin, GenericViewSet):
|
||||
@@ -576,7 +603,7 @@ class InterfaceConnectionViewSet(ListModelMixin, GenericViewSet):
|
||||
pk__lt=F('_connected_interface')
|
||||
)
|
||||
serializer_class = serializers.InterfaceConnectionSerializer
|
||||
filterset_class = filters.InterfaceConnectionFilter
|
||||
filterset_class = filters.InterfaceConnectionFilterSet
|
||||
|
||||
|
||||
#
|
||||
@@ -588,7 +615,7 @@ class CableViewSet(ModelViewSet):
|
||||
'termination_a', 'termination_b'
|
||||
)
|
||||
serializer_class = serializers.CableSerializer
|
||||
filterset_class = filters.CableFilter
|
||||
filterset_class = filters.CableFilterSet
|
||||
|
||||
|
||||
#
|
||||
@@ -600,7 +627,7 @@ class VirtualChassisViewSet(ModelViewSet):
|
||||
member_count=Count('members')
|
||||
)
|
||||
serializer_class = serializers.VirtualChassisSerializer
|
||||
filterset_class = filters.VirtualChassisFilter
|
||||
filterset_class = filters.VirtualChassisFilterSet
|
||||
|
||||
|
||||
#
|
||||
@@ -614,7 +641,7 @@ class PowerPanelViewSet(ModelViewSet):
|
||||
powerfeed_count=Count('powerfeeds')
|
||||
)
|
||||
serializer_class = serializers.PowerPanelSerializer
|
||||
filterset_class = filters.PowerPanelFilter
|
||||
filterset_class = filters.PowerPanelFilterSet
|
||||
|
||||
|
||||
#
|
||||
@@ -624,7 +651,7 @@ class PowerPanelViewSet(ModelViewSet):
|
||||
class PowerFeedViewSet(CustomFieldModelViewSet):
|
||||
queryset = PowerFeed.objects.prefetch_related('power_panel', 'rack', 'tags')
|
||||
serializer_class = serializers.PowerFeedSerializer
|
||||
filterset_class = filters.PowerFeedFilter
|
||||
filterset_class = filters.PowerFeedFilterSet
|
||||
|
||||
|
||||
#
|
||||
|
||||
@@ -195,6 +195,7 @@ class ConsolePortTypeChoices(ChoiceSet):
|
||||
|
||||
TYPE_DE9 = 'de-9'
|
||||
TYPE_DB25 = 'db-25'
|
||||
TYPE_RJ12 = 'rj-12'
|
||||
TYPE_RJ45 = 'rj-45'
|
||||
TYPE_USB_A = 'usb-a'
|
||||
TYPE_USB_B = 'usb-b'
|
||||
@@ -209,6 +210,7 @@ class ConsolePortTypeChoices(ChoiceSet):
|
||||
('Serial', (
|
||||
(TYPE_DE9, 'DE-9'),
|
||||
(TYPE_DB25, 'DB-25'),
|
||||
(TYPE_RJ12, 'RJ-12'),
|
||||
(TYPE_RJ45, 'RJ-45'),
|
||||
)),
|
||||
('USB', (
|
||||
@@ -268,6 +270,13 @@ class PowerPortTypeChoices(ChoiceSet):
|
||||
TYPE_NEMA_L620P = 'nema-l6-20p'
|
||||
TYPE_NEMA_L630P = 'nema-l6-30p'
|
||||
TYPE_NEMA_L650P = 'nema-l6-50p'
|
||||
# California style
|
||||
TYPE_CS6361C = 'cs6361c'
|
||||
TYPE_CS6365C = 'cs6365c'
|
||||
TYPE_CS8165C = 'cs8165c'
|
||||
TYPE_CS8265C = 'cs8265c'
|
||||
TYPE_CS8365C = 'cs8365c'
|
||||
TYPE_CS8465C = 'cs8465c'
|
||||
# ITA/international
|
||||
TYPE_ITA_E = 'ita-e'
|
||||
TYPE_ITA_F = 'ita-f'
|
||||
@@ -323,6 +332,14 @@ class PowerPortTypeChoices(ChoiceSet):
|
||||
(TYPE_NEMA_L630P, 'NEMA L6-30P'),
|
||||
(TYPE_NEMA_L650P, 'NEMA L6-50P'),
|
||||
)),
|
||||
('California Style', (
|
||||
(TYPE_CS6361C, 'CS6361C'),
|
||||
(TYPE_CS6365C, 'CS6365C'),
|
||||
(TYPE_CS8165C, 'CS8165C'),
|
||||
(TYPE_CS8265C, 'CS8265C'),
|
||||
(TYPE_CS8365C, 'CS8365C'),
|
||||
(TYPE_CS8465C, 'CS8465C'),
|
||||
)),
|
||||
('International/ITA', (
|
||||
(TYPE_ITA_E, 'ITA Type E (CEE 7/5)'),
|
||||
(TYPE_ITA_F, 'ITA Type F (CEE 7/4)'),
|
||||
@@ -382,6 +399,13 @@ class PowerOutletTypeChoices(ChoiceSet):
|
||||
TYPE_NEMA_L620R = 'nema-l6-20r'
|
||||
TYPE_NEMA_L630R = 'nema-l6-30r'
|
||||
TYPE_NEMA_L650R = 'nema-l6-50r'
|
||||
# California style
|
||||
TYPE_CS6360C = 'CS6360C'
|
||||
TYPE_CS6364C = 'CS6364C'
|
||||
TYPE_CS8164C = 'CS8164C'
|
||||
TYPE_CS8264C = 'CS8264C'
|
||||
TYPE_CS8364C = 'CS8364C'
|
||||
TYPE_CS8464C = 'CS8464C'
|
||||
# ITA/international
|
||||
TYPE_ITA_E = 'ita-e'
|
||||
TYPE_ITA_F = 'ita-f'
|
||||
@@ -436,6 +460,14 @@ class PowerOutletTypeChoices(ChoiceSet):
|
||||
(TYPE_NEMA_L630R, 'NEMA L6-30R'),
|
||||
(TYPE_NEMA_L650R, 'NEMA L6-50R'),
|
||||
)),
|
||||
('California Style', (
|
||||
(TYPE_CS6360C, 'CS6360C'),
|
||||
(TYPE_CS6364C, 'CS6364C'),
|
||||
(TYPE_CS8164C, 'CS8164C'),
|
||||
(TYPE_CS8264C, 'CS8264C'),
|
||||
(TYPE_CS8364C, 'CS8364C'),
|
||||
(TYPE_CS8464C, 'CS8464C'),
|
||||
)),
|
||||
('ITA/International', (
|
||||
(TYPE_ITA_E, 'ITA Type E (CEE7/5)'),
|
||||
(TYPE_ITA_F, 'ITA Type F (CEE7/3)'),
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
from django.db.models import Q
|
||||
|
||||
from .choices import InterfaceTypeChoices
|
||||
|
||||
|
||||
#
|
||||
# Rack elevation rendering
|
||||
#
|
||||
|
||||
RACK_ELEVATION_UNIT_WIDTH_DEFAULT = 230
|
||||
RACK_ELEVATION_UNIT_HEIGHT_DEFAULT = 20
|
||||
|
||||
|
||||
#
|
||||
# Interface type groups
|
||||
#
|
||||
@@ -20,6 +30,12 @@ WIRELESS_IFACE_TYPES = [
|
||||
|
||||
NONCONNECTABLE_IFACE_TYPES = VIRTUAL_IFACE_TYPES + WIRELESS_IFACE_TYPES
|
||||
|
||||
|
||||
#
|
||||
# Cabling and connections
|
||||
#
|
||||
|
||||
# TODO: Replace with CableStatusChoices?
|
||||
# Console/power/interface connection statuses
|
||||
CONNECTION_STATUS_PLANNED = False
|
||||
CONNECTION_STATUS_CONNECTED = True
|
||||
@@ -29,21 +45,21 @@ CONNECTION_STATUS_CHOICES = [
|
||||
]
|
||||
|
||||
# Cable endpoint types
|
||||
CABLE_TERMINATION_TYPES = [
|
||||
'consoleport', 'consoleserverport', 'interface', 'poweroutlet', 'powerport', 'frontport', 'rearport',
|
||||
'circuittermination', 'powerfeed',
|
||||
]
|
||||
|
||||
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'),
|
||||
}
|
||||
CABLE_TERMINATION_MODELS = Q(
|
||||
Q(app_label='circuits', model__in=(
|
||||
'circuittermination',
|
||||
)) |
|
||||
Q(app_label='dcim', model__in=(
|
||||
'consoleport',
|
||||
'consoleserverport',
|
||||
'frontport',
|
||||
'interface',
|
||||
'powerfeed',
|
||||
'poweroutlet',
|
||||
'powerport',
|
||||
'rearport',
|
||||
))
|
||||
)
|
||||
|
||||
COMPATIBLE_TERMINATION_TYPES = {
|
||||
'consoleport': ['consoleserverport', 'frontport', 'rearport'],
|
||||
@@ -55,69 +71,3 @@ COMPATIBLE_TERMINATION_TYPES = {
|
||||
'rearport': ['consoleport', 'consoleserverport', 'interface', 'frontport', 'rearport', 'circuittermination'],
|
||||
'circuittermination': ['interface', 'frontport', 'rearport'],
|
||||
}
|
||||
|
||||
|
||||
RACK_ELEVATION_STYLE = """
|
||||
* {
|
||||
font-family: sans-serif;
|
||||
font-size: 13px;
|
||||
}
|
||||
rect {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
text {
|
||||
text-anchor: middle;
|
||||
dominant-baseline: middle;
|
||||
}
|
||||
.rack {
|
||||
background-color: #f0f0f0;
|
||||
fill: none;
|
||||
stroke: black;
|
||||
stroke-width: 3px;
|
||||
}
|
||||
.slot {
|
||||
fill: #f7f7f7;
|
||||
stroke: #a0a0a0;
|
||||
}
|
||||
.slot:hover {
|
||||
fill: #fff;
|
||||
}
|
||||
.slot+.add-device {
|
||||
fill: none;
|
||||
}
|
||||
.slot:hover+.add-device {
|
||||
fill: blue;
|
||||
}
|
||||
.add-device:hover {
|
||||
fill: blue;
|
||||
}
|
||||
.add-device:hover+.slot {
|
||||
fill: #fff;
|
||||
}
|
||||
.reserved {
|
||||
fill: url(#reserved);
|
||||
}
|
||||
.reserved:hover {
|
||||
fill: url(#reserved);
|
||||
}
|
||||
.occupied {
|
||||
fill: url(#occupied);
|
||||
}
|
||||
.occupied:hover {
|
||||
fill: url(#occupied);
|
||||
}
|
||||
.blocked {
|
||||
fill: url(#blocked);
|
||||
}
|
||||
.blocked:hover {
|
||||
fill: url(#blocked);
|
||||
}
|
||||
.blocked:hover+.add-device {
|
||||
fill: none;
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
# Rack Elevation SVG Size
|
||||
RACK_ELEVATION_UNIT_WIDTH_DEFAULT = 230
|
||||
RACK_ELEVATION_UNIT_HEIGHT_DEFAULT = 20
|
||||
|
||||
@@ -3,14 +3,24 @@ from django.core.validators import MinValueValidator, MaxValueValidator
|
||||
from django.db import models
|
||||
from netaddr import AddrFormatError, EUI, mac_unix_expanded
|
||||
|
||||
from ipam.constants import BGP_ASN_MAX, BGP_ASN_MIN
|
||||
|
||||
|
||||
class ASNField(models.BigIntegerField):
|
||||
description = "32-bit ASN field"
|
||||
default_validators = [
|
||||
MinValueValidator(1),
|
||||
MaxValueValidator(4294967295),
|
||||
MinValueValidator(BGP_ASN_MIN),
|
||||
MaxValueValidator(BGP_ASN_MAX),
|
||||
]
|
||||
|
||||
def formfield(self, **kwargs):
|
||||
defaults = {
|
||||
'min_value': BGP_ASN_MIN,
|
||||
'max_value': BGP_ASN_MAX,
|
||||
}
|
||||
defaults.update(**kwargs)
|
||||
return super().formfield(**defaults)
|
||||
|
||||
|
||||
class mac_unix_expanded_uppercase(mac_unix_expanded):
|
||||
word_fmt = '%.2X'
|
||||
|
||||
@@ -2,8 +2,8 @@ import django_filters
|
||||
from django.contrib.auth.models import User
|
||||
from django.db.models import Q
|
||||
|
||||
from extras.filters import CustomFieldFilterSet, LocalConfigContextFilter, CreatedUpdatedFilterSet
|
||||
from tenancy.filtersets import TenancyFilterSet
|
||||
from extras.filters import CustomFieldFilterSet, LocalConfigContextFilterSet, CreatedUpdatedFilterSet
|
||||
from tenancy.filters import TenancyFilterSet
|
||||
from tenancy.models import Tenant
|
||||
from utilities.constants import COLOR_CHOICES
|
||||
from utilities.filters import (
|
||||
@@ -22,7 +22,46 @@ from .models import (
|
||||
)
|
||||
|
||||
|
||||
class RegionFilter(NameSlugSearchFilterSet):
|
||||
__all__ = (
|
||||
'CableFilterSet',
|
||||
'ConsoleConnectionFilterSet',
|
||||
'ConsolePortFilterSet',
|
||||
'ConsolePortTemplateFilterSet',
|
||||
'ConsoleServerPortFilterSet',
|
||||
'ConsoleServerPortTemplateFilterSet',
|
||||
'DeviceBayFilterSet',
|
||||
'DeviceBayTemplateFilterSet',
|
||||
'DeviceFilterSet',
|
||||
'DeviceRoleFilterSet',
|
||||
'DeviceTypeFilterSet',
|
||||
'FrontPortFilterSet',
|
||||
'FrontPortTemplateFilterSet',
|
||||
'InterfaceConnectionFilterSet',
|
||||
'InterfaceFilterSet',
|
||||
'InterfaceTemplateFilterSet',
|
||||
'InventoryItemFilterSet',
|
||||
'ManufacturerFilterSet',
|
||||
'PlatformFilterSet',
|
||||
'PowerConnectionFilterSet',
|
||||
'PowerFeedFilterSet',
|
||||
'PowerOutletFilterSet',
|
||||
'PowerOutletTemplateFilterSet',
|
||||
'PowerPanelFilterSet',
|
||||
'PowerPortFilterSet',
|
||||
'PowerPortTemplateFilterSet',
|
||||
'RackFilterSet',
|
||||
'RackGroupFilterSet',
|
||||
'RackReservationFilterSet',
|
||||
'RackRoleFilterSet',
|
||||
'RearPortFilterSet',
|
||||
'RearPortTemplateFilterSet',
|
||||
'RegionFilterSet',
|
||||
'SiteFilterSet',
|
||||
'VirtualChassisFilterSet',
|
||||
)
|
||||
|
||||
|
||||
class RegionFilterSet(NameSlugSearchFilterSet):
|
||||
parent_id = django_filters.ModelMultipleChoiceFilter(
|
||||
queryset=Region.objects.all(),
|
||||
label='Parent region (ID)',
|
||||
@@ -39,7 +78,7 @@ class RegionFilter(NameSlugSearchFilterSet):
|
||||
fields = ['id', 'name', 'slug']
|
||||
|
||||
|
||||
class SiteFilter(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet):
|
||||
class SiteFilterSet(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet):
|
||||
id__in = NumericInFilter(
|
||||
field_name='id',
|
||||
lookup_expr='in'
|
||||
@@ -93,7 +132,18 @@ class SiteFilter(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet
|
||||
return queryset.filter(qs_filter)
|
||||
|
||||
|
||||
class RackGroupFilter(NameSlugSearchFilterSet):
|
||||
class RackGroupFilterSet(NameSlugSearchFilterSet):
|
||||
region_id = TreeNodeMultipleChoiceFilter(
|
||||
queryset=Region.objects.all(),
|
||||
field_name='site__region__in',
|
||||
label='Region (ID)',
|
||||
)
|
||||
region = TreeNodeMultipleChoiceFilter(
|
||||
queryset=Region.objects.all(),
|
||||
field_name='site__region__in',
|
||||
to_field_name='slug',
|
||||
label='Region (slug)',
|
||||
)
|
||||
site_id = django_filters.ModelMultipleChoiceFilter(
|
||||
queryset=Site.objects.all(),
|
||||
label='Site (ID)',
|
||||
@@ -110,14 +160,14 @@ class RackGroupFilter(NameSlugSearchFilterSet):
|
||||
fields = ['id', 'name', 'slug']
|
||||
|
||||
|
||||
class RackRoleFilter(NameSlugSearchFilterSet):
|
||||
class RackRoleFilterSet(NameSlugSearchFilterSet):
|
||||
|
||||
class Meta:
|
||||
model = RackRole
|
||||
fields = ['id', 'name', 'slug', 'color']
|
||||
|
||||
|
||||
class RackFilter(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet):
|
||||
class RackFilterSet(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet):
|
||||
id__in = NumericInFilter(
|
||||
field_name='id',
|
||||
lookup_expr='in'
|
||||
@@ -126,6 +176,17 @@ class RackFilter(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet
|
||||
method='search',
|
||||
label='Search',
|
||||
)
|
||||
region_id = TreeNodeMultipleChoiceFilter(
|
||||
queryset=Region.objects.all(),
|
||||
field_name='site__region__in',
|
||||
label='Region (ID)',
|
||||
)
|
||||
region = TreeNodeMultipleChoiceFilter(
|
||||
queryset=Region.objects.all(),
|
||||
field_name='site__region__in',
|
||||
to_field_name='slug',
|
||||
label='Region (slug)',
|
||||
)
|
||||
site_id = django_filters.ModelMultipleChoiceFilter(
|
||||
queryset=Site.objects.all(),
|
||||
label='Site (ID)',
|
||||
@@ -184,7 +245,7 @@ class RackFilter(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet
|
||||
)
|
||||
|
||||
|
||||
class RackReservationFilter(TenancyFilterSet):
|
||||
class RackReservationFilterSet(TenancyFilterSet):
|
||||
id__in = NumericInFilter(
|
||||
field_name='id',
|
||||
lookup_expr='in'
|
||||
@@ -245,14 +306,14 @@ class RackReservationFilter(TenancyFilterSet):
|
||||
)
|
||||
|
||||
|
||||
class ManufacturerFilter(NameSlugSearchFilterSet):
|
||||
class ManufacturerFilterSet(NameSlugSearchFilterSet):
|
||||
|
||||
class Meta:
|
||||
model = Manufacturer
|
||||
fields = ['id', 'name', 'slug']
|
||||
|
||||
|
||||
class DeviceTypeFilter(CustomFieldFilterSet, CreatedUpdatedFilterSet):
|
||||
class DeviceTypeFilterSet(CustomFieldFilterSet, CreatedUpdatedFilterSet):
|
||||
id__in = NumericInFilter(
|
||||
field_name='id',
|
||||
lookup_expr='in'
|
||||
@@ -343,70 +404,70 @@ class DeviceTypeComponentFilterSet(NameSlugSearchFilterSet):
|
||||
)
|
||||
|
||||
|
||||
class ConsolePortTemplateFilter(DeviceTypeComponentFilterSet):
|
||||
class ConsolePortTemplateFilterSet(DeviceTypeComponentFilterSet):
|
||||
|
||||
class Meta:
|
||||
model = ConsolePortTemplate
|
||||
fields = ['id', 'name', 'type']
|
||||
|
||||
|
||||
class ConsoleServerPortTemplateFilter(DeviceTypeComponentFilterSet):
|
||||
class ConsoleServerPortTemplateFilterSet(DeviceTypeComponentFilterSet):
|
||||
|
||||
class Meta:
|
||||
model = ConsoleServerPortTemplate
|
||||
fields = ['id', 'name', 'type']
|
||||
|
||||
|
||||
class PowerPortTemplateFilter(DeviceTypeComponentFilterSet):
|
||||
class PowerPortTemplateFilterSet(DeviceTypeComponentFilterSet):
|
||||
|
||||
class Meta:
|
||||
model = PowerPortTemplate
|
||||
fields = ['id', 'name', 'type', 'maximum_draw', 'allocated_draw']
|
||||
|
||||
|
||||
class PowerOutletTemplateFilter(DeviceTypeComponentFilterSet):
|
||||
class PowerOutletTemplateFilterSet(DeviceTypeComponentFilterSet):
|
||||
|
||||
class Meta:
|
||||
model = PowerOutletTemplate
|
||||
fields = ['id', 'name', 'type', 'feed_leg']
|
||||
|
||||
|
||||
class InterfaceTemplateFilter(DeviceTypeComponentFilterSet):
|
||||
class InterfaceTemplateFilterSet(DeviceTypeComponentFilterSet):
|
||||
|
||||
class Meta:
|
||||
model = InterfaceTemplate
|
||||
fields = ['id', 'name', 'type', 'mgmt_only']
|
||||
|
||||
|
||||
class FrontPortTemplateFilter(DeviceTypeComponentFilterSet):
|
||||
class FrontPortTemplateFilterSet(DeviceTypeComponentFilterSet):
|
||||
|
||||
class Meta:
|
||||
model = FrontPortTemplate
|
||||
fields = ['id', 'name', 'type']
|
||||
|
||||
|
||||
class RearPortTemplateFilter(DeviceTypeComponentFilterSet):
|
||||
class RearPortTemplateFilterSet(DeviceTypeComponentFilterSet):
|
||||
|
||||
class Meta:
|
||||
model = RearPortTemplate
|
||||
fields = ['id', 'name', 'type', 'positions']
|
||||
|
||||
|
||||
class DeviceBayTemplateFilter(DeviceTypeComponentFilterSet):
|
||||
class DeviceBayTemplateFilterSet(DeviceTypeComponentFilterSet):
|
||||
|
||||
class Meta:
|
||||
model = DeviceBayTemplate
|
||||
fields = ['id', 'name']
|
||||
|
||||
|
||||
class DeviceRoleFilter(NameSlugSearchFilterSet):
|
||||
class DeviceRoleFilterSet(NameSlugSearchFilterSet):
|
||||
|
||||
class Meta:
|
||||
model = DeviceRole
|
||||
fields = ['id', 'name', 'slug', 'color', 'vm_role']
|
||||
|
||||
|
||||
class PlatformFilter(NameSlugSearchFilterSet):
|
||||
class PlatformFilterSet(NameSlugSearchFilterSet):
|
||||
manufacturer_id = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='manufacturer',
|
||||
queryset=Manufacturer.objects.all(),
|
||||
@@ -424,7 +485,7 @@ class PlatformFilter(NameSlugSearchFilterSet):
|
||||
fields = ['id', 'name', 'slug', 'napalm_driver']
|
||||
|
||||
|
||||
class DeviceFilter(LocalConfigContextFilter, TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet):
|
||||
class DeviceFilterSet(LocalConfigContextFilterSet, TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet):
|
||||
id__in = NumericInFilter(
|
||||
field_name='id',
|
||||
lookup_expr='in'
|
||||
@@ -645,7 +706,8 @@ class DeviceComponentFilterSet(django_filters.FilterSet):
|
||||
queryset=Device.objects.all(),
|
||||
label='Device (ID)',
|
||||
)
|
||||
device = django_filters.ModelChoiceFilter(
|
||||
device = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='device__name',
|
||||
queryset=Device.objects.all(),
|
||||
to_field_name='name',
|
||||
label='Device (name)',
|
||||
@@ -661,7 +723,7 @@ class DeviceComponentFilterSet(django_filters.FilterSet):
|
||||
)
|
||||
|
||||
|
||||
class ConsolePortFilter(DeviceComponentFilterSet):
|
||||
class ConsolePortFilterSet(DeviceComponentFilterSet):
|
||||
type = django_filters.MultipleChoiceFilter(
|
||||
choices=ConsolePortTypeChoices,
|
||||
null_value=None
|
||||
@@ -677,7 +739,7 @@ class ConsolePortFilter(DeviceComponentFilterSet):
|
||||
fields = ['id', 'name', 'description', 'connection_status']
|
||||
|
||||
|
||||
class ConsoleServerPortFilter(DeviceComponentFilterSet):
|
||||
class ConsoleServerPortFilterSet(DeviceComponentFilterSet):
|
||||
type = django_filters.MultipleChoiceFilter(
|
||||
choices=ConsolePortTypeChoices,
|
||||
null_value=None
|
||||
@@ -693,7 +755,7 @@ class ConsoleServerPortFilter(DeviceComponentFilterSet):
|
||||
fields = ['id', 'name', 'description', 'connection_status']
|
||||
|
||||
|
||||
class PowerPortFilter(DeviceComponentFilterSet):
|
||||
class PowerPortFilterSet(DeviceComponentFilterSet):
|
||||
type = django_filters.MultipleChoiceFilter(
|
||||
choices=PowerPortTypeChoices,
|
||||
null_value=None
|
||||
@@ -709,7 +771,7 @@ class PowerPortFilter(DeviceComponentFilterSet):
|
||||
fields = ['id', 'name', 'maximum_draw', 'allocated_draw', 'description', 'connection_status']
|
||||
|
||||
|
||||
class PowerOutletFilter(DeviceComponentFilterSet):
|
||||
class PowerOutletFilterSet(DeviceComponentFilterSet):
|
||||
type = django_filters.MultipleChoiceFilter(
|
||||
choices=PowerOutletTypeChoices,
|
||||
null_value=None
|
||||
@@ -725,7 +787,7 @@ class PowerOutletFilter(DeviceComponentFilterSet):
|
||||
fields = ['id', 'name', 'feed_leg', 'description', 'connection_status']
|
||||
|
||||
|
||||
class InterfaceFilter(django_filters.FilterSet):
|
||||
class InterfaceFilterSet(django_filters.FilterSet):
|
||||
"""
|
||||
Not using DeviceComponentFilterSet for Interfaces because we need to check for VirtualChassis membership.
|
||||
"""
|
||||
@@ -853,7 +915,7 @@ class InterfaceFilter(django_filters.FilterSet):
|
||||
}.get(value, queryset.none())
|
||||
|
||||
|
||||
class FrontPortFilter(DeviceComponentFilterSet):
|
||||
class FrontPortFilterSet(DeviceComponentFilterSet):
|
||||
cabled = django_filters.BooleanFilter(
|
||||
field_name='cable',
|
||||
lookup_expr='isnull',
|
||||
@@ -865,7 +927,7 @@ class FrontPortFilter(DeviceComponentFilterSet):
|
||||
fields = ['id', 'name', 'type', 'description']
|
||||
|
||||
|
||||
class RearPortFilter(DeviceComponentFilterSet):
|
||||
class RearPortFilterSet(DeviceComponentFilterSet):
|
||||
cabled = django_filters.BooleanFilter(
|
||||
field_name='cable',
|
||||
lookup_expr='isnull',
|
||||
@@ -877,18 +939,40 @@ class RearPortFilter(DeviceComponentFilterSet):
|
||||
fields = ['id', 'name', 'type', 'positions', 'description']
|
||||
|
||||
|
||||
class DeviceBayFilter(DeviceComponentFilterSet):
|
||||
class DeviceBayFilterSet(DeviceComponentFilterSet):
|
||||
|
||||
class Meta:
|
||||
model = DeviceBay
|
||||
fields = ['id', 'name', 'description']
|
||||
|
||||
|
||||
class InventoryItemFilter(DeviceComponentFilterSet):
|
||||
class InventoryItemFilterSet(DeviceComponentFilterSet):
|
||||
q = django_filters.CharFilter(
|
||||
method='search',
|
||||
label='Search',
|
||||
)
|
||||
region_id = TreeNodeMultipleChoiceFilter(
|
||||
queryset=Region.objects.all(),
|
||||
field_name='device__site__region__in',
|
||||
label='Region (ID)',
|
||||
)
|
||||
region = TreeNodeMultipleChoiceFilter(
|
||||
queryset=Region.objects.all(),
|
||||
field_name='device__site__region__in',
|
||||
to_field_name='slug',
|
||||
label='Region (slug)',
|
||||
)
|
||||
site_id = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='device__site',
|
||||
queryset=Site.objects.all(),
|
||||
label='Site (ID)',
|
||||
)
|
||||
site = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='device__site__slug',
|
||||
queryset=Site.objects.all(),
|
||||
to_field_name='slug',
|
||||
label='Site name (slug)',
|
||||
)
|
||||
device_id = django_filters.ModelChoiceFilter(
|
||||
queryset=Device.objects.all(),
|
||||
label='Device (ID)',
|
||||
@@ -926,18 +1010,29 @@ class InventoryItemFilter(DeviceComponentFilterSet):
|
||||
qs_filter = (
|
||||
Q(name__icontains=value) |
|
||||
Q(part_id__icontains=value) |
|
||||
Q(serial__iexact=value) |
|
||||
Q(asset_tag__iexact=value) |
|
||||
Q(serial__icontains=value) |
|
||||
Q(asset_tag__icontains=value) |
|
||||
Q(description__icontains=value)
|
||||
)
|
||||
return queryset.filter(qs_filter)
|
||||
|
||||
|
||||
class VirtualChassisFilter(django_filters.FilterSet):
|
||||
class VirtualChassisFilterSet(django_filters.FilterSet):
|
||||
q = django_filters.CharFilter(
|
||||
method='search',
|
||||
label='Search',
|
||||
)
|
||||
region_id = TreeNodeMultipleChoiceFilter(
|
||||
queryset=Region.objects.all(),
|
||||
field_name='master__site__region__in',
|
||||
label='Region (ID)',
|
||||
)
|
||||
region = TreeNodeMultipleChoiceFilter(
|
||||
queryset=Region.objects.all(),
|
||||
field_name='master__site__region__in',
|
||||
to_field_name='slug',
|
||||
label='Region (slug)',
|
||||
)
|
||||
site_id = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='master__site',
|
||||
queryset=Site.objects.all(),
|
||||
@@ -976,7 +1071,7 @@ class VirtualChassisFilter(django_filters.FilterSet):
|
||||
return queryset.filter(qs_filter)
|
||||
|
||||
|
||||
class CableFilter(django_filters.FilterSet):
|
||||
class CableFilterSet(django_filters.FilterSet):
|
||||
q = django_filters.CharFilter(
|
||||
method='search',
|
||||
label='Search',
|
||||
@@ -993,7 +1088,7 @@ class CableFilter(django_filters.FilterSet):
|
||||
device_id = MultiValueNumberFilter(
|
||||
method='filter_device'
|
||||
)
|
||||
device = MultiValueNumberFilter(
|
||||
device = MultiValueCharFilter(
|
||||
method='filter_device',
|
||||
field_name='device__name'
|
||||
)
|
||||
@@ -1013,6 +1108,14 @@ class CableFilter(django_filters.FilterSet):
|
||||
method='filter_device',
|
||||
field_name='device__site__slug'
|
||||
)
|
||||
tenant_id = MultiValueNumberFilter(
|
||||
method='filter_device',
|
||||
field_name='device__tenant_id'
|
||||
)
|
||||
tenant = MultiValueNumberFilter(
|
||||
method='filter_device',
|
||||
field_name='device__tenant__slug'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Cable
|
||||
@@ -1031,14 +1134,17 @@ class CableFilter(django_filters.FilterSet):
|
||||
return queryset
|
||||
|
||||
|
||||
class ConsoleConnectionFilter(django_filters.FilterSet):
|
||||
class ConsoleConnectionFilterSet(django_filters.FilterSet):
|
||||
site = django_filters.CharFilter(
|
||||
method='filter_site',
|
||||
label='Site (slug)',
|
||||
)
|
||||
device = django_filters.CharFilter(
|
||||
device_id = MultiValueNumberFilter(
|
||||
method='filter_device'
|
||||
)
|
||||
device = MultiValueCharFilter(
|
||||
method='filter_device',
|
||||
label='Device',
|
||||
field_name='device__name'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
@@ -1051,22 +1157,25 @@ class ConsoleConnectionFilter(django_filters.FilterSet):
|
||||
return queryset.filter(connected_endpoint__device__site__slug=value)
|
||||
|
||||
def filter_device(self, queryset, name, value):
|
||||
if not value.strip():
|
||||
if not value:
|
||||
return queryset
|
||||
return queryset.filter(
|
||||
Q(device__name__icontains=value) |
|
||||
Q(connected_endpoint__device__name__icontains=value)
|
||||
Q(**{'{}__in'.format(name): value}) |
|
||||
Q(**{'connected_endpoint__{}__in'.format(name): value})
|
||||
)
|
||||
|
||||
|
||||
class PowerConnectionFilter(django_filters.FilterSet):
|
||||
class PowerConnectionFilterSet(django_filters.FilterSet):
|
||||
site = django_filters.CharFilter(
|
||||
method='filter_site',
|
||||
label='Site (slug)',
|
||||
)
|
||||
device = django_filters.CharFilter(
|
||||
device_id = MultiValueNumberFilter(
|
||||
method='filter_device'
|
||||
)
|
||||
device = MultiValueCharFilter(
|
||||
method='filter_device',
|
||||
label='Device',
|
||||
field_name='device__name'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
@@ -1079,22 +1188,25 @@ class PowerConnectionFilter(django_filters.FilterSet):
|
||||
return queryset.filter(_connected_poweroutlet__device__site__slug=value)
|
||||
|
||||
def filter_device(self, queryset, name, value):
|
||||
if not value.strip():
|
||||
if not value:
|
||||
return queryset
|
||||
return queryset.filter(
|
||||
Q(device__name__icontains=value) |
|
||||
Q(_connected_poweroutlet__device__name__icontains=value)
|
||||
Q(**{'{}__in'.format(name): value}) |
|
||||
Q(**{'_connected_poweroutlet__{}__in'.format(name): value})
|
||||
)
|
||||
|
||||
|
||||
class InterfaceConnectionFilter(django_filters.FilterSet):
|
||||
class InterfaceConnectionFilterSet(django_filters.FilterSet):
|
||||
site = django_filters.CharFilter(
|
||||
method='filter_site',
|
||||
label='Site (slug)',
|
||||
)
|
||||
device = django_filters.CharFilter(
|
||||
device_id = MultiValueNumberFilter(
|
||||
method='filter_device'
|
||||
)
|
||||
device = MultiValueCharFilter(
|
||||
method='filter_device',
|
||||
label='Device',
|
||||
field_name='device__name'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
@@ -1110,15 +1222,15 @@ class InterfaceConnectionFilter(django_filters.FilterSet):
|
||||
)
|
||||
|
||||
def filter_device(self, queryset, name, value):
|
||||
if not value.strip():
|
||||
if not value:
|
||||
return queryset
|
||||
return queryset.filter(
|
||||
Q(device__name__icontains=value) |
|
||||
Q(_connected_interface__device__name__icontains=value)
|
||||
Q(**{'{}__in'.format(name): value}) |
|
||||
Q(**{'_connected_interface__{}__in'.format(name): value})
|
||||
)
|
||||
|
||||
|
||||
class PowerPanelFilter(django_filters.FilterSet):
|
||||
class PowerPanelFilterSet(django_filters.FilterSet):
|
||||
id__in = NumericInFilter(
|
||||
field_name='id',
|
||||
lookup_expr='in'
|
||||
@@ -1127,6 +1239,17 @@ class PowerPanelFilter(django_filters.FilterSet):
|
||||
method='search',
|
||||
label='Search',
|
||||
)
|
||||
region_id = TreeNodeMultipleChoiceFilter(
|
||||
queryset=Region.objects.all(),
|
||||
field_name='site__region__in',
|
||||
label='Region (ID)',
|
||||
)
|
||||
region = TreeNodeMultipleChoiceFilter(
|
||||
queryset=Region.objects.all(),
|
||||
field_name='site__region__in',
|
||||
to_field_name='slug',
|
||||
label='Region (slug)',
|
||||
)
|
||||
site_id = django_filters.ModelMultipleChoiceFilter(
|
||||
queryset=Site.objects.all(),
|
||||
label='Site (ID)',
|
||||
@@ -1156,7 +1279,7 @@ class PowerPanelFilter(django_filters.FilterSet):
|
||||
return queryset.filter(qs_filter)
|
||||
|
||||
|
||||
class PowerFeedFilter(CustomFieldFilterSet, CreatedUpdatedFilterSet):
|
||||
class PowerFeedFilterSet(CustomFieldFilterSet, CreatedUpdatedFilterSet):
|
||||
id__in = NumericInFilter(
|
||||
field_name='id',
|
||||
lookup_expr='in'
|
||||
@@ -1165,6 +1288,17 @@ class PowerFeedFilter(CustomFieldFilterSet, CreatedUpdatedFilterSet):
|
||||
method='search',
|
||||
label='Search',
|
||||
)
|
||||
region_id = TreeNodeMultipleChoiceFilter(
|
||||
queryset=Region.objects.all(),
|
||||
field_name='power_panel__site__region__in',
|
||||
label='Region (ID)',
|
||||
)
|
||||
region = TreeNodeMultipleChoiceFilter(
|
||||
queryset=Region.objects.all(),
|
||||
field_name='power_panel__site__region__in',
|
||||
to_field_name='slug',
|
||||
label='Region (slug)',
|
||||
)
|
||||
site_id = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='power_panel__site',
|
||||
queryset=Site.objects.all(),
|
||||
|
||||
@@ -16,7 +16,8 @@ from circuits.models import Circuit, Provider
|
||||
from extras.forms import (
|
||||
AddRemoveTagsForm, CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm, LocalConfigContextFilterForm
|
||||
)
|
||||
from ipam.models import IPAddress, VLAN, VLANGroup
|
||||
from ipam.constants import BGP_ASN_MAX, BGP_ASN_MIN
|
||||
from ipam.models import IPAddress, VLAN
|
||||
from tenancy.forms import TenancyFilterForm, TenancyForm
|
||||
from tenancy.models import Tenant, TenantGroup
|
||||
from utilities.forms import (
|
||||
@@ -102,6 +103,17 @@ class InterfaceCommonForm:
|
||||
elif self.cleaned_data['mode'] == InterfaceModeChoices.MODE_TAGGED_ALL:
|
||||
self.cleaned_data['tagged_vlans'] = []
|
||||
|
||||
# Validate tagged VLANs; must be a global VLAN or in the same site
|
||||
elif self.cleaned_data['mode'] == InterfaceModeChoices.MODE_TAGGED:
|
||||
valid_sites = [None, self.cleaned_data['device'].site]
|
||||
invalid_vlans = [str(v) for v in tagged_vlans if v.site not in valid_sites]
|
||||
|
||||
if invalid_vlans:
|
||||
raise forms.ValidationError({
|
||||
'tagged_vlans': "The tagged VLANs ({}) must belong to the same site as the interface's parent "
|
||||
"device/VM, or they must be global".format(', '.join(invalid_vlans))
|
||||
})
|
||||
|
||||
|
||||
class BulkRenameForm(forms.Form):
|
||||
"""
|
||||
@@ -309,8 +321,8 @@ class SiteBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor
|
||||
)
|
||||
)
|
||||
asn = forms.IntegerField(
|
||||
min_value=1,
|
||||
max_value=4294967295,
|
||||
min_value=BGP_ASN_MIN,
|
||||
max_value=BGP_ASN_MAX,
|
||||
required=False,
|
||||
label='ASN'
|
||||
)
|
||||
@@ -392,6 +404,18 @@ class RackGroupCSVForm(forms.ModelForm):
|
||||
|
||||
|
||||
class RackGroupFilterForm(BootstrapMixin, forms.Form):
|
||||
region = FilterChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
to_field_name='slug',
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
api_url="/api/dcim/regions/",
|
||||
value_field="slug",
|
||||
filter_for={
|
||||
'site': 'region'
|
||||
}
|
||||
)
|
||||
)
|
||||
site = FilterChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
to_field_name='slug',
|
||||
@@ -660,11 +684,23 @@ class RackBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor
|
||||
|
||||
class RackFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
|
||||
model = Rack
|
||||
field_order = ['q', 'site', 'group_id', 'status', 'role', 'tenant_group', 'tenant']
|
||||
field_order = ['q', 'region', 'site', 'group_id', 'status', 'role', 'tenant_group', 'tenant']
|
||||
q = forms.CharField(
|
||||
required=False,
|
||||
label='Search'
|
||||
)
|
||||
region = FilterChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
to_field_name='slug',
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
api_url="/api/dcim/regions/",
|
||||
value_field="slug",
|
||||
filter_for={
|
||||
'site': 'region'
|
||||
}
|
||||
)
|
||||
)
|
||||
site = FilterChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
to_field_name='slug',
|
||||
@@ -676,16 +712,15 @@ class RackFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
|
||||
}
|
||||
)
|
||||
)
|
||||
group_id = ChainedModelChoiceField(
|
||||
label='Rack group',
|
||||
queryset=RackGroup.objects.prefetch_related('site'),
|
||||
chains=(
|
||||
('site', 'site'),
|
||||
group_id = FilterChoiceField(
|
||||
queryset=RackGroup.objects.prefetch_related(
|
||||
'site'
|
||||
),
|
||||
required=False,
|
||||
label='Rack group',
|
||||
null_label='-- None --',
|
||||
widget=APISelectMultiple(
|
||||
api_url="/api/dcim/rack-groups/",
|
||||
null_option=True,
|
||||
null_option=True
|
||||
)
|
||||
)
|
||||
status = forms.MultipleChoiceField(
|
||||
@@ -705,6 +740,34 @@ class RackFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
|
||||
)
|
||||
|
||||
|
||||
#
|
||||
# Rack elevations
|
||||
#
|
||||
|
||||
class RackElevationFilterForm(RackFilterForm):
|
||||
field_order = ['q', 'region', 'site', 'group_id', 'id', 'status', 'role', 'tenant_group', 'tenant']
|
||||
id = ChainedModelChoiceField(
|
||||
queryset=Rack.objects.all(),
|
||||
label='Rack',
|
||||
chains=(
|
||||
('site', 'site'),
|
||||
('group_id', 'group_id'),
|
||||
),
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
api_url='/api/dcim/racks/',
|
||||
display_field='display_name',
|
||||
)
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# Filter the rack field based on the site and group
|
||||
self.fields['site'].widget.add_filter_for('id', 'site')
|
||||
self.fields['group_id'].widget.add_filter_for('id', 'group_id')
|
||||
|
||||
|
||||
#
|
||||
# Rack reservations
|
||||
#
|
||||
@@ -1484,7 +1547,8 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldForm):
|
||||
widget=APISelect(
|
||||
api_url="/api/dcim/manufacturers/",
|
||||
filter_for={
|
||||
'device_type': 'manufacturer_id'
|
||||
'device_type': 'manufacturer_id',
|
||||
'platform': 'manufacturer_id'
|
||||
}
|
||||
)
|
||||
)
|
||||
@@ -1553,7 +1617,10 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldForm):
|
||||
),
|
||||
'status': StaticSelect2(),
|
||||
'platform': APISelect(
|
||||
api_url="/api/dcim/platforms/"
|
||||
api_url="/api/dcim/platforms/",
|
||||
additional_query_params={
|
||||
"manufacturer_id": "null"
|
||||
}
|
||||
),
|
||||
'primary_ip4': StaticSelect2(),
|
||||
'primary_ip6': StaticSelect2(),
|
||||
@@ -1909,7 +1976,7 @@ class DeviceFilterForm(BootstrapMixin, LocalConfigContextFilterForm, TenancyFilt
|
||||
widget=APISelectMultiple(
|
||||
api_url="/api/dcim/rack-groups/",
|
||||
filter_for={
|
||||
'rack_id': 'rack_group_id',
|
||||
'rack_id': 'group_id',
|
||||
}
|
||||
)
|
||||
)
|
||||
@@ -2519,36 +2586,6 @@ class InterfaceForm(InterfaceCommonForm, BootstrapMixin, forms.ModelForm):
|
||||
type=InterfaceTypeChoices.TYPE_LAG
|
||||
)
|
||||
|
||||
# Limit VLan choices to those in: global vlans, global groups, the current site's group, the current site
|
||||
vlan_choices = []
|
||||
global_vlans = VLAN.objects.filter(site=None, group=None)
|
||||
vlan_choices.append(
|
||||
('Global', [(vlan.pk, vlan) for vlan in global_vlans])
|
||||
)
|
||||
for group in VLANGroup.objects.filter(site=None):
|
||||
global_group_vlans = VLAN.objects.filter(group=group)
|
||||
vlan_choices.append(
|
||||
(group.name, [(vlan.pk, vlan) for vlan in global_group_vlans])
|
||||
)
|
||||
|
||||
site = getattr(self.instance.parent, 'site', None)
|
||||
if site is not None:
|
||||
|
||||
# Add non-grouped site VLANs
|
||||
site_vlans = VLAN.objects.filter(site=site, group=None)
|
||||
vlan_choices.append((site.name, [(vlan.pk, vlan) for vlan in site_vlans]))
|
||||
|
||||
# Add grouped site VLANs
|
||||
for group in VLANGroup.objects.filter(site=site):
|
||||
site_group_vlans = VLAN.objects.filter(group=group)
|
||||
vlan_choices.append((
|
||||
'{} / {}'.format(group.site.name, group.name),
|
||||
[(vlan.pk, vlan) for vlan in site_group_vlans]
|
||||
))
|
||||
|
||||
self.fields['untagged_vlan'].choices = [(None, '---------')] + vlan_choices
|
||||
self.fields['tagged_vlans'].choices = vlan_choices
|
||||
|
||||
|
||||
class InterfaceCreateForm(InterfaceCommonForm, ComponentForm, forms.Form):
|
||||
name_pattern = ExpandableNameField(
|
||||
@@ -2630,36 +2667,6 @@ class InterfaceCreateForm(InterfaceCommonForm, ComponentForm, forms.Form):
|
||||
else:
|
||||
self.fields['lag'].queryset = Interface.objects.none()
|
||||
|
||||
# Limit VLan choices to those in: global vlans, global groups, the current site's group, the current site
|
||||
vlan_choices = []
|
||||
global_vlans = VLAN.objects.filter(site=None, group=None)
|
||||
vlan_choices.append(
|
||||
('Global', [(vlan.pk, vlan) for vlan in global_vlans])
|
||||
)
|
||||
for group in VLANGroup.objects.filter(site=None):
|
||||
global_group_vlans = VLAN.objects.filter(group=group)
|
||||
vlan_choices.append(
|
||||
(group.name, [(vlan.pk, vlan) for vlan in global_group_vlans])
|
||||
)
|
||||
|
||||
site = getattr(self.parent, 'site', None)
|
||||
if site is not None:
|
||||
|
||||
# Add non-grouped site VLANs
|
||||
site_vlans = VLAN.objects.filter(site=site, group=None)
|
||||
vlan_choices.append((site.name, [(vlan.pk, vlan) for vlan in site_vlans]))
|
||||
|
||||
# Add grouped site VLANs
|
||||
for group in VLANGroup.objects.filter(site=site):
|
||||
site_group_vlans = VLAN.objects.filter(group=group)
|
||||
vlan_choices.append((
|
||||
'{} / {}'.format(group.site.name, group.name),
|
||||
[(vlan.pk, vlan) for vlan in site_group_vlans]
|
||||
))
|
||||
|
||||
self.fields['untagged_vlan'].choices = [(None, '---------')] + vlan_choices
|
||||
self.fields['tagged_vlans'].choices = vlan_choices
|
||||
|
||||
|
||||
class InterfaceCSVForm(forms.ModelForm):
|
||||
device = FlexibleModelChoiceField(
|
||||
@@ -2809,36 +2816,6 @@ class InterfaceBulkEditForm(InterfaceCommonForm, BootstrapMixin, AddRemoveTagsFo
|
||||
else:
|
||||
self.fields['lag'].choices = []
|
||||
|
||||
# Limit VLan choices to those in: global vlans, global groups, the current site's group, the current site
|
||||
vlan_choices = []
|
||||
global_vlans = VLAN.objects.filter(site=None, group=None)
|
||||
vlan_choices.append(
|
||||
('Global', [(vlan.pk, vlan) for vlan in global_vlans])
|
||||
)
|
||||
for group in VLANGroup.objects.filter(site=None):
|
||||
global_group_vlans = VLAN.objects.filter(group=group)
|
||||
vlan_choices.append(
|
||||
(group.name, [(vlan.pk, vlan) for vlan in global_group_vlans])
|
||||
)
|
||||
if self.parent_obj is not None:
|
||||
site = getattr(self.parent_obj, 'site', None)
|
||||
if site is not None:
|
||||
|
||||
# Add non-grouped site VLANs
|
||||
site_vlans = VLAN.objects.filter(site=site, group=None)
|
||||
vlan_choices.append((site.name, [(vlan.pk, vlan) for vlan in site_vlans]))
|
||||
|
||||
# Add grouped site VLANs
|
||||
for group in VLANGroup.objects.filter(site=site):
|
||||
site_group_vlans = VLAN.objects.filter(group=group)
|
||||
vlan_choices.append((
|
||||
'{} / {}'.format(group.site.name, group.name),
|
||||
[(vlan.pk, vlan) for vlan in site_group_vlans]
|
||||
))
|
||||
|
||||
self.fields['untagged_vlan'].choices = [(None, '---------')] + vlan_choices
|
||||
self.fields['tagged_vlans'].choices = vlan_choices
|
||||
|
||||
|
||||
class InterfaceBulkRenameForm(BulkRenameForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
@@ -3266,6 +3243,7 @@ class ConnectCableToCircuitTerminationForm(BootstrapMixin, ChainedFieldsMixin, f
|
||||
termination_b_provider = forms.ModelChoiceField(
|
||||
queryset=Provider.objects.all(),
|
||||
label='Provider',
|
||||
required=False,
|
||||
widget=APISelect(
|
||||
api_url='/api/circuits/providers/',
|
||||
filter_for={
|
||||
@@ -3319,6 +3297,7 @@ class ConnectCableToPowerFeedForm(BootstrapMixin, ChainedFieldsMixin, forms.Mode
|
||||
termination_b_site = forms.ModelChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
label='Site',
|
||||
required=False,
|
||||
widget=APISelect(
|
||||
api_url='/api/dcim/sites/',
|
||||
display_field='cid',
|
||||
@@ -3350,6 +3329,7 @@ class ConnectCableToPowerFeedForm(BootstrapMixin, ChainedFieldsMixin, forms.Mode
|
||||
('rack_group', 'termination_b_rackgroup'),
|
||||
),
|
||||
label='Power Panel',
|
||||
required=False,
|
||||
widget=APISelect(
|
||||
api_url='/api/dcim/power-panels/',
|
||||
filter_for={
|
||||
@@ -3394,9 +3374,7 @@ class CableCSVForm(forms.ModelForm):
|
||||
)
|
||||
side_a_type = forms.ModelChoiceField(
|
||||
queryset=ContentType.objects.all(),
|
||||
limit_choices_to={
|
||||
'model__in': CABLE_TERMINATION_TYPES,
|
||||
},
|
||||
limit_choices_to=CABLE_TERMINATION_MODELS,
|
||||
to_field_name='model',
|
||||
help_text='Side A type'
|
||||
)
|
||||
@@ -3415,9 +3393,7 @@ class CableCSVForm(forms.ModelForm):
|
||||
)
|
||||
side_b_type = forms.ModelChoiceField(
|
||||
queryset=ContentType.objects.all(),
|
||||
limit_choices_to={
|
||||
'model__in': CABLE_TERMINATION_TYPES,
|
||||
},
|
||||
limit_choices_to=CABLE_TERMINATION_MODELS,
|
||||
to_field_name='model',
|
||||
help_text='Side B type'
|
||||
)
|
||||
@@ -3581,6 +3557,17 @@ class CableFilterForm(BootstrapMixin, forms.Form):
|
||||
}
|
||||
)
|
||||
)
|
||||
tenant = FilterChoiceField(
|
||||
queryset=Tenant.objects.all(),
|
||||
to_field_name='slug',
|
||||
widget=APISelectMultiple(
|
||||
api_url="/api/tenancy/tenants/",
|
||||
value_field='slug',
|
||||
filter_for={
|
||||
'device_id': 'tenant',
|
||||
}
|
||||
)
|
||||
)
|
||||
rack_id = FilterChoiceField(
|
||||
queryset=Rack.objects.all(),
|
||||
label='Rack',
|
||||
@@ -3605,9 +3592,13 @@ class CableFilterForm(BootstrapMixin, forms.Form):
|
||||
required=False,
|
||||
widget=ColorSelect()
|
||||
)
|
||||
device = forms.CharField(
|
||||
device_id = FilterChoiceField(
|
||||
queryset=Device.objects.all(),
|
||||
required=False,
|
||||
label='Device name'
|
||||
label='Device',
|
||||
widget=APISelectMultiple(
|
||||
api_url='/api/dcim/devices/',
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -3726,38 +3717,59 @@ class DeviceBayBulkRenameForm(BulkRenameForm):
|
||||
#
|
||||
|
||||
class ConsoleConnectionFilterForm(BootstrapMixin, forms.Form):
|
||||
site = forms.ModelChoiceField(
|
||||
site = FilterChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
required=False,
|
||||
to_field_name='slug'
|
||||
to_field_name='slug',
|
||||
widget=APISelectMultiple(
|
||||
api_url="/api/dcim/sites/",
|
||||
value_field="slug",
|
||||
)
|
||||
)
|
||||
device = forms.CharField(
|
||||
device_id = FilterChoiceField(
|
||||
queryset=Device.objects.all(),
|
||||
required=False,
|
||||
label='Device name'
|
||||
label='Device',
|
||||
widget=APISelectMultiple(
|
||||
api_url='/api/dcim/devices/',
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class PowerConnectionFilterForm(BootstrapMixin, forms.Form):
|
||||
site = forms.ModelChoiceField(
|
||||
site = FilterChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
required=False,
|
||||
to_field_name='slug'
|
||||
to_field_name='slug',
|
||||
widget=APISelectMultiple(
|
||||
api_url="/api/dcim/sites/",
|
||||
value_field="slug",
|
||||
)
|
||||
)
|
||||
device = forms.CharField(
|
||||
device_id = FilterChoiceField(
|
||||
queryset=Device.objects.all(),
|
||||
required=False,
|
||||
label='Device name'
|
||||
label='Device',
|
||||
widget=APISelectMultiple(
|
||||
api_url='/api/dcim/devices/',
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class InterfaceConnectionFilterForm(BootstrapMixin, forms.Form):
|
||||
site = forms.ModelChoiceField(
|
||||
site = FilterChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
required=False,
|
||||
to_field_name='slug'
|
||||
to_field_name='slug',
|
||||
widget=APISelectMultiple(
|
||||
api_url="/api/dcim/sites/",
|
||||
value_field="slug",
|
||||
)
|
||||
)
|
||||
device = forms.CharField(
|
||||
device_id = FilterChoiceField(
|
||||
queryset=Device.objects.all(),
|
||||
required=False,
|
||||
label='Device name'
|
||||
label='Device',
|
||||
widget=APISelectMultiple(
|
||||
api_url='/api/dcim/devices/',
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -3773,9 +3785,12 @@ class InventoryItemForm(BootstrapMixin, forms.ModelForm):
|
||||
class Meta:
|
||||
model = InventoryItem
|
||||
fields = [
|
||||
'name', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'description', 'tags',
|
||||
'name', 'device', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'description', 'tags',
|
||||
]
|
||||
widgets = {
|
||||
'device': APISelect(
|
||||
api_url="/api/dcim/devices/"
|
||||
),
|
||||
'manufacturer': APISelect(
|
||||
api_url="/api/dcim/manufacturers/"
|
||||
)
|
||||
@@ -3811,9 +3826,19 @@ class InventoryItemBulkEditForm(BootstrapMixin, BulkEditForm):
|
||||
queryset=InventoryItem.objects.all(),
|
||||
widget=forms.MultipleHiddenInput()
|
||||
)
|
||||
device = forms.ModelChoiceField(
|
||||
queryset=Device.objects.all(),
|
||||
required=False,
|
||||
widget=APISelect(
|
||||
api_url="/api/dcim/devices/"
|
||||
)
|
||||
)
|
||||
manufacturer = forms.ModelChoiceField(
|
||||
queryset=Manufacturer.objects.all(),
|
||||
required=False
|
||||
required=False,
|
||||
widget=APISelect(
|
||||
api_url="/api/dcim/manufacturers/"
|
||||
)
|
||||
)
|
||||
part_id = forms.CharField(
|
||||
max_length=50,
|
||||
@@ -3837,18 +3862,48 @@ class InventoryItemFilterForm(BootstrapMixin, forms.Form):
|
||||
required=False,
|
||||
label='Search'
|
||||
)
|
||||
device = forms.CharField(
|
||||
region = FilterChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
to_field_name='slug',
|
||||
required=False,
|
||||
label='Device name'
|
||||
widget=APISelectMultiple(
|
||||
api_url="/api/dcim/regions/",
|
||||
value_field="slug",
|
||||
filter_for={
|
||||
'site': 'region'
|
||||
}
|
||||
)
|
||||
)
|
||||
site = FilterChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
to_field_name='slug',
|
||||
widget=APISelectMultiple(
|
||||
api_url="/api/dcim/sites/",
|
||||
value_field="slug",
|
||||
filter_for={
|
||||
'device_id': 'site'
|
||||
}
|
||||
)
|
||||
)
|
||||
device_id = FilterChoiceField(
|
||||
queryset=Device.objects.all(),
|
||||
required=False,
|
||||
label='Device',
|
||||
widget=APISelect(
|
||||
api_url='/api/dcim/devices/',
|
||||
)
|
||||
)
|
||||
manufacturer = FilterChoiceField(
|
||||
queryset=Manufacturer.objects.all(),
|
||||
to_field_name='slug',
|
||||
null_label='-- None --'
|
||||
widget=APISelect(
|
||||
api_url="/api/dcim/manufacturers/",
|
||||
value_field="slug",
|
||||
)
|
||||
)
|
||||
discovered = forms.NullBooleanField(
|
||||
required=False,
|
||||
widget=forms.Select(
|
||||
widget=StaticSelect2(
|
||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||
)
|
||||
)
|
||||
@@ -3995,6 +4050,18 @@ class VirtualChassisFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
||||
required=False,
|
||||
label='Search'
|
||||
)
|
||||
region = FilterChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
to_field_name='slug',
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
api_url="/api/dcim/regions/",
|
||||
value_field="slug",
|
||||
filter_for={
|
||||
'site': 'region'
|
||||
}
|
||||
)
|
||||
)
|
||||
site = FilterChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
to_field_name='slug',
|
||||
@@ -4100,6 +4167,18 @@ class PowerPanelFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
||||
required=False,
|
||||
label='Search'
|
||||
)
|
||||
region = FilterChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
to_field_name='slug',
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
api_url="/api/dcim/regions/",
|
||||
value_field="slug",
|
||||
filter_for={
|
||||
'site': 'region'
|
||||
}
|
||||
)
|
||||
)
|
||||
site = FilterChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
to_field_name='slug',
|
||||
@@ -4320,6 +4399,18 @@ class PowerFeedFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
||||
required=False,
|
||||
label='Search'
|
||||
)
|
||||
region = FilterChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
to_field_name='slug',
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
api_url="/api/dcim/regions/",
|
||||
value_field="slug",
|
||||
filter_for={
|
||||
'site': 'region'
|
||||
}
|
||||
)
|
||||
)
|
||||
site = FilterChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
to_field_name='slug',
|
||||
|
||||
@@ -1,259 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.14 on 2018-07-31 02:06
|
||||
import dcim.fields
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import utilities.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
replaces = [('dcim', '0002_auto_20160622_1821'), ('dcim', '0003_auto_20160628_1721'), ('dcim', '0004_auto_20160701_2049'), ('dcim', '0005_auto_20160706_1722'), ('dcim', '0006_add_device_primary_ip4_ip6'), ('dcim', '0007_device_copy_primary_ip'), ('dcim', '0008_device_remove_primary_ip'), ('dcim', '0009_site_32bit_asn_support'), ('dcim', '0010_devicebay_installed_device_set_null'), ('dcim', '0011_devicetype_part_number'), ('dcim', '0012_site_rack_device_add_tenant'), ('dcim', '0013_add_interface_form_factors'), ('dcim', '0014_rack_add_type_width'), ('dcim', '0015_rack_add_u_height_validator'), ('dcim', '0016_module_add_manufacturer'), ('dcim', '0017_rack_add_role'), ('dcim', '0018_device_add_asset_tag'), ('dcim', '0019_new_iface_form_factors'), ('dcim', '0020_rack_desc_units'), ('dcim', '0021_add_ff_flexstack'), ('dcim', '0022_color_names_to_rgb')]
|
||||
|
||||
dependencies = [
|
||||
('dcim', '0001_initial'),
|
||||
('ipam', '0001_initial'),
|
||||
('tenancy', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='device',
|
||||
name='rack',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='devices', to='dcim.Rack'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='consoleserverporttemplate',
|
||||
name='device_type',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='cs_port_templates', to='dcim.DeviceType'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='consoleserverport',
|
||||
name='device',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='cs_ports', to='dcim.Device'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='consoleporttemplate',
|
||||
name='device_type',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='console_port_templates', to='dcim.DeviceType'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='consoleport',
|
||||
name='cs_port',
|
||||
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='connected_console', to='dcim.ConsoleServerPort', verbose_name=b'Console server port'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='consoleport',
|
||||
name='device',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='console_ports', to='dcim.Device'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='rackgroup',
|
||||
unique_together=set([('site', 'name'), ('site', 'slug')]),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='rack',
|
||||
unique_together=set([('site', 'facility_id'), ('site', 'name')]),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='powerporttemplate',
|
||||
unique_together=set([('device_type', 'name')]),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='powerport',
|
||||
unique_together=set([('device', 'name')]),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='poweroutlettemplate',
|
||||
unique_together=set([('device_type', 'name')]),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='poweroutlet',
|
||||
unique_together=set([('device', 'name')]),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='module',
|
||||
unique_together=set([('device', 'parent', 'name')]),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='interfacetemplate',
|
||||
unique_together=set([('device_type', 'name')]),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='interface',
|
||||
name='mac_address',
|
||||
field=dcim.fields.MACAddressField(blank=True, null=True, verbose_name=b'MAC Address'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='interface',
|
||||
unique_together=set([('device', 'name')]),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='devicetype',
|
||||
name='subdevice_role',
|
||||
field=models.NullBooleanField(choices=[(None, b'None'), (True, b'Parent'), (False, b'Child')], default=None, help_text=b'Parent devices house child devices in device bays. Select "None" if this device type is neither a parent nor a child.', verbose_name=b'Parent/child status'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='devicetype',
|
||||
unique_together=set([('manufacturer', 'slug'), ('manufacturer', 'model')]),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='device',
|
||||
unique_together=set([('rack', 'position', 'face')]),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='consoleserverporttemplate',
|
||||
unique_together=set([('device_type', 'name')]),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='consoleserverport',
|
||||
unique_together=set([('device', 'name')]),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='consoleporttemplate',
|
||||
unique_together=set([('device_type', 'name')]),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='consoleport',
|
||||
unique_together=set([('device', 'name')]),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DeviceBay',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=50, verbose_name=b'Name')),
|
||||
('device', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='device_bays', to='dcim.Device')),
|
||||
('installed_device', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='parent_bay', to='dcim.Device')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['device', 'name'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DeviceBayTemplate',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=30)),
|
||||
('device_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='device_bay_templates', to='dcim.DeviceType')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['device_type', 'name'],
|
||||
},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='devicebaytemplate',
|
||||
unique_together=set([('device_type', 'name')]),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='devicebay',
|
||||
unique_together=set([('device', 'name')]),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='device',
|
||||
name='primary_ip4',
|
||||
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='primary_ip4_for', to='ipam.IPAddress', verbose_name=b'Primary IPv4'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='device',
|
||||
name='primary_ip6',
|
||||
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='primary_ip6_for', to='ipam.IPAddress', verbose_name=b'Primary IPv6'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='site',
|
||||
name='asn',
|
||||
field=dcim.fields.ASNField(blank=True, null=True, verbose_name=b'ASN'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='devicebay',
|
||||
name='installed_device',
|
||||
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='parent_bay', to='dcim.Device'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='devicetype',
|
||||
name='part_number',
|
||||
field=models.CharField(blank=True, help_text=b'Discrete part number (optional)', max_length=50),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='device',
|
||||
name='tenant',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='devices', to='tenancy.Tenant'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='rack',
|
||||
name='tenant',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='racks', to='tenancy.Tenant'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='site',
|
||||
name='tenant',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='sites', to='tenancy.Tenant'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='rack',
|
||||
name='type',
|
||||
field=models.PositiveSmallIntegerField(blank=True, choices=[(100, b'2-post frame'), (200, b'4-post frame'), (300, b'4-post cabinet'), (1000, b'Wall-mounted frame'), (1100, b'Wall-mounted cabinet')], null=True, verbose_name=b'Type'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='rack',
|
||||
name='width',
|
||||
field=models.PositiveSmallIntegerField(choices=[(19, b'19 inches'), (23, b'23 inches')], default=19, help_text=b'Rail-to-rail width', verbose_name=b'Width'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='rack',
|
||||
name='u_height',
|
||||
field=models.PositiveSmallIntegerField(default=42, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(100)], verbose_name=b'Height (U)'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='module',
|
||||
name='manufacturer',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='modules', to='dcim.Manufacturer'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='RackRole',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=50, unique=True)),
|
||||
('slug', models.SlugField(unique=True)),
|
||||
('color', utilities.fields.ColorField(max_length=6)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['name'],
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='rack',
|
||||
name='role',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='racks', to='dcim.RackRole'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='device',
|
||||
name='asset_tag',
|
||||
field=utilities.fields.NullableCharField(blank=True, help_text=b'A unique tag used to identify this device', max_length=50, null=True, unique=True, verbose_name=b'Asset tag'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='rack',
|
||||
name='desc_units',
|
||||
field=models.BooleanField(default=False, help_text=b'Units are numbered top-to-bottom', verbose_name=b'Descending units'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='device',
|
||||
name='position',
|
||||
field=models.PositiveSmallIntegerField(blank=True, help_text=b'The lowest-numbered unit occupied by the device', null=True, validators=[django.core.validators.MinValueValidator(1)], verbose_name=b'Position (U)'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='interface',
|
||||
name='form_factor',
|
||||
field=models.PositiveSmallIntegerField(choices=[[b'Virtual interfaces', [[0, b'Virtual']]], [b'Ethernet (fixed)', [[800, b'100BASE-TX (10/100ME)'], [1000, b'1000BASE-T (1GE)'], [1150, b'10GBASE-T (10GE)']]], [b'Ethernet (modular)', [[1050, b'GBIC (1GE)'], [1100, b'SFP (1GE)'], [1200, b'SFP+ (10GE)'], [1300, b'XFP (10GE)'], [1310, b'XENPAK (10GE)'], [1320, b'X2 (10GE)'], [1350, b'SFP28 (25GE)'], [1400, b'QSFP+ (40GE)'], [1500, b'CFP (100GE)'], [1600, b'QSFP28 (100GE)']]], [b'FibreChannel', [[3010, b'SFP (1GFC)'], [3020, b'SFP (2GFC)'], [3040, b'SFP (4GFC)'], [3080, b'SFP+ (8GFC)'], [3160, b'SFP+ (16GFC)']]], [b'Serial', [[4000, b'T1 (1.544 Mbps)'], [4010, b'E1 (2.048 Mbps)'], [4040, b'T3 (45 Mbps)'], [4050, b'E3 (34 Mbps)']]], [b'Stacking', [[5000, b'Cisco StackWise'], [5050, b'Cisco StackWise Plus'], [5100, b'Cisco FlexStack'], [5150, b'Cisco FlexStack Plus']]], [b'Other', [[32767, b'Other']]]], default=1200),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='interfacetemplate',
|
||||
name='form_factor',
|
||||
field=models.PositiveSmallIntegerField(choices=[[b'Virtual interfaces', [[0, b'Virtual']]], [b'Ethernet (fixed)', [[800, b'100BASE-TX (10/100ME)'], [1000, b'1000BASE-T (1GE)'], [1150, b'10GBASE-T (10GE)']]], [b'Ethernet (modular)', [[1050, b'GBIC (1GE)'], [1100, b'SFP (1GE)'], [1200, b'SFP+ (10GE)'], [1300, b'XFP (10GE)'], [1310, b'XENPAK (10GE)'], [1320, b'X2 (10GE)'], [1350, b'SFP28 (25GE)'], [1400, b'QSFP+ (40GE)'], [1500, b'CFP (100GE)'], [1600, b'QSFP28 (100GE)']]], [b'FibreChannel', [[3010, b'SFP (1GFC)'], [3020, b'SFP (2GFC)'], [3040, b'SFP (4GFC)'], [3080, b'SFP+ (8GFC)'], [3160, b'SFP+ (16GFC)']]], [b'Serial', [[4000, b'T1 (1.544 Mbps)'], [4010, b'E1 (2.048 Mbps)'], [4040, b'T3 (45 Mbps)'], [4050, b'E3 (34 Mbps)']]], [b'Stacking', [[5000, b'Cisco StackWise'], [5050, b'Cisco StackWise Plus'], [5100, b'Cisco FlexStack'], [5150, b'Cisco FlexStack Plus']]], [b'Other', [[32767, b'Other']]]], default=1200),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='devicerole',
|
||||
name='color',
|
||||
field=utilities.fields.ColorField(max_length=6),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,101 @@
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
import dcim.fields
|
||||
|
||||
|
||||
def copy_primary_ip(apps, schema_editor):
|
||||
Device = apps.get_model('dcim', 'Device')
|
||||
for d in Device.objects.select_related('primary_ip'):
|
||||
if not d.primary_ip:
|
||||
continue
|
||||
if d.primary_ip.family == 4:
|
||||
d.primary_ip4 = d.primary_ip
|
||||
elif d.primary_ip.family == 6:
|
||||
d.primary_ip6 = d.primary_ip
|
||||
d.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
replaces = [('dcim', '0003_auto_20160628_1721'), ('dcim', '0004_auto_20160701_2049'), ('dcim', '0005_auto_20160706_1722'), ('dcim', '0006_add_device_primary_ip4_ip6'), ('dcim', '0007_device_copy_primary_ip'), ('dcim', '0008_device_remove_primary_ip'), ('dcim', '0009_site_32bit_asn_support'), ('dcim', '0010_devicebay_installed_device_set_null')]
|
||||
|
||||
dependencies = [
|
||||
('ipam', '0001_initial'),
|
||||
('dcim', '0002_auto_20160622_1821'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='interface',
|
||||
name='form_factor',
|
||||
field=models.PositiveSmallIntegerField(choices=[[0, b'Virtual'], [800, b'10/100M (100BASE-TX)'], [1000, b'1GE (1000BASE-T)'], [1100, b'1GE (SFP)'], [1150, b'10GE (10GBASE-T)'], [1200, b'10GE (SFP+)'], [1300, b'10GE (XFP)'], [1400, b'40GE (QSFP+)']], default=1200),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='interfacetemplate',
|
||||
name='form_factor',
|
||||
field=models.PositiveSmallIntegerField(choices=[[0, b'Virtual'], [800, b'10/100M (100BASE-TX)'], [1000, b'1GE (1000BASE-T)'], [1100, b'1GE (SFP)'], [1150, b'10GE (10GBASE-T)'], [1200, b'10GE (SFP+)'], [1300, b'10GE (XFP)'], [1400, b'40GE (QSFP+)']], default=1200),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='devicetype',
|
||||
name='subdevice_role',
|
||||
field=models.NullBooleanField(choices=[(None, b'None'), (True, b'Parent'), (False, b'Child')], default=None, help_text=b'Parent devices house child devices in device bays. Select "None" if this device type is neither a parent nor a child.', verbose_name=b'Parent/child status'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DeviceBayTemplate',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=30)),
|
||||
('device_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='device_bay_templates', to='dcim.DeviceType')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['device_type', 'name'],
|
||||
'unique_together': {('device_type', 'name')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DeviceBay',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=50, verbose_name=b'Name')),
|
||||
('device', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='device_bays', to='dcim.Device')),
|
||||
('installed_device', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='parent_bay', to='dcim.Device')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['device', 'name'],
|
||||
'unique_together': {('device', 'name')},
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='interface',
|
||||
name='mac_address',
|
||||
field=dcim.fields.MACAddressField(blank=True, null=True, verbose_name=b'MAC Address'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='device',
|
||||
name='primary_ip4',
|
||||
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='primary_ip4_for', to='ipam.IPAddress', verbose_name=b'Primary IPv4'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='device',
|
||||
name='primary_ip6',
|
||||
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='primary_ip6_for', to='ipam.IPAddress', verbose_name=b'Primary IPv6'),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=copy_primary_ip,
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='device',
|
||||
name='primary_ip',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='site',
|
||||
name='asn',
|
||||
field=dcim.fields.ASNField(blank=True, null=True, verbose_name=b'ASN'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='devicebay',
|
||||
name='installed_device',
|
||||
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='parent_bay', to='dcim.Device'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,154 @@
|
||||
import django.core.validators
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
import utilities.fields
|
||||
|
||||
COLOR_CONVERSION = {
|
||||
'teal': '009688',
|
||||
'green': '4caf50',
|
||||
'blue': '2196f3',
|
||||
'purple': '9c27b0',
|
||||
'yellow': 'ffeb3b',
|
||||
'orange': 'ff9800',
|
||||
'red': 'f44336',
|
||||
'light_gray': 'c0c0c0',
|
||||
'medium_gray': '9e9e9e',
|
||||
'dark_gray': '607d8b',
|
||||
}
|
||||
|
||||
|
||||
def color_names_to_rgb(apps, schema_editor):
|
||||
RackRole = apps.get_model('dcim', 'RackRole')
|
||||
DeviceRole = apps.get_model('dcim', 'DeviceRole')
|
||||
for color_name, color_rgb in COLOR_CONVERSION.items():
|
||||
RackRole.objects.filter(color=color_name).update(color=color_rgb)
|
||||
DeviceRole.objects.filter(color=color_name).update(color=color_rgb)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
replaces = [('dcim', '0011_devicetype_part_number'), ('dcim', '0012_site_rack_device_add_tenant'), ('dcim', '0013_add_interface_form_factors'), ('dcim', '0014_rack_add_type_width'), ('dcim', '0015_rack_add_u_height_validator'), ('dcim', '0016_module_add_manufacturer'), ('dcim', '0017_rack_add_role'), ('dcim', '0018_device_add_asset_tag'), ('dcim', '0019_new_iface_form_factors'), ('dcim', '0020_rack_desc_units'), ('dcim', '0021_add_ff_flexstack'), ('dcim', '0022_color_names_to_rgb')]
|
||||
|
||||
dependencies = [
|
||||
('dcim', '0010_devicebay_installed_device_set_null'),
|
||||
('tenancy', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='devicetype',
|
||||
name='part_number',
|
||||
field=models.CharField(blank=True, help_text=b'Discrete part number (optional)', max_length=50),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='device',
|
||||
name='tenant',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='devices', to='tenancy.Tenant'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='rack',
|
||||
name='tenant',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='racks', to='tenancy.Tenant'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='site',
|
||||
name='tenant',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='sites', to='tenancy.Tenant'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='interface',
|
||||
name='form_factor',
|
||||
field=models.PositiveSmallIntegerField(choices=[[b'Virtual interfaces', [[0, b'Virtual']]], [b'Ethernet', [[800, b'100BASE-TX (10/100M)'], [1000, b'1000BASE-T (1GE)'], [1150, b'10GBASE-T (10GE)']]], [b'Modular', [[1050, b'GBIC (1GE)'], [1100, b'SFP (1GE)'], [1300, b'XFP (10GE)'], [1200, b'SFP+ (10GE)'], [1400, b'QSFP+ (40GE)'], [1500, b'CFP (100GE)'], [1600, b'QSFP28 (100GE)']]], [b'Serial', [[4000, b'T1 (1.544 Mbps)'], [4010, b'E1 (2.048 Mbps)'], [4040, b'T3 (45 Mbps)'], [4050, b'E3 (34 Mbps)']]], [b'Stacking', [[5000, b'Cisco StackWise'], [5050, b'Cisco StackWise Plus']]]], default=1200),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='interfacetemplate',
|
||||
name='form_factor',
|
||||
field=models.PositiveSmallIntegerField(choices=[[b'Virtual interfaces', [[0, b'Virtual']]], [b'Ethernet', [[800, b'100BASE-TX (10/100M)'], [1000, b'1000BASE-T (1GE)'], [1150, b'10GBASE-T (10GE)']]], [b'Modular', [[1050, b'GBIC (1GE)'], [1100, b'SFP (1GE)'], [1300, b'XFP (10GE)'], [1200, b'SFP+ (10GE)'], [1400, b'QSFP+ (40GE)'], [1500, b'CFP (100GE)'], [1600, b'QSFP28 (100GE)']]], [b'Serial', [[4000, b'T1 (1.544 Mbps)'], [4010, b'E1 (2.048 Mbps)'], [4040, b'T3 (45 Mbps)'], [4050, b'E3 (34 Mbps)']]], [b'Stacking', [[5000, b'Cisco StackWise'], [5050, b'Cisco StackWise Plus']]]], default=1200),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='rack',
|
||||
name='type',
|
||||
field=models.PositiveSmallIntegerField(blank=True, choices=[(100, b'2-post frame'), (200, b'4-post frame'), (300, b'4-post cabinet'), (1000, b'Wall-mounted frame'), (1100, b'Wall-mounted cabinet')], null=True, verbose_name=b'Type'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='rack',
|
||||
name='width',
|
||||
field=models.PositiveSmallIntegerField(choices=[(19, b'19 inches'), (23, b'23 inches')], default=19, help_text=b'Rail-to-rail width', verbose_name=b'Width'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='rack',
|
||||
name='u_height',
|
||||
field=models.PositiveSmallIntegerField(default=42, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(100)], verbose_name=b'Height (U)'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='module',
|
||||
name='manufacturer',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='modules', to='dcim.Manufacturer'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='RackRole',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=50, unique=True)),
|
||||
('slug', models.SlugField(unique=True)),
|
||||
('color', models.CharField(choices=[[b'teal', b'Teal'], [b'green', b'Green'], [b'blue', b'Blue'], [b'purple', b'Purple'], [b'yellow', b'Yellow'], [b'orange', b'Orange'], [b'red', b'Red'], [b'light_gray', b'Light Gray'], [b'medium_gray', b'Medium Gray'], [b'dark_gray', b'Dark Gray']], max_length=30)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['name'],
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='rack',
|
||||
name='role',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='racks', to='dcim.RackRole'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='device',
|
||||
name='asset_tag',
|
||||
field=utilities.fields.NullableCharField(blank=True, help_text=b'A unique tag used to identify this device', max_length=50, null=True, unique=True, verbose_name=b'Asset tag'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='interface',
|
||||
name='form_factor',
|
||||
field=models.PositiveSmallIntegerField(choices=[[b'Virtual interfaces', [[0, b'Virtual']]], [b'Ethernet (fixed)', [[800, b'100BASE-TX (10/100ME)'], [1000, b'1000BASE-T (1GE)'], [1150, b'10GBASE-T (10GE)']]], [b'Ethernet (modular)', [[1050, b'GBIC (1GE)'], [1100, b'SFP (1GE)'], [1200, b'SFP+ (10GE)'], [1300, b'XFP (10GE)'], [1310, b'XENPAK (10GE)'], [1320, b'X2 (10GE)'], [1350, b'SFP28 (25GE)'], [1400, b'QSFP+ (40GE)'], [1500, b'CFP (100GE)'], [1600, b'QSFP28 (100GE)']]], [b'FibreChannel', [[3010, b'SFP (1GFC)'], [3020, b'SFP (2GFC)'], [3040, b'SFP (4GFC)'], [3080, b'SFP+ (8GFC)'], [3160, b'SFP+ (16GFC)']]], [b'Serial', [[4000, b'T1 (1.544 Mbps)'], [4010, b'E1 (2.048 Mbps)'], [4040, b'T3 (45 Mbps)'], [4050, b'E3 (34 Mbps)']]], [b'Stacking', [[5000, b'Cisco StackWise'], [5050, b'Cisco StackWise Plus']]], [b'Other', [[32767, b'Other']]]], default=1200),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='interfacetemplate',
|
||||
name='form_factor',
|
||||
field=models.PositiveSmallIntegerField(choices=[[b'Virtual interfaces', [[0, b'Virtual']]], [b'Ethernet (fixed)', [[800, b'100BASE-TX (10/100ME)'], [1000, b'1000BASE-T (1GE)'], [1150, b'10GBASE-T (10GE)']]], [b'Ethernet (modular)', [[1050, b'GBIC (1GE)'], [1100, b'SFP (1GE)'], [1200, b'SFP+ (10GE)'], [1300, b'XFP (10GE)'], [1310, b'XENPAK (10GE)'], [1320, b'X2 (10GE)'], [1350, b'SFP28 (25GE)'], [1400, b'QSFP+ (40GE)'], [1500, b'CFP (100GE)'], [1600, b'QSFP28 (100GE)']]], [b'FibreChannel', [[3010, b'SFP (1GFC)'], [3020, b'SFP (2GFC)'], [3040, b'SFP (4GFC)'], [3080, b'SFP+ (8GFC)'], [3160, b'SFP+ (16GFC)']]], [b'Serial', [[4000, b'T1 (1.544 Mbps)'], [4010, b'E1 (2.048 Mbps)'], [4040, b'T3 (45 Mbps)'], [4050, b'E3 (34 Mbps)']]], [b'Stacking', [[5000, b'Cisco StackWise'], [5050, b'Cisco StackWise Plus']]], [b'Other', [[32767, b'Other']]]], default=1200),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='rack',
|
||||
name='desc_units',
|
||||
field=models.BooleanField(default=False, help_text=b'Units are numbered top-to-bottom', verbose_name=b'Descending units'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='device',
|
||||
name='position',
|
||||
field=models.PositiveSmallIntegerField(blank=True, help_text=b'The lowest-numbered unit occupied by the device', null=True, validators=[django.core.validators.MinValueValidator(1)], verbose_name=b'Position (U)'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='interface',
|
||||
name='form_factor',
|
||||
field=models.PositiveSmallIntegerField(choices=[[b'Virtual interfaces', [[0, b'Virtual']]], [b'Ethernet (fixed)', [[800, b'100BASE-TX (10/100ME)'], [1000, b'1000BASE-T (1GE)'], [1150, b'10GBASE-T (10GE)']]], [b'Ethernet (modular)', [[1050, b'GBIC (1GE)'], [1100, b'SFP (1GE)'], [1200, b'SFP+ (10GE)'], [1300, b'XFP (10GE)'], [1310, b'XENPAK (10GE)'], [1320, b'X2 (10GE)'], [1350, b'SFP28 (25GE)'], [1400, b'QSFP+ (40GE)'], [1500, b'CFP (100GE)'], [1600, b'QSFP28 (100GE)']]], [b'FibreChannel', [[3010, b'SFP (1GFC)'], [3020, b'SFP (2GFC)'], [3040, b'SFP (4GFC)'], [3080, b'SFP+ (8GFC)'], [3160, b'SFP+ (16GFC)']]], [b'Serial', [[4000, b'T1 (1.544 Mbps)'], [4010, b'E1 (2.048 Mbps)'], [4040, b'T3 (45 Mbps)'], [4050, b'E3 (34 Mbps)']]], [b'Stacking', [[5000, b'Cisco StackWise'], [5050, b'Cisco StackWise Plus'], [5100, b'Cisco FlexStack'], [5150, b'Cisco FlexStack Plus']]], [b'Other', [[32767, b'Other']]]], default=1200),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='interfacetemplate',
|
||||
name='form_factor',
|
||||
field=models.PositiveSmallIntegerField(choices=[[b'Virtual interfaces', [[0, b'Virtual']]], [b'Ethernet (fixed)', [[800, b'100BASE-TX (10/100ME)'], [1000, b'1000BASE-T (1GE)'], [1150, b'10GBASE-T (10GE)']]], [b'Ethernet (modular)', [[1050, b'GBIC (1GE)'], [1100, b'SFP (1GE)'], [1200, b'SFP+ (10GE)'], [1300, b'XFP (10GE)'], [1310, b'XENPAK (10GE)'], [1320, b'X2 (10GE)'], [1350, b'SFP28 (25GE)'], [1400, b'QSFP+ (40GE)'], [1500, b'CFP (100GE)'], [1600, b'QSFP28 (100GE)']]], [b'FibreChannel', [[3010, b'SFP (1GFC)'], [3020, b'SFP (2GFC)'], [3040, b'SFP (4GFC)'], [3080, b'SFP+ (8GFC)'], [3160, b'SFP+ (16GFC)']]], [b'Serial', [[4000, b'T1 (1.544 Mbps)'], [4010, b'E1 (2.048 Mbps)'], [4040, b'T3 (45 Mbps)'], [4050, b'E3 (34 Mbps)']]], [b'Stacking', [[5000, b'Cisco StackWise'], [5050, b'Cisco StackWise Plus'], [5100, b'Cisco FlexStack'], [5150, b'Cisco FlexStack Plus']]], [b'Other', [[32767, b'Other']]]], default=1200),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=color_names_to_rgb,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='devicerole',
|
||||
name='color',
|
||||
field=utilities.fields.ColorField(max_length=6),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='rackrole',
|
||||
name='color',
|
||||
field=utilities.fields.ColorField(max_length=6),
|
||||
),
|
||||
]
|
||||
@@ -1,12 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.14 on 2018-07-31 02:13
|
||||
import dcim.fields
|
||||
from django.conf import settings
|
||||
import django.contrib.postgres.fields
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import mptt.fields
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
import dcim.fields
|
||||
import utilities.fields
|
||||
|
||||
|
||||
@@ -32,8 +31,8 @@ class Migration(migrations.Migration):
|
||||
replaces = [('dcim', '0023_devicetype_comments'), ('dcim', '0024_site_add_contact_fields'), ('dcim', '0025_devicetype_add_interface_ordering'), ('dcim', '0026_add_rack_reservations'), ('dcim', '0027_device_add_site'), ('dcim', '0028_device_copy_rack_to_site'), ('dcim', '0029_allow_rackless_devices'), ('dcim', '0030_interface_add_lag'), ('dcim', '0031_regions'), ('dcim', '0032_device_increase_name_length'), ('dcim', '0033_rackreservation_rack_editable'), ('dcim', '0034_rename_module_to_inventoryitem'), ('dcim', '0035_device_expand_status_choices'), ('dcim', '0036_add_ff_juniper_vcp'), ('dcim', '0037_unicode_literals'), ('dcim', '0038_wireless_interfaces'), ('dcim', '0039_interface_add_enabled_mtu'), ('dcim', '0040_inventoryitem_add_asset_tag_description'), ('dcim', '0041_napalm_integration'), ('dcim', '0042_interface_ff_10ge_cx4'), ('dcim', '0043_device_component_name_lengths')]
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('dcim', '0022_color_names_to_rgb'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
@@ -94,10 +93,15 @@ class Migration(migrations.Migration):
|
||||
name='site',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='devices', to='dcim.Site'),
|
||||
),
|
||||
migrations.AddField(
|
||||
migrations.AlterField(
|
||||
model_name='interface',
|
||||
name='lag',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='member_interfaces', to='dcim.Interface', verbose_name='Parent LAG'),
|
||||
name='form_factor',
|
||||
field=models.PositiveSmallIntegerField(choices=[[b'Virtual interfaces', [[0, b'Virtual'], [200, b'Link Aggregation Group (LAG)']]], [b'Ethernet (fixed)', [[800, b'100BASE-TX (10/100ME)'], [1000, b'1000BASE-T (1GE)'], [1150, b'10GBASE-T (10GE)']]], [b'Ethernet (modular)', [[1050, b'GBIC (1GE)'], [1100, b'SFP (1GE)'], [1200, b'SFP+ (10GE)'], [1300, b'XFP (10GE)'], [1310, b'XENPAK (10GE)'], [1320, b'X2 (10GE)'], [1350, b'SFP28 (25GE)'], [1400, b'QSFP+ (40GE)'], [1500, b'CFP (100GE)'], [1600, b'QSFP28 (100GE)']]], [b'FibreChannel', [[3010, b'SFP (1GFC)'], [3020, b'SFP (2GFC)'], [3040, b'SFP (4GFC)'], [3080, b'SFP+ (8GFC)'], [3160, b'SFP+ (16GFC)']]], [b'Serial', [[4000, b'T1 (1.544 Mbps)'], [4010, b'E1 (2.048 Mbps)'], [4040, b'T3 (45 Mbps)'], [4050, b'E3 (34 Mbps)'], [4050, b'E3 (34 Mbps)']]], [b'Stacking', [[5000, b'Cisco StackWise'], [5050, b'Cisco StackWise Plus'], [5100, b'Cisco FlexStack'], [5150, b'Cisco FlexStack Plus']]], [b'Other', [[32767, b'Other']]]], default=1200),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='interfacetemplate',
|
||||
name='form_factor',
|
||||
field=models.PositiveSmallIntegerField(choices=[[b'Virtual interfaces', [[0, b'Virtual'], [200, b'Link Aggregation Group (LAG)']]], [b'Ethernet (fixed)', [[800, b'100BASE-TX (10/100ME)'], [1000, b'1000BASE-T (1GE)'], [1150, b'10GBASE-T (10GE)']]], [b'Ethernet (modular)', [[1050, b'GBIC (1GE)'], [1100, b'SFP (1GE)'], [1200, b'SFP+ (10GE)'], [1300, b'XFP (10GE)'], [1310, b'XENPAK (10GE)'], [1320, b'X2 (10GE)'], [1350, b'SFP28 (25GE)'], [1400, b'QSFP+ (40GE)'], [1500, b'CFP (100GE)'], [1600, b'QSFP28 (100GE)']]], [b'FibreChannel', [[3010, b'SFP (1GFC)'], [3020, b'SFP (2GFC)'], [3040, b'SFP (4GFC)'], [3080, b'SFP+ (8GFC)'], [3160, b'SFP+ (16GFC)']]], [b'Serial', [[4000, b'T1 (1.544 Mbps)'], [4010, b'E1 (2.048 Mbps)'], [4040, b'T3 (45 Mbps)'], [4050, b'E3 (34 Mbps)'], [4050, b'E3 (34 Mbps)']]], [b'Stacking', [[5000, b'Cisco StackWise'], [5050, b'Cisco StackWise Plus'], [5100, b'Cisco FlexStack'], [5150, b'Cisco FlexStack Plus']]], [b'Other', [[32767, b'Other']]]], default=1200),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Region',
|
||||
@@ -157,7 +161,17 @@ class Migration(migrations.Migration):
|
||||
migrations.AlterField(
|
||||
model_name='device',
|
||||
name='status',
|
||||
field=models.PositiveSmallIntegerField(choices=[[1, 'Active'], [0, 'Offline'], [2, 'Planned'], [3, 'Staged'], [4, 'Failed'], [5, 'Inventory']], default=1, verbose_name='Status'),
|
||||
field=models.PositiveSmallIntegerField(choices=[[1, b'Active'], [0, b'Offline'], [2, b'Planned'], [3, b'Staged'], [4, b'Failed'], [5, b'Inventory']], default=1, verbose_name=b'Status'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='interface',
|
||||
name='form_factor',
|
||||
field=models.PositiveSmallIntegerField(choices=[[b'Virtual interfaces', [[0, b'Virtual'], [200, b'Link Aggregation Group (LAG)']]], [b'Ethernet (fixed)', [[800, b'100BASE-TX (10/100ME)'], [1000, b'1000BASE-T (1GE)'], [1150, b'10GBASE-T (10GE)']]], [b'Ethernet (modular)', [[1050, b'GBIC (1GE)'], [1100, b'SFP (1GE)'], [1200, b'SFP+ (10GE)'], [1300, b'XFP (10GE)'], [1310, b'XENPAK (10GE)'], [1320, b'X2 (10GE)'], [1350, b'SFP28 (25GE)'], [1400, b'QSFP+ (40GE)'], [1500, b'CFP (100GE)'], [1600, b'QSFP28 (100GE)']]], [b'FibreChannel', [[3010, b'SFP (1GFC)'], [3020, b'SFP (2GFC)'], [3040, b'SFP (4GFC)'], [3080, b'SFP+ (8GFC)'], [3160, b'SFP+ (16GFC)']]], [b'Serial', [[4000, b'T1 (1.544 Mbps)'], [4010, b'E1 (2.048 Mbps)'], [4040, b'T3 (45 Mbps)'], [4050, b'E3 (34 Mbps)'], [4050, b'E3 (34 Mbps)']]], [b'Stacking', [[5000, b'Cisco StackWise'], [5050, b'Cisco StackWise Plus'], [5100, b'Cisco FlexStack'], [5150, b'Cisco FlexStack Plus'], [5200, b'Juniper VCP']]], [b'Other', [[32767, b'Other']]]], default=1200),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='interfacetemplate',
|
||||
name='form_factor',
|
||||
field=models.PositiveSmallIntegerField(choices=[[b'Virtual interfaces', [[0, b'Virtual'], [200, b'Link Aggregation Group (LAG)']]], [b'Ethernet (fixed)', [[800, b'100BASE-TX (10/100ME)'], [1000, b'1000BASE-T (1GE)'], [1150, b'10GBASE-T (10GE)']]], [b'Ethernet (modular)', [[1050, b'GBIC (1GE)'], [1100, b'SFP (1GE)'], [1200, b'SFP+ (10GE)'], [1300, b'XFP (10GE)'], [1310, b'XENPAK (10GE)'], [1320, b'X2 (10GE)'], [1350, b'SFP28 (25GE)'], [1400, b'QSFP+ (40GE)'], [1500, b'CFP (100GE)'], [1600, b'QSFP28 (100GE)']]], [b'FibreChannel', [[3010, b'SFP (1GFC)'], [3020, b'SFP (2GFC)'], [3040, b'SFP (4GFC)'], [3080, b'SFP+ (8GFC)'], [3160, b'SFP+ (16GFC)']]], [b'Serial', [[4000, b'T1 (1.544 Mbps)'], [4010, b'E1 (2.048 Mbps)'], [4040, b'T3 (45 Mbps)'], [4050, b'E3 (34 Mbps)'], [4050, b'E3 (34 Mbps)']]], [b'Stacking', [[5000, b'Cisco StackWise'], [5050, b'Cisco StackWise Plus'], [5100, b'Cisco FlexStack'], [5150, b'Cisco FlexStack Plus'], [5200, b'Juniper VCP']]], [b'Other', [[32767, b'Other']]]], default=1200),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='consoleport',
|
||||
@@ -199,6 +213,11 @@ class Migration(migrations.Migration):
|
||||
name='serial',
|
||||
field=models.CharField(blank=True, max_length=50, verbose_name='Serial number'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='device',
|
||||
name='status',
|
||||
field=models.PositiveSmallIntegerField(choices=[[1, 'Active'], [0, 'Offline'], [2, 'Planned'], [3, 'Staged'], [4, 'Failed'], [5, 'Inventory']], default=1, verbose_name='Status'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='devicebay',
|
||||
name='name',
|
||||
@@ -244,6 +263,16 @@ class Migration(migrations.Migration):
|
||||
name='u_height',
|
||||
field=models.PositiveSmallIntegerField(default=1, verbose_name='Height (U)'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='interface',
|
||||
name='form_factor',
|
||||
field=models.PositiveSmallIntegerField(choices=[['Virtual interfaces', [[0, 'Virtual'], [200, 'Link Aggregation Group (LAG)']]], ['Ethernet (fixed)', [[800, '100BASE-TX (10/100ME)'], [1000, '1000BASE-T (1GE)'], [1150, '10GBASE-T (10GE)']]], ['Ethernet (modular)', [[1050, 'GBIC (1GE)'], [1100, 'SFP (1GE)'], [1200, 'SFP+ (10GE)'], [1300, 'XFP (10GE)'], [1310, 'XENPAK (10GE)'], [1320, 'X2 (10GE)'], [1350, 'SFP28 (25GE)'], [1400, 'QSFP+ (40GE)'], [1500, 'CFP (100GE)'], [1600, 'QSFP28 (100GE)']]], ['FibreChannel', [[3010, 'SFP (1GFC)'], [3020, 'SFP (2GFC)'], [3040, 'SFP (4GFC)'], [3080, 'SFP+ (8GFC)'], [3160, 'SFP+ (16GFC)']]], ['Serial', [[4000, 'T1 (1.544 Mbps)'], [4010, 'E1 (2.048 Mbps)'], [4040, 'T3 (45 Mbps)'], [4050, 'E3 (34 Mbps)'], [4050, 'E3 (34 Mbps)']]], ['Stacking', [[5000, 'Cisco StackWise'], [5050, 'Cisco StackWise Plus'], [5100, 'Cisco FlexStack'], [5150, 'Cisco FlexStack Plus'], [5200, 'Juniper VCP']]], ['Other', [[32767, 'Other']]]], default=1200),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='interface',
|
||||
name='lag',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='member_interfaces', to='dcim.Interface', verbose_name='Parent LAG'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='interface',
|
||||
name='mac_address',
|
||||
@@ -259,6 +288,11 @@ class Migration(migrations.Migration):
|
||||
name='connection_status',
|
||||
field=models.BooleanField(choices=[[False, 'Planned'], [True, 'Connected']], default=True, verbose_name='Status'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='interfacetemplate',
|
||||
name='form_factor',
|
||||
field=models.PositiveSmallIntegerField(choices=[['Virtual interfaces', [[0, 'Virtual'], [200, 'Link Aggregation Group (LAG)']]], ['Ethernet (fixed)', [[800, '100BASE-TX (10/100ME)'], [1000, '1000BASE-T (1GE)'], [1150, '10GBASE-T (10GE)']]], ['Ethernet (modular)', [[1050, 'GBIC (1GE)'], [1100, 'SFP (1GE)'], [1200, 'SFP+ (10GE)'], [1300, 'XFP (10GE)'], [1310, 'XENPAK (10GE)'], [1320, 'X2 (10GE)'], [1350, 'SFP28 (25GE)'], [1400, 'QSFP+ (40GE)'], [1500, 'CFP (100GE)'], [1600, 'QSFP28 (100GE)']]], ['FibreChannel', [[3010, 'SFP (1GFC)'], [3020, 'SFP (2GFC)'], [3040, 'SFP (4GFC)'], [3080, 'SFP+ (8GFC)'], [3160, 'SFP+ (16GFC)']]], ['Serial', [[4000, 'T1 (1.544 Mbps)'], [4010, 'E1 (2.048 Mbps)'], [4040, 'T3 (45 Mbps)'], [4050, 'E3 (34 Mbps)'], [4050, 'E3 (34 Mbps)']]], ['Stacking', [[5000, 'Cisco StackWise'], [5050, 'Cisco StackWise Plus'], [5100, 'Cisco FlexStack'], [5150, 'Cisco FlexStack Plus'], [5200, 'Juniper VCP']]], ['Other', [[32767, 'Other']]]], default=1200),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='interfacetemplate',
|
||||
name='mgmt_only',
|
||||
@@ -329,6 +363,16 @@ class Migration(migrations.Migration):
|
||||
name='contact_email',
|
||||
field=models.EmailField(blank=True, max_length=254, verbose_name='Contact E-mail'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='interface',
|
||||
name='form_factor',
|
||||
field=models.PositiveSmallIntegerField(choices=[['Virtual interfaces', [[0, 'Virtual'], [200, 'Link Aggregation Group (LAG)']]], ['Ethernet (fixed)', [[800, '100BASE-TX (10/100ME)'], [1000, '1000BASE-T (1GE)'], [1150, '10GBASE-T (10GE)']]], ['Ethernet (modular)', [[1050, 'GBIC (1GE)'], [1100, 'SFP (1GE)'], [1200, 'SFP+ (10GE)'], [1300, 'XFP (10GE)'], [1310, 'XENPAK (10GE)'], [1320, 'X2 (10GE)'], [1350, 'SFP28 (25GE)'], [1400, 'QSFP+ (40GE)'], [1500, 'CFP (100GE)'], [1600, 'QSFP28 (100GE)']]], ['Wireless', [[2600, 'IEEE 802.11a'], [2610, 'IEEE 802.11b/g'], [2620, 'IEEE 802.11n'], [2630, 'IEEE 802.11ac'], [2640, 'IEEE 802.11ad']]], ['FibreChannel', [[3010, 'SFP (1GFC)'], [3020, 'SFP (2GFC)'], [3040, 'SFP (4GFC)'], [3080, 'SFP+ (8GFC)'], [3160, 'SFP+ (16GFC)']]], ['Serial', [[4000, 'T1 (1.544 Mbps)'], [4010, 'E1 (2.048 Mbps)'], [4040, 'T3 (45 Mbps)'], [4050, 'E3 (34 Mbps)']]], ['Stacking', [[5000, 'Cisco StackWise'], [5050, 'Cisco StackWise Plus'], [5100, 'Cisco FlexStack'], [5150, 'Cisco FlexStack Plus'], [5200, 'Juniper VCP']]], ['Other', [[32767, 'Other']]]], default=1200),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='interfacetemplate',
|
||||
name='form_factor',
|
||||
field=models.PositiveSmallIntegerField(choices=[['Virtual interfaces', [[0, 'Virtual'], [200, 'Link Aggregation Group (LAG)']]], ['Ethernet (fixed)', [[800, '100BASE-TX (10/100ME)'], [1000, '1000BASE-T (1GE)'], [1150, '10GBASE-T (10GE)']]], ['Ethernet (modular)', [[1050, 'GBIC (1GE)'], [1100, 'SFP (1GE)'], [1200, 'SFP+ (10GE)'], [1300, 'XFP (10GE)'], [1310, 'XENPAK (10GE)'], [1320, 'X2 (10GE)'], [1350, 'SFP28 (25GE)'], [1400, 'QSFP+ (40GE)'], [1500, 'CFP (100GE)'], [1600, 'QSFP28 (100GE)']]], ['Wireless', [[2600, 'IEEE 802.11a'], [2610, 'IEEE 802.11b/g'], [2620, 'IEEE 802.11n'], [2630, 'IEEE 802.11ac'], [2640, 'IEEE 802.11ad']]], ['FibreChannel', [[3010, 'SFP (1GFC)'], [3020, 'SFP (2GFC)'], [3040, 'SFP (4GFC)'], [3080, 'SFP+ (8GFC)'], [3160, 'SFP+ (16GFC)']]], ['Serial', [[4000, 'T1 (1.544 Mbps)'], [4010, 'E1 (2.048 Mbps)'], [4040, 'T3 (45 Mbps)'], [4050, 'E3 (34 Mbps)']]], ['Stacking', [[5000, 'Cisco StackWise'], [5050, 'Cisco StackWise Plus'], [5100, 'Cisco FlexStack'], [5150, 'Cisco FlexStack Plus'], [5200, 'Juniper VCP']]], ['Other', [[32767, 'Other']]]], default=1200),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='interface',
|
||||
name='enabled',
|
||||
|
||||
@@ -1,144 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.14 on 2018-07-31 02:17
|
||||
from django.conf import settings
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import timezone_field.fields
|
||||
import utilities.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
replaces = [('dcim', '0044_virtualization'), ('dcim', '0045_devicerole_vm_role'), ('dcim', '0046_rack_lengthen_facility_id'), ('dcim', '0047_more_100ge_form_factors'), ('dcim', '0048_rack_serial'), ('dcim', '0049_rackreservation_change_user'), ('dcim', '0050_interface_vlan_tagging'), ('dcim', '0051_rackreservation_tenant'), ('dcim', '0052_virtual_chassis'), ('dcim', '0053_platform_manufacturer'), ('dcim', '0054_site_status_timezone_description'), ('dcim', '0055_virtualchassis_ordering')]
|
||||
|
||||
dependencies = [
|
||||
('dcim', '0043_device_component_name_lengths'),
|
||||
('ipam', '0020_ipaddress_add_role_carp'),
|
||||
('virtualization', '0001_virtualization'),
|
||||
('tenancy', '0003_unicode_literals'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='device',
|
||||
name='cluster',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='devices', to='virtualization.Cluster'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='interface',
|
||||
name='virtual_machine',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='interfaces', to='virtualization.VirtualMachine'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='interface',
|
||||
name='device',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='interfaces', to='dcim.Device'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='devicerole',
|
||||
name='vm_role',
|
||||
field=models.BooleanField(default=True, help_text='Virtual machines may be assigned to this role', verbose_name='VM Role'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='rack',
|
||||
name='facility_id',
|
||||
field=utilities.fields.NullableCharField(blank=True, max_length=50, null=True, verbose_name='Facility ID'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='interface',
|
||||
name='form_factor',
|
||||
field=models.PositiveSmallIntegerField(choices=[['Virtual interfaces', [[0, 'Virtual'], [200, 'Link Aggregation Group (LAG)']]], ['Ethernet (fixed)', [[800, '100BASE-TX (10/100ME)'], [1000, '1000BASE-T (1GE)'], [1150, '10GBASE-T (10GE)'], [1170, '10GBASE-CX4 (10GE)']]], ['Ethernet (modular)', [[1050, 'GBIC (1GE)'], [1100, 'SFP (1GE)'], [1200, 'SFP+ (10GE)'], [1300, 'XFP (10GE)'], [1310, 'XENPAK (10GE)'], [1320, 'X2 (10GE)'], [1350, 'SFP28 (25GE)'], [1400, 'QSFP+ (40GE)'], [1500, 'CFP (100GE)'], [1510, 'CFP2 (100GE)'], [1520, 'CFP4 (100GE)'], [1550, 'Cisco CPAK (100GE)'], [1600, 'QSFP28 (100GE)']]], ['Wireless', [[2600, 'IEEE 802.11a'], [2610, 'IEEE 802.11b/g'], [2620, 'IEEE 802.11n'], [2630, 'IEEE 802.11ac'], [2640, 'IEEE 802.11ad']]], ['FibreChannel', [[3010, 'SFP (1GFC)'], [3020, 'SFP (2GFC)'], [3040, 'SFP (4GFC)'], [3080, 'SFP+ (8GFC)'], [3160, 'SFP+ (16GFC)']]], ['Serial', [[4000, 'T1 (1.544 Mbps)'], [4010, 'E1 (2.048 Mbps)'], [4040, 'T3 (45 Mbps)'], [4050, 'E3 (34 Mbps)']]], ['Stacking', [[5000, 'Cisco StackWise'], [5050, 'Cisco StackWise Plus'], [5100, 'Cisco FlexStack'], [5150, 'Cisco FlexStack Plus'], [5200, 'Juniper VCP']]], ['Other', [[32767, 'Other']]]], default=1200),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='interfacetemplate',
|
||||
name='form_factor',
|
||||
field=models.PositiveSmallIntegerField(choices=[['Virtual interfaces', [[0, 'Virtual'], [200, 'Link Aggregation Group (LAG)']]], ['Ethernet (fixed)', [[800, '100BASE-TX (10/100ME)'], [1000, '1000BASE-T (1GE)'], [1150, '10GBASE-T (10GE)'], [1170, '10GBASE-CX4 (10GE)']]], ['Ethernet (modular)', [[1050, 'GBIC (1GE)'], [1100, 'SFP (1GE)'], [1200, 'SFP+ (10GE)'], [1300, 'XFP (10GE)'], [1310, 'XENPAK (10GE)'], [1320, 'X2 (10GE)'], [1350, 'SFP28 (25GE)'], [1400, 'QSFP+ (40GE)'], [1500, 'CFP (100GE)'], [1510, 'CFP2 (100GE)'], [1520, 'CFP4 (100GE)'], [1550, 'Cisco CPAK (100GE)'], [1600, 'QSFP28 (100GE)']]], ['Wireless', [[2600, 'IEEE 802.11a'], [2610, 'IEEE 802.11b/g'], [2620, 'IEEE 802.11n'], [2630, 'IEEE 802.11ac'], [2640, 'IEEE 802.11ad']]], ['FibreChannel', [[3010, 'SFP (1GFC)'], [3020, 'SFP (2GFC)'], [3040, 'SFP (4GFC)'], [3080, 'SFP+ (8GFC)'], [3160, 'SFP+ (16GFC)']]], ['Serial', [[4000, 'T1 (1.544 Mbps)'], [4010, 'E1 (2.048 Mbps)'], [4040, 'T3 (45 Mbps)'], [4050, 'E3 (34 Mbps)']]], ['Stacking', [[5000, 'Cisco StackWise'], [5050, 'Cisco StackWise Plus'], [5100, 'Cisco FlexStack'], [5150, 'Cisco FlexStack Plus'], [5200, 'Juniper VCP']]], ['Other', [[32767, 'Other']]]], default=1200),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='rack',
|
||||
name='serial',
|
||||
field=models.CharField(blank=True, max_length=50, verbose_name='Serial number'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='rackreservation',
|
||||
name='user',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='interface',
|
||||
name='mode',
|
||||
field=models.PositiveSmallIntegerField(blank=True, choices=[[100, 'Access'], [200, 'Tagged'], [300, 'Tagged All']], null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='interface',
|
||||
name='tagged_vlans',
|
||||
field=models.ManyToManyField(blank=True, related_name='interfaces_as_tagged', to='ipam.VLAN', verbose_name='Tagged VLANs'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='interface',
|
||||
name='untagged_vlan',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='interfaces_as_untagged', to='ipam.VLAN', verbose_name='Untagged VLAN'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='rackreservation',
|
||||
name='tenant',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='rackreservations', to='tenancy.Tenant'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='VirtualChassis',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('domain', models.CharField(blank=True, max_length=30)),
|
||||
('master', models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, related_name='vc_master_for', to='dcim.Device')),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'virtual chassis',
|
||||
'ordering': ['master'],
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='device',
|
||||
name='virtual_chassis',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='members', to='dcim.VirtualChassis'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='device',
|
||||
name='vc_position',
|
||||
field=models.PositiveSmallIntegerField(blank=True, null=True, validators=[django.core.validators.MaxValueValidator(255)]),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='device',
|
||||
name='vc_priority',
|
||||
field=models.PositiveSmallIntegerField(blank=True, null=True, validators=[django.core.validators.MaxValueValidator(255)]),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='device',
|
||||
unique_together=set([('rack', 'position', 'face'), ('virtual_chassis', 'vc_position')]),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='platform',
|
||||
name='manufacturer',
|
||||
field=models.ForeignKey(blank=True, help_text='Optionally limit this platform to devices of a certain manufacturer', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='platforms', to='dcim.Manufacturer'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='platform',
|
||||
name='napalm_driver',
|
||||
field=models.CharField(blank=True, help_text='The name of the NAPALM driver to use when interacting with devices', max_length=50, verbose_name='NAPALM driver'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='site',
|
||||
name='description',
|
||||
field=models.CharField(blank=True, max_length=100),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='site',
|
||||
name='status',
|
||||
field=models.PositiveSmallIntegerField(choices=[[1, 'Active'], [2, 'Planned'], [4, 'Retired']], default=1),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='site',
|
||||
name='time_zone',
|
||||
field=timezone_field.fields.TimeZoneField(blank=True),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,354 @@
|
||||
import django.contrib.postgres.fields.jsonb
|
||||
import django.core.validators
|
||||
import django.db.models.deletion
|
||||
import taggit.managers
|
||||
import timezone_field.fields
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
import utilities.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
replaces = [('dcim', '0044_virtualization'), ('dcim', '0045_devicerole_vm_role'), ('dcim', '0046_rack_lengthen_facility_id'), ('dcim', '0047_more_100ge_form_factors'), ('dcim', '0048_rack_serial'), ('dcim', '0049_rackreservation_change_user'), ('dcim', '0050_interface_vlan_tagging'), ('dcim', '0051_rackreservation_tenant'), ('dcim', '0052_virtual_chassis'), ('dcim', '0053_platform_manufacturer'), ('dcim', '0054_site_status_timezone_description'), ('dcim', '0055_virtualchassis_ordering'), ('dcim', '0056_django2'), ('dcim', '0057_tags'), ('dcim', '0058_relax_rack_naming_constraints'), ('dcim', '0059_site_latitude_longitude'), ('dcim', '0060_change_logging'), ('dcim', '0061_platform_napalm_args')]
|
||||
|
||||
dependencies = [
|
||||
('virtualization', '0001_virtualization'),
|
||||
('tenancy', '0003_unicode_literals'),
|
||||
('ipam', '0020_ipaddress_add_role_carp'),
|
||||
('dcim', '0043_device_component_name_lengths'),
|
||||
('taggit', '0002_auto_20150616_2121'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='device',
|
||||
name='cluster',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='devices', to='virtualization.Cluster'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='interface',
|
||||
name='virtual_machine',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='interfaces', to='virtualization.VirtualMachine'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='interface',
|
||||
name='device',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='interfaces', to='dcim.Device'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='devicerole',
|
||||
name='vm_role',
|
||||
field=models.BooleanField(default=True, help_text='Virtual machines may be assigned to this role', verbose_name='VM Role'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='rack',
|
||||
name='facility_id',
|
||||
field=utilities.fields.NullableCharField(blank=True, max_length=50, null=True, verbose_name='Facility ID'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='interface',
|
||||
name='form_factor',
|
||||
field=models.PositiveSmallIntegerField(choices=[['Virtual interfaces', [[0, 'Virtual'], [200, 'Link Aggregation Group (LAG)']]], ['Ethernet (fixed)', [[800, '100BASE-TX (10/100ME)'], [1000, '1000BASE-T (1GE)'], [1150, '10GBASE-T (10GE)'], [1170, '10GBASE-CX4 (10GE)']]], ['Ethernet (modular)', [[1050, 'GBIC (1GE)'], [1100, 'SFP (1GE)'], [1200, 'SFP+ (10GE)'], [1300, 'XFP (10GE)'], [1310, 'XENPAK (10GE)'], [1320, 'X2 (10GE)'], [1350, 'SFP28 (25GE)'], [1400, 'QSFP+ (40GE)'], [1500, 'CFP (100GE)'], [1510, 'CFP2 (100GE)'], [1520, 'CFP4 (100GE)'], [1550, 'Cisco CPAK (100GE)'], [1600, 'QSFP28 (100GE)']]], ['Wireless', [[2600, 'IEEE 802.11a'], [2610, 'IEEE 802.11b/g'], [2620, 'IEEE 802.11n'], [2630, 'IEEE 802.11ac'], [2640, 'IEEE 802.11ad']]], ['FibreChannel', [[3010, 'SFP (1GFC)'], [3020, 'SFP (2GFC)'], [3040, 'SFP (4GFC)'], [3080, 'SFP+ (8GFC)'], [3160, 'SFP+ (16GFC)']]], ['Serial', [[4000, 'T1 (1.544 Mbps)'], [4010, 'E1 (2.048 Mbps)'], [4040, 'T3 (45 Mbps)'], [4050, 'E3 (34 Mbps)']]], ['Stacking', [[5000, 'Cisco StackWise'], [5050, 'Cisco StackWise Plus'], [5100, 'Cisco FlexStack'], [5150, 'Cisco FlexStack Plus'], [5200, 'Juniper VCP']]], ['Other', [[32767, 'Other']]]], default=1200),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='interfacetemplate',
|
||||
name='form_factor',
|
||||
field=models.PositiveSmallIntegerField(choices=[['Virtual interfaces', [[0, 'Virtual'], [200, 'Link Aggregation Group (LAG)']]], ['Ethernet (fixed)', [[800, '100BASE-TX (10/100ME)'], [1000, '1000BASE-T (1GE)'], [1150, '10GBASE-T (10GE)'], [1170, '10GBASE-CX4 (10GE)']]], ['Ethernet (modular)', [[1050, 'GBIC (1GE)'], [1100, 'SFP (1GE)'], [1200, 'SFP+ (10GE)'], [1300, 'XFP (10GE)'], [1310, 'XENPAK (10GE)'], [1320, 'X2 (10GE)'], [1350, 'SFP28 (25GE)'], [1400, 'QSFP+ (40GE)'], [1500, 'CFP (100GE)'], [1510, 'CFP2 (100GE)'], [1520, 'CFP4 (100GE)'], [1550, 'Cisco CPAK (100GE)'], [1600, 'QSFP28 (100GE)']]], ['Wireless', [[2600, 'IEEE 802.11a'], [2610, 'IEEE 802.11b/g'], [2620, 'IEEE 802.11n'], [2630, 'IEEE 802.11ac'], [2640, 'IEEE 802.11ad']]], ['FibreChannel', [[3010, 'SFP (1GFC)'], [3020, 'SFP (2GFC)'], [3040, 'SFP (4GFC)'], [3080, 'SFP+ (8GFC)'], [3160, 'SFP+ (16GFC)']]], ['Serial', [[4000, 'T1 (1.544 Mbps)'], [4010, 'E1 (2.048 Mbps)'], [4040, 'T3 (45 Mbps)'], [4050, 'E3 (34 Mbps)']]], ['Stacking', [[5000, 'Cisco StackWise'], [5050, 'Cisco StackWise Plus'], [5100, 'Cisco FlexStack'], [5150, 'Cisco FlexStack Plus'], [5200, 'Juniper VCP']]], ['Other', [[32767, 'Other']]]], default=1200),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='rack',
|
||||
name='serial',
|
||||
field=models.CharField(blank=True, max_length=50, verbose_name='Serial number'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='rackreservation',
|
||||
name='user',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='interface',
|
||||
name='mode',
|
||||
field=models.PositiveSmallIntegerField(blank=True, choices=[[100, 'Access'], [200, 'Tagged'], [300, 'Tagged All']], null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='interface',
|
||||
name='tagged_vlans',
|
||||
field=models.ManyToManyField(blank=True, related_name='interfaces_as_tagged', to='ipam.VLAN', verbose_name='Tagged VLANs'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='rackreservation',
|
||||
name='tenant',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='rackreservations', to='tenancy.Tenant'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='VirtualChassis',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('domain', models.CharField(blank=True, max_length=30)),
|
||||
('master', models.OneToOneField(default=1, on_delete=django.db.models.deletion.PROTECT, related_name='vc_master_for', to='dcim.Device')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['master'],
|
||||
'verbose_name_plural': 'virtual chassis',
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='device',
|
||||
name='virtual_chassis',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='members', to='dcim.VirtualChassis'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='device',
|
||||
name='vc_position',
|
||||
field=models.PositiveSmallIntegerField(blank=True, null=True, validators=[django.core.validators.MaxValueValidator(255)]),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='device',
|
||||
name='vc_priority',
|
||||
field=models.PositiveSmallIntegerField(blank=True, null=True, validators=[django.core.validators.MaxValueValidator(255)]),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='device',
|
||||
unique_together={('rack', 'position', 'face'), ('virtual_chassis', 'vc_position')},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='platform',
|
||||
name='napalm_driver',
|
||||
field=models.CharField(blank=True, help_text='The name of the NAPALM driver to use when interacting with devices', max_length=50, verbose_name='NAPALM driver'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='site',
|
||||
name='description',
|
||||
field=models.CharField(blank=True, max_length=100),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='site',
|
||||
name='status',
|
||||
field=models.PositiveSmallIntegerField(choices=[[1, 'Active'], [2, 'Planned'], [4, 'Retired']], default=1),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='site',
|
||||
name='time_zone',
|
||||
field=timezone_field.fields.TimeZoneField(blank=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='virtualchassis',
|
||||
name='master',
|
||||
field=models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, related_name='vc_master_for', to='dcim.Device'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='interface',
|
||||
name='untagged_vlan',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='interfaces_as_untagged', to='ipam.VLAN', verbose_name='Untagged VLAN'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='platform',
|
||||
name='manufacturer',
|
||||
field=models.ForeignKey(blank=True, help_text='Optionally limit this platform to devices of a certain manufacturer', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='platforms', to='dcim.Manufacturer'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='device',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='devicetype',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='rack',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='site',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='consoleport',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='consoleserverport',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='devicebay',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='interface',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='inventoryitem',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='poweroutlet',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='powerport',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='virtualchassis',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags'),
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='rack',
|
||||
options={'ordering': ['site', 'group', 'name']},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='rack',
|
||||
unique_together={('group', 'name'), ('group', 'facility_id')},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='site',
|
||||
name='latitude',
|
||||
field=models.DecimalField(blank=True, decimal_places=6, max_digits=8, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='site',
|
||||
name='longitude',
|
||||
field=models.DecimalField(blank=True, decimal_places=6, max_digits=9, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='devicerole',
|
||||
name='created',
|
||||
field=models.DateField(auto_now_add=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='devicerole',
|
||||
name='last_updated',
|
||||
field=models.DateTimeField(auto_now=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='devicetype',
|
||||
name='created',
|
||||
field=models.DateField(auto_now_add=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='devicetype',
|
||||
name='last_updated',
|
||||
field=models.DateTimeField(auto_now=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='manufacturer',
|
||||
name='created',
|
||||
field=models.DateField(auto_now_add=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='manufacturer',
|
||||
name='last_updated',
|
||||
field=models.DateTimeField(auto_now=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='platform',
|
||||
name='created',
|
||||
field=models.DateField(auto_now_add=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='platform',
|
||||
name='last_updated',
|
||||
field=models.DateTimeField(auto_now=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='rackgroup',
|
||||
name='created',
|
||||
field=models.DateField(auto_now_add=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='rackgroup',
|
||||
name='last_updated',
|
||||
field=models.DateTimeField(auto_now=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='rackreservation',
|
||||
name='last_updated',
|
||||
field=models.DateTimeField(auto_now=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='rackrole',
|
||||
name='created',
|
||||
field=models.DateField(auto_now_add=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='rackrole',
|
||||
name='last_updated',
|
||||
field=models.DateTimeField(auto_now=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='region',
|
||||
name='created',
|
||||
field=models.DateField(auto_now_add=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='region',
|
||||
name='last_updated',
|
||||
field=models.DateTimeField(auto_now=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='virtualchassis',
|
||||
name='created',
|
||||
field=models.DateField(auto_now_add=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='virtualchassis',
|
||||
name='last_updated',
|
||||
field=models.DateTimeField(auto_now=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='device',
|
||||
name='created',
|
||||
field=models.DateField(auto_now_add=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='device',
|
||||
name='last_updated',
|
||||
field=models.DateTimeField(auto_now=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='rack',
|
||||
name='created',
|
||||
field=models.DateField(auto_now_add=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='rack',
|
||||
name='last_updated',
|
||||
field=models.DateTimeField(auto_now=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='rackreservation',
|
||||
name='created',
|
||||
field=models.DateField(auto_now_add=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='site',
|
||||
name='created',
|
||||
field=models.DateField(auto_now_add=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='site',
|
||||
name='last_updated',
|
||||
field=models.DateTimeField(auto_now=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='platform',
|
||||
name='napalm_args',
|
||||
field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, help_text='Additional arguments to pass when initiating the NAPALM driver (JSON format)', null=True, verbose_name='NAPALM arguments'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,124 @@
|
||||
import django.contrib.postgres.fields.jsonb
|
||||
import django.core.validators
|
||||
import django.db.models.deletion
|
||||
import taggit.managers
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
replaces = [('dcim', '0062_interface_mtu'), ('dcim', '0063_device_local_context_data'), ('dcim', '0064_remove_platform_rpc_client'), ('dcim', '0065_front_rear_ports')]
|
||||
|
||||
dependencies = [
|
||||
('taggit', '0002_auto_20150616_2121'),
|
||||
('dcim', '0061_platform_napalm_args'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='interface',
|
||||
name='mtu',
|
||||
field=models.PositiveIntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(65536)], verbose_name='MTU'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='interface',
|
||||
name='form_factor',
|
||||
field=models.PositiveSmallIntegerField(choices=[['Virtual interfaces', [[0, 'Virtual'], [200, 'Link Aggregation Group (LAG)']]], ['Ethernet (fixed)', [[800, '100BASE-TX (10/100ME)'], [1000, '1000BASE-T (1GE)'], [1150, '10GBASE-T (10GE)'], [1170, '10GBASE-CX4 (10GE)']]], ['Ethernet (modular)', [[1050, 'GBIC (1GE)'], [1100, 'SFP (1GE)'], [1200, 'SFP+ (10GE)'], [1300, 'XFP (10GE)'], [1310, 'XENPAK (10GE)'], [1320, 'X2 (10GE)'], [1350, 'SFP28 (25GE)'], [1400, 'QSFP+ (40GE)'], [1500, 'CFP (100GE)'], [1510, 'CFP2 (100GE)'], [1520, 'CFP4 (100GE)'], [1550, 'Cisco CPAK (100GE)'], [1600, 'QSFP28 (100GE)']]], ['Wireless', [[2600, 'IEEE 802.11a'], [2610, 'IEEE 802.11b/g'], [2620, 'IEEE 802.11n'], [2630, 'IEEE 802.11ac'], [2640, 'IEEE 802.11ad']]], ['SONET', [[6100, 'OC-3/STM-1'], [6200, 'OC-12/STM-4'], [6300, 'OC-48/STM-16'], [6400, 'OC-192/STM-64'], [6500, 'OC-768/STM-256'], [6600, 'OC-1920/STM-640'], [6700, 'OC-3840/STM-1234']]], ['FibreChannel', [[3010, 'SFP (1GFC)'], [3020, 'SFP (2GFC)'], [3040, 'SFP (4GFC)'], [3080, 'SFP+ (8GFC)'], [3160, 'SFP+ (16GFC)'], [3320, 'SFP28 (32GFC)']]], ['Serial', [[4000, 'T1 (1.544 Mbps)'], [4010, 'E1 (2.048 Mbps)'], [4040, 'T3 (45 Mbps)'], [4050, 'E3 (34 Mbps)']]], ['Stacking', [[5000, 'Cisco StackWise'], [5050, 'Cisco StackWise Plus'], [5100, 'Cisco FlexStack'], [5150, 'Cisco FlexStack Plus'], [5200, 'Juniper VCP'], [5300, 'Extreme SummitStack'], [5310, 'Extreme SummitStack-128'], [5320, 'Extreme SummitStack-256'], [5330, 'Extreme SummitStack-512']]], ['Other', [[32767, 'Other']]]], default=1200),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='interfacetemplate',
|
||||
name='form_factor',
|
||||
field=models.PositiveSmallIntegerField(choices=[['Virtual interfaces', [[0, 'Virtual'], [200, 'Link Aggregation Group (LAG)']]], ['Ethernet (fixed)', [[800, '100BASE-TX (10/100ME)'], [1000, '1000BASE-T (1GE)'], [1150, '10GBASE-T (10GE)'], [1170, '10GBASE-CX4 (10GE)']]], ['Ethernet (modular)', [[1050, 'GBIC (1GE)'], [1100, 'SFP (1GE)'], [1200, 'SFP+ (10GE)'], [1300, 'XFP (10GE)'], [1310, 'XENPAK (10GE)'], [1320, 'X2 (10GE)'], [1350, 'SFP28 (25GE)'], [1400, 'QSFP+ (40GE)'], [1500, 'CFP (100GE)'], [1510, 'CFP2 (100GE)'], [1520, 'CFP4 (100GE)'], [1550, 'Cisco CPAK (100GE)'], [1600, 'QSFP28 (100GE)']]], ['Wireless', [[2600, 'IEEE 802.11a'], [2610, 'IEEE 802.11b/g'], [2620, 'IEEE 802.11n'], [2630, 'IEEE 802.11ac'], [2640, 'IEEE 802.11ad']]], ['SONET', [[6100, 'OC-3/STM-1'], [6200, 'OC-12/STM-4'], [6300, 'OC-48/STM-16'], [6400, 'OC-192/STM-64'], [6500, 'OC-768/STM-256'], [6600, 'OC-1920/STM-640'], [6700, 'OC-3840/STM-1234']]], ['FibreChannel', [[3010, 'SFP (1GFC)'], [3020, 'SFP (2GFC)'], [3040, 'SFP (4GFC)'], [3080, 'SFP+ (8GFC)'], [3160, 'SFP+ (16GFC)'], [3320, 'SFP28 (32GFC)']]], ['Serial', [[4000, 'T1 (1.544 Mbps)'], [4010, 'E1 (2.048 Mbps)'], [4040, 'T3 (45 Mbps)'], [4050, 'E3 (34 Mbps)']]], ['Stacking', [[5000, 'Cisco StackWise'], [5050, 'Cisco StackWise Plus'], [5100, 'Cisco FlexStack'], [5150, 'Cisco FlexStack Plus'], [5200, 'Juniper VCP'], [5300, 'Extreme SummitStack'], [5310, 'Extreme SummitStack-128'], [5320, 'Extreme SummitStack-256'], [5330, 'Extreme SummitStack-512']]], ['Other', [[32767, 'Other']]]], default=1200),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='device',
|
||||
name='local_context_data',
|
||||
field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True),
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='platform',
|
||||
name='rpc_client',
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='RearPort',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=64)),
|
||||
('type', models.PositiveSmallIntegerField()),
|
||||
('positions', models.PositiveSmallIntegerField(default=1, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(64)])),
|
||||
('description', models.CharField(blank=True, max_length=100)),
|
||||
('device', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='rearports', to='dcim.Device')),
|
||||
('tags', taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['device', 'name'],
|
||||
'unique_together': {('device', 'name')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='RearPortTemplate',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=64)),
|
||||
('type', models.PositiveSmallIntegerField()),
|
||||
('positions', models.PositiveSmallIntegerField(default=1, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(64)])),
|
||||
('device_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='rearport_templates', to='dcim.DeviceType')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['device_type', 'name'],
|
||||
'unique_together': {('device_type', 'name')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='FrontPortTemplate',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=64)),
|
||||
('type', models.PositiveSmallIntegerField()),
|
||||
('rear_port_position', models.PositiveSmallIntegerField(default=1, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(64)])),
|
||||
('device_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='frontport_templates', to='dcim.DeviceType')),
|
||||
('rear_port', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='frontport_templates', to='dcim.RearPortTemplate')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['device_type', 'name'],
|
||||
'unique_together': {('rear_port', 'rear_port_position'), ('device_type', 'name')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='FrontPort',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=64)),
|
||||
('type', models.PositiveSmallIntegerField()),
|
||||
('rear_port_position', models.PositiveSmallIntegerField(default=1, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(64)])),
|
||||
('description', models.CharField(blank=True, max_length=100)),
|
||||
('device', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='frontports', to='dcim.Device')),
|
||||
('rear_port', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='frontports', to='dcim.RearPort')),
|
||||
('tags', taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['device', 'name'],
|
||||
'unique_together': {('device', 'name'), ('rear_port', 'rear_port_position')},
|
||||
},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='consoleporttemplate',
|
||||
name='device_type',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='consoleport_templates', to='dcim.DeviceType'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='consoleserverporttemplate',
|
||||
name='device_type',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='consoleserverport_templates', to='dcim.DeviceType'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='poweroutlettemplate',
|
||||
name='device_type',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='poweroutlet_templates', to='dcim.DeviceType'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='powerporttemplate',
|
||||
name='device_type',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='powerport_templates', to='dcim.DeviceType'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,146 @@
|
||||
import taggit.managers
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
replaces = [('dcim', '0067_device_type_remove_qualifiers'), ('dcim', '0068_rack_new_fields'), ('dcim', '0069_deprecate_nullablecharfield'), ('dcim', '0070_custom_tag_models')]
|
||||
|
||||
dependencies = [
|
||||
('extras', '0019_tag_taggeditem'),
|
||||
('dcim', '0066_cables'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='devicetype',
|
||||
name='is_console_server',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='devicetype',
|
||||
name='is_network_device',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='devicetype',
|
||||
name='is_pdu',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='devicetype',
|
||||
name='interface_ordering',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='rack',
|
||||
name='status',
|
||||
field=models.PositiveSmallIntegerField(default=3),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='rack',
|
||||
name='outer_depth',
|
||||
field=models.PositiveSmallIntegerField(blank=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='rack',
|
||||
name='outer_unit',
|
||||
field=models.PositiveSmallIntegerField(blank=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='rack',
|
||||
name='outer_width',
|
||||
field=models.PositiveSmallIntegerField(blank=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='device',
|
||||
name='asset_tag',
|
||||
field=models.CharField(blank=True, max_length=50, null=True, unique=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='device',
|
||||
name='name',
|
||||
field=models.CharField(blank=True, max_length=64, null=True, unique=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='inventoryitem',
|
||||
name='asset_tag',
|
||||
field=models.CharField(blank=True, max_length=50, null=True, unique=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='rack',
|
||||
name='asset_tag',
|
||||
field=models.CharField(blank=True, max_length=50, null=True, unique=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='rack',
|
||||
name='facility_id',
|
||||
field=models.CharField(blank=True, max_length=50, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='consoleport',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='extras.TaggedItem', to='extras.Tag', verbose_name='Tags'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='consoleserverport',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='extras.TaggedItem', to='extras.Tag', verbose_name='Tags'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='device',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='extras.TaggedItem', to='extras.Tag', verbose_name='Tags'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='devicebay',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='extras.TaggedItem', to='extras.Tag', verbose_name='Tags'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='devicetype',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='extras.TaggedItem', to='extras.Tag', verbose_name='Tags'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='frontport',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='extras.TaggedItem', to='extras.Tag', verbose_name='Tags'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='interface',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='extras.TaggedItem', to='extras.Tag', verbose_name='Tags'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='inventoryitem',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='extras.TaggedItem', to='extras.Tag', verbose_name='Tags'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='poweroutlet',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='extras.TaggedItem', to='extras.Tag', verbose_name='Tags'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='powerport',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='extras.TaggedItem', to='extras.Tag', verbose_name='Tags'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='rack',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='extras.TaggedItem', to='extras.Tag', verbose_name='Tags'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='rearport',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='extras.TaggedItem', to='extras.Tag', verbose_name='Tags'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='site',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='extras.TaggedItem', to='extras.Tag', verbose_name='Tags'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='virtualchassis',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='extras.TaggedItem', to='extras.Tag', verbose_name='Tags'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,839 @@
|
||||
import sys
|
||||
|
||||
import django.core.validators
|
||||
import django.db.models.deletion
|
||||
import taggit.managers
|
||||
from django.db import migrations, models
|
||||
|
||||
SITE_STATUS_CHOICES = (
|
||||
(1, 'active'),
|
||||
(2, 'planned'),
|
||||
(4, 'retired'),
|
||||
)
|
||||
|
||||
RACK_TYPE_CHOICES = (
|
||||
(100, '2-post-frame'),
|
||||
(200, '4-post-frame'),
|
||||
(300, '4-post-cabinet'),
|
||||
(1000, 'wall-frame'),
|
||||
(1100, 'wall-cabinet'),
|
||||
)
|
||||
|
||||
RACK_STATUS_CHOICES = (
|
||||
(0, 'reserved'),
|
||||
(1, 'available'),
|
||||
(2, 'planned'),
|
||||
(3, 'active'),
|
||||
(4, 'deprecated'),
|
||||
)
|
||||
|
||||
RACK_DIMENSION_CHOICES = (
|
||||
(1000, 'mm'),
|
||||
(2000, 'in'),
|
||||
)
|
||||
|
||||
SUBDEVICE_ROLE_CHOICES = (
|
||||
('true', 'parent'),
|
||||
('false', 'child'),
|
||||
)
|
||||
|
||||
DEVICE_FACE_CHOICES = (
|
||||
(0, 'front'),
|
||||
(1, 'rear'),
|
||||
)
|
||||
|
||||
DEVICE_STATUS_CHOICES = (
|
||||
(0, 'offline'),
|
||||
(1, 'active'),
|
||||
(2, 'planned'),
|
||||
(3, 'staged'),
|
||||
(4, 'failed'),
|
||||
(5, 'inventory'),
|
||||
(6, 'decommissioning'),
|
||||
)
|
||||
|
||||
INTERFACE_TYPE_CHOICES = (
|
||||
(0, 'virtual'),
|
||||
(200, 'lag'),
|
||||
(800, '100base-tx'),
|
||||
(1000, '1000base-t'),
|
||||
(1050, '1000base-x-gbic'),
|
||||
(1100, '1000base-x-sfp'),
|
||||
(1120, '2.5gbase-t'),
|
||||
(1130, '5gbase-t'),
|
||||
(1150, '10gbase-t'),
|
||||
(1170, '10gbase-cx4'),
|
||||
(1200, '10gbase-x-sfpp'),
|
||||
(1300, '10gbase-x-xfp'),
|
||||
(1310, '10gbase-x-xenpak'),
|
||||
(1320, '10gbase-x-x2'),
|
||||
(1350, '25gbase-x-sfp28'),
|
||||
(1400, '40gbase-x-qsfpp'),
|
||||
(1420, '50gbase-x-sfp28'),
|
||||
(1500, '100gbase-x-cfp'),
|
||||
(1510, '100gbase-x-cfp2'),
|
||||
(1520, '100gbase-x-cfp4'),
|
||||
(1550, '100gbase-x-cpak'),
|
||||
(1600, '100gbase-x-qsfp28'),
|
||||
(1650, '200gbase-x-cfp2'),
|
||||
(1700, '200gbase-x-qsfp56'),
|
||||
(1750, '400gbase-x-qsfpdd'),
|
||||
(1800, '400gbase-x-osfp'),
|
||||
(2600, 'ieee802.11a'),
|
||||
(2610, 'ieee802.11g'),
|
||||
(2620, 'ieee802.11n'),
|
||||
(2630, 'ieee802.11ac'),
|
||||
(2640, 'ieee802.11ad'),
|
||||
(2810, 'gsm'),
|
||||
(2820, 'cdma'),
|
||||
(2830, 'lte'),
|
||||
(6100, 'sonet-oc3'),
|
||||
(6200, 'sonet-oc12'),
|
||||
(6300, 'sonet-oc48'),
|
||||
(6400, 'sonet-oc192'),
|
||||
(6500, 'sonet-oc768'),
|
||||
(6600, 'sonet-oc1920'),
|
||||
(6700, 'sonet-oc3840'),
|
||||
(3010, '1gfc-sfp'),
|
||||
(3020, '2gfc-sfp'),
|
||||
(3040, '4gfc-sfp'),
|
||||
(3080, '8gfc-sfpp'),
|
||||
(3160, '16gfc-sfpp'),
|
||||
(3320, '32gfc-sfp28'),
|
||||
(3400, '128gfc-sfp28'),
|
||||
(7010, 'inifiband-sdr'),
|
||||
(7020, 'inifiband-ddr'),
|
||||
(7030, 'inifiband-qdr'),
|
||||
(7040, 'inifiband-fdr10'),
|
||||
(7050, 'inifiband-fdr'),
|
||||
(7060, 'inifiband-edr'),
|
||||
(7070, 'inifiband-hdr'),
|
||||
(7080, 'inifiband-ndr'),
|
||||
(7090, 'inifiband-xdr'),
|
||||
(4000, 't1'),
|
||||
(4010, 'e1'),
|
||||
(4040, 't3'),
|
||||
(4050, 'e3'),
|
||||
(5000, 'cisco-stackwise'),
|
||||
(5050, 'cisco-stackwise-plus'),
|
||||
(5100, 'cisco-flexstack'),
|
||||
(5150, 'cisco-flexstack-plus'),
|
||||
(5200, 'juniper-vcp'),
|
||||
(5300, 'extreme-summitstack'),
|
||||
(5310, 'extreme-summitstack-128'),
|
||||
(5320, 'extreme-summitstack-256'),
|
||||
(5330, 'extreme-summitstack-512'),
|
||||
)
|
||||
|
||||
INTERFACE_MODE_CHOICES = (
|
||||
(100, 'access'),
|
||||
(200, 'tagged'),
|
||||
(300, 'tagged-all'),
|
||||
)
|
||||
|
||||
PORT_TYPE_CHOICES = (
|
||||
(1000, '8p8c'),
|
||||
(1100, '110-punch'),
|
||||
(1200, 'bnc'),
|
||||
(2000, 'st'),
|
||||
(2100, 'sc'),
|
||||
(2110, 'sc-apc'),
|
||||
(2200, 'fc'),
|
||||
(2300, 'lc'),
|
||||
(2310, 'lc-apc'),
|
||||
(2400, 'mtrj'),
|
||||
(2500, 'mpo'),
|
||||
(2600, 'lsh'),
|
||||
(2610, 'lsh-apc'),
|
||||
)
|
||||
|
||||
CABLE_TYPE_CHOICES = (
|
||||
(1300, 'cat3'),
|
||||
(1500, 'cat5'),
|
||||
(1510, 'cat5e'),
|
||||
(1600, 'cat6'),
|
||||
(1610, 'cat6a'),
|
||||
(1700, 'cat7'),
|
||||
(1800, 'dac-active'),
|
||||
(1810, 'dac-passive'),
|
||||
(1900, 'coaxial'),
|
||||
(3000, 'mmf'),
|
||||
(3010, 'mmf-om1'),
|
||||
(3020, 'mmf-om2'),
|
||||
(3030, 'mmf-om3'),
|
||||
(3040, 'mmf-om4'),
|
||||
(3500, 'smf'),
|
||||
(3510, 'smf-os1'),
|
||||
(3520, 'smf-os2'),
|
||||
(3800, 'aoc'),
|
||||
(5000, 'power'),
|
||||
)
|
||||
|
||||
CABLE_STATUS_CHOICES = (
|
||||
('true', 'connected'),
|
||||
('false', 'planned'),
|
||||
)
|
||||
|
||||
CABLE_LENGTH_UNIT_CHOICES = (
|
||||
(1200, 'm'),
|
||||
(1100, 'cm'),
|
||||
(2100, 'ft'),
|
||||
(2000, 'in'),
|
||||
)
|
||||
|
||||
POWERFEED_STATUS_CHOICES = (
|
||||
(0, 'offline'),
|
||||
(1, 'active'),
|
||||
(2, 'planned'),
|
||||
(4, 'failed'),
|
||||
)
|
||||
|
||||
POWERFEED_TYPE_CHOICES = (
|
||||
(1, 'primary'),
|
||||
(2, 'redundant'),
|
||||
)
|
||||
|
||||
POWERFEED_SUPPLY_CHOICES = (
|
||||
(1, 'ac'),
|
||||
(2, 'dc'),
|
||||
)
|
||||
|
||||
POWERFEED_PHASE_CHOICES = (
|
||||
(1, 'single-phase'),
|
||||
(3, 'three-phase'),
|
||||
)
|
||||
|
||||
POWEROUTLET_FEED_LEG_CHOICES_CHOICES = (
|
||||
(1, 'A'),
|
||||
(2, 'B'),
|
||||
(3, 'C'),
|
||||
)
|
||||
|
||||
|
||||
def cache_cable_devices(apps, schema_editor):
|
||||
Cable = apps.get_model('dcim', 'Cable')
|
||||
|
||||
if 'test' not in sys.argv:
|
||||
print("\nUpdating cable device terminations...")
|
||||
cable_count = Cable.objects.count()
|
||||
|
||||
# Cache A/B termination devices on all existing Cables. Note that the custom save() method on Cable is not
|
||||
# available during a migration, so we replicate its logic here.
|
||||
for i, cable in enumerate(Cable.objects.all(), start=1):
|
||||
|
||||
if not i % 1000 and 'test' not in sys.argv:
|
||||
print("[{}/{}]".format(i, cable_count))
|
||||
|
||||
termination_a_model = apps.get_model(cable.termination_a_type.app_label, cable.termination_a_type.model)
|
||||
termination_a_device = None
|
||||
if hasattr(termination_a_model, 'device'):
|
||||
termination_a = termination_a_model.objects.get(pk=cable.termination_a_id)
|
||||
termination_a_device = termination_a.device
|
||||
|
||||
termination_b_model = apps.get_model(cable.termination_b_type.app_label, cable.termination_b_type.model)
|
||||
termination_b_device = None
|
||||
if hasattr(termination_b_model, 'device'):
|
||||
termination_b = termination_b_model.objects.get(pk=cable.termination_b_id)
|
||||
termination_b_device = termination_b.device
|
||||
|
||||
Cable.objects.filter(pk=cable.pk).update(
|
||||
_termination_a_device=termination_a_device,
|
||||
_termination_b_device=termination_b_device
|
||||
)
|
||||
|
||||
|
||||
def site_status_to_slug(apps, schema_editor):
|
||||
Site = apps.get_model('dcim', 'Site')
|
||||
for id, slug in SITE_STATUS_CHOICES:
|
||||
Site.objects.filter(status=str(id)).update(status=slug)
|
||||
|
||||
|
||||
def rack_type_to_slug(apps, schema_editor):
|
||||
Rack = apps.get_model('dcim', 'Rack')
|
||||
for id, slug in RACK_TYPE_CHOICES:
|
||||
Rack.objects.filter(type=str(id)).update(type=slug)
|
||||
|
||||
|
||||
def rack_status_to_slug(apps, schema_editor):
|
||||
Rack = apps.get_model('dcim', 'Rack')
|
||||
for id, slug in RACK_STATUS_CHOICES:
|
||||
Rack.objects.filter(status=str(id)).update(status=slug)
|
||||
|
||||
|
||||
def rack_outer_unit_to_slug(apps, schema_editor):
|
||||
Rack = apps.get_model('dcim', 'Rack')
|
||||
for id, slug in RACK_DIMENSION_CHOICES:
|
||||
Rack.objects.filter(status=str(id)).update(status=slug)
|
||||
|
||||
|
||||
def devicetype_subdevicerole_to_slug(apps, schema_editor):
|
||||
DeviceType = apps.get_model('dcim', 'DeviceType')
|
||||
for boolean, slug in SUBDEVICE_ROLE_CHOICES:
|
||||
DeviceType.objects.filter(subdevice_role=boolean).update(subdevice_role=slug)
|
||||
|
||||
|
||||
def device_face_to_slug(apps, schema_editor):
|
||||
Device = apps.get_model('dcim', 'Device')
|
||||
for id, slug in DEVICE_FACE_CHOICES:
|
||||
Device.objects.filter(face=str(id)).update(face=slug)
|
||||
|
||||
|
||||
def device_status_to_slug(apps, schema_editor):
|
||||
Device = apps.get_model('dcim', 'Device')
|
||||
for id, slug in DEVICE_STATUS_CHOICES:
|
||||
Device.objects.filter(status=str(id)).update(status=slug)
|
||||
|
||||
|
||||
def interfacetemplate_type_to_slug(apps, schema_editor):
|
||||
InterfaceTemplate = apps.get_model('dcim', 'InterfaceTemplate')
|
||||
for id, slug in INTERFACE_TYPE_CHOICES:
|
||||
InterfaceTemplate.objects.filter(type=id).update(type=slug)
|
||||
|
||||
|
||||
def interface_type_to_slug(apps, schema_editor):
|
||||
Interface = apps.get_model('dcim', 'Interface')
|
||||
for id, slug in INTERFACE_TYPE_CHOICES:
|
||||
Interface.objects.filter(type=id).update(type=slug)
|
||||
|
||||
|
||||
def interface_mode_to_slug(apps, schema_editor):
|
||||
Interface = apps.get_model('dcim', 'Interface')
|
||||
for id, slug in INTERFACE_MODE_CHOICES:
|
||||
Interface.objects.filter(mode=id).update(mode=slug)
|
||||
|
||||
|
||||
def frontporttemplate_type_to_slug(apps, schema_editor):
|
||||
FrontPortTemplate = apps.get_model('dcim', 'FrontPortTemplate')
|
||||
for id, slug in PORT_TYPE_CHOICES:
|
||||
FrontPortTemplate.objects.filter(type=id).update(type=slug)
|
||||
|
||||
|
||||
def rearporttemplate_type_to_slug(apps, schema_editor):
|
||||
RearPortTemplate = apps.get_model('dcim', 'RearPortTemplate')
|
||||
for id, slug in PORT_TYPE_CHOICES:
|
||||
RearPortTemplate.objects.filter(type=id).update(type=slug)
|
||||
|
||||
|
||||
def frontport_type_to_slug(apps, schema_editor):
|
||||
FrontPort = apps.get_model('dcim', 'FrontPort')
|
||||
for id, slug in PORT_TYPE_CHOICES:
|
||||
FrontPort.objects.filter(type=id).update(type=slug)
|
||||
|
||||
|
||||
def rearport_type_to_slug(apps, schema_editor):
|
||||
RearPort = apps.get_model('dcim', 'RearPort')
|
||||
for id, slug in PORT_TYPE_CHOICES:
|
||||
RearPort.objects.filter(type=id).update(type=slug)
|
||||
|
||||
|
||||
def cable_type_to_slug(apps, schema_editor):
|
||||
Cable = apps.get_model('dcim', 'Cable')
|
||||
for id, slug in CABLE_TYPE_CHOICES:
|
||||
Cable.objects.filter(type=id).update(type=slug)
|
||||
|
||||
|
||||
def cable_status_to_slug(apps, schema_editor):
|
||||
Cable = apps.get_model('dcim', 'Cable')
|
||||
for bool_str, slug in CABLE_STATUS_CHOICES:
|
||||
Cable.objects.filter(status=bool_str).update(status=slug)
|
||||
|
||||
|
||||
def cable_length_unit_to_slug(apps, schema_editor):
|
||||
Cable = apps.get_model('dcim', 'Cable')
|
||||
for id, slug in CABLE_LENGTH_UNIT_CHOICES:
|
||||
Cable.objects.filter(length_unit=id).update(length_unit=slug)
|
||||
|
||||
|
||||
def powerfeed_status_to_slug(apps, schema_editor):
|
||||
PowerFeed = apps.get_model('dcim', 'PowerFeed')
|
||||
for id, slug in POWERFEED_STATUS_CHOICES:
|
||||
PowerFeed.objects.filter(status=id).update(status=slug)
|
||||
|
||||
|
||||
def powerfeed_type_to_slug(apps, schema_editor):
|
||||
PowerFeed = apps.get_model('dcim', 'PowerFeed')
|
||||
for id, slug in POWERFEED_TYPE_CHOICES:
|
||||
PowerFeed.objects.filter(type=id).update(type=slug)
|
||||
|
||||
|
||||
def powerfeed_supply_to_slug(apps, schema_editor):
|
||||
PowerFeed = apps.get_model('dcim', 'PowerFeed')
|
||||
for id, slug in POWERFEED_SUPPLY_CHOICES:
|
||||
PowerFeed.objects.filter(supply=id).update(supply=slug)
|
||||
|
||||
|
||||
def powerfeed_phase_to_slug(apps, schema_editor):
|
||||
PowerFeed = apps.get_model('dcim', 'PowerFeed')
|
||||
for id, slug in POWERFEED_PHASE_CHOICES:
|
||||
PowerFeed.objects.filter(phase=id).update(phase=slug)
|
||||
|
||||
|
||||
def poweroutlettemplate_feed_leg_to_slug(apps, schema_editor):
|
||||
PowerOutletTemplate = apps.get_model('dcim', 'PowerOutletTemplate')
|
||||
for id, slug in POWEROUTLET_FEED_LEG_CHOICES_CHOICES:
|
||||
PowerOutletTemplate.objects.filter(feed_leg=id).update(feed_leg=slug)
|
||||
|
||||
|
||||
def poweroutlet_feed_leg_to_slug(apps, schema_editor):
|
||||
PowerOutlet = apps.get_model('dcim', 'PowerOutlet')
|
||||
for id, slug in POWEROUTLET_FEED_LEG_CHOICES_CHOICES:
|
||||
PowerOutlet.objects.filter(feed_leg=id).update(feed_leg=slug)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
replaces = [('dcim', '0071_device_components_add_description'), ('dcim', '0072_powerfeeds'), ('dcim', '0073_interface_form_factor_to_type'), ('dcim', '0074_increase_field_length_platform_name_slug'), ('dcim', '0075_cable_devices'), ('dcim', '0076_console_port_types'), ('dcim', '0077_power_types'), ('dcim', '0078_3569_site_fields'), ('dcim', '0079_3569_rack_fields'), ('dcim', '0080_3569_devicetype_fields'), ('dcim', '0081_3569_device_fields'), ('dcim', '0082_3569_interface_fields'), ('dcim', '0082_3569_port_fields'), ('dcim', '0083_3569_cable_fields'), ('dcim', '0084_3569_powerfeed_fields'), ('dcim', '0085_3569_poweroutlet_fields'), ('dcim', '0086_device_name_nonunique'), ('dcim', '0087_role_descriptions'), ('dcim', '0088_powerfeed_available_power')]
|
||||
|
||||
dependencies = [
|
||||
('dcim', '0070_custom_tag_models'),
|
||||
('extras', '0021_add_color_comments_changelog_to_tag'),
|
||||
('tenancy', '0006_custom_tag_models'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='consoleport',
|
||||
name='description',
|
||||
field=models.CharField(blank=True, max_length=100),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='consoleserverport',
|
||||
name='description',
|
||||
field=models.CharField(blank=True, max_length=100),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='devicebay',
|
||||
name='description',
|
||||
field=models.CharField(blank=True, max_length=100),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='poweroutlet',
|
||||
name='description',
|
||||
field=models.CharField(blank=True, max_length=100),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='powerport',
|
||||
name='description',
|
||||
field=models.CharField(blank=True, max_length=100),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='PowerPanel',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False)),
|
||||
('created', models.DateField(auto_now_add=True, null=True)),
|
||||
('last_updated', models.DateTimeField(auto_now=True, null=True)),
|
||||
('name', models.CharField(max_length=50)),
|
||||
('rack_group', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='dcim.RackGroup')),
|
||||
('site', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='dcim.Site')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['site', 'name'],
|
||||
'unique_together': {('site', 'name')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='PowerFeed',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False)),
|
||||
('created', models.DateField(auto_now_add=True, null=True)),
|
||||
('last_updated', models.DateTimeField(auto_now=True, null=True)),
|
||||
('name', models.CharField(max_length=50)),
|
||||
('status', models.PositiveSmallIntegerField(default=1)),
|
||||
('type', models.PositiveSmallIntegerField(default=1)),
|
||||
('supply', models.PositiveSmallIntegerField(default=1)),
|
||||
('phase', models.PositiveSmallIntegerField(default=1)),
|
||||
('voltage', models.PositiveSmallIntegerField(default=120, validators=[django.core.validators.MinValueValidator(1)])),
|
||||
('amperage', models.PositiveSmallIntegerField(default=20, validators=[django.core.validators.MinValueValidator(1)])),
|
||||
('max_utilization', models.PositiveSmallIntegerField(default=80, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(100)])),
|
||||
('available_power', models.PositiveSmallIntegerField(default=0, editable=False)),
|
||||
('comments', models.TextField(blank=True)),
|
||||
('cable', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='dcim.Cable')),
|
||||
('power_panel', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='powerfeeds', to='dcim.PowerPanel')),
|
||||
('rack', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='dcim.Rack')),
|
||||
('tags', taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='extras.TaggedItem', to='extras.Tag', verbose_name='Tags')),
|
||||
('connected_endpoint', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='dcim.PowerPort')),
|
||||
('connection_status', models.NullBooleanField()),
|
||||
],
|
||||
options={
|
||||
'ordering': ['power_panel', 'name'],
|
||||
'unique_together': {('power_panel', 'name')},
|
||||
},
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='powerport',
|
||||
old_name='connected_endpoint',
|
||||
new_name='_connected_poweroutlet',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='powerport',
|
||||
name='_connected_powerfeed',
|
||||
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='dcim.PowerFeed'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='powerport',
|
||||
name='allocated_draw',
|
||||
field=models.PositiveSmallIntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(1)]),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='powerport',
|
||||
name='maximum_draw',
|
||||
field=models.PositiveSmallIntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(1)]),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='powerporttemplate',
|
||||
name='allocated_draw',
|
||||
field=models.PositiveSmallIntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(1)]),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='powerporttemplate',
|
||||
name='maximum_draw',
|
||||
field=models.PositiveSmallIntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(1)]),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='poweroutlet',
|
||||
name='feed_leg',
|
||||
field=models.PositiveSmallIntegerField(blank=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='poweroutlet',
|
||||
name='power_port',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='poweroutlets', to='dcim.PowerPort'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='poweroutlettemplate',
|
||||
name='feed_leg',
|
||||
field=models.PositiveSmallIntegerField(blank=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='poweroutlettemplate',
|
||||
name='power_port',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='poweroutlet_templates', to='dcim.PowerPortTemplate'),
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='interface',
|
||||
old_name='form_factor',
|
||||
new_name='type',
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='interfacetemplate',
|
||||
old_name='form_factor',
|
||||
new_name='type',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='platform',
|
||||
name='name',
|
||||
field=models.CharField(max_length=100, unique=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='platform',
|
||||
name='slug',
|
||||
field=models.SlugField(max_length=100, unique=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='cable',
|
||||
name='_termination_a_device',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='dcim.Device'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='cable',
|
||||
name='_termination_b_device',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='dcim.Device'),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=cache_cable_devices,
|
||||
reverse_code=django.db.migrations.operations.special.RunPython.noop,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='consoleport',
|
||||
name='type',
|
||||
field=models.CharField(blank=True, max_length=50),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='consoleporttemplate',
|
||||
name='type',
|
||||
field=models.CharField(blank=True, max_length=50),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='consoleserverport',
|
||||
name='type',
|
||||
field=models.CharField(blank=True, max_length=50),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='consoleserverporttemplate',
|
||||
name='type',
|
||||
field=models.CharField(blank=True, max_length=50),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='poweroutlet',
|
||||
name='type',
|
||||
field=models.CharField(blank=True, max_length=50),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='poweroutlettemplate',
|
||||
name='type',
|
||||
field=models.CharField(blank=True, max_length=50),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='powerport',
|
||||
name='type',
|
||||
field=models.CharField(blank=True, max_length=50),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='powerporttemplate',
|
||||
name='type',
|
||||
field=models.CharField(blank=True, max_length=50),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='site',
|
||||
name='status',
|
||||
field=models.CharField(default='active', max_length=50),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=site_status_to_slug,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='rack',
|
||||
name='type',
|
||||
field=models.CharField(blank=True, default='', max_length=50),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=rack_type_to_slug,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='rack',
|
||||
name='type',
|
||||
field=models.CharField(blank=True, max_length=50),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='rack',
|
||||
name='status',
|
||||
field=models.CharField(default='active', max_length=50),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=rack_status_to_slug,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='rack',
|
||||
name='outer_unit',
|
||||
field=models.CharField(blank=True, default='', max_length=50),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=rack_outer_unit_to_slug,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='rack',
|
||||
name='outer_unit',
|
||||
field=models.CharField(blank=True, max_length=50),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='devicetype',
|
||||
name='subdevice_role',
|
||||
field=models.CharField(blank=True, default='', max_length=50),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=devicetype_subdevicerole_to_slug,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='devicetype',
|
||||
name='subdevice_role',
|
||||
field=models.CharField(blank=True, max_length=50),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='device',
|
||||
name='face',
|
||||
field=models.CharField(blank=True, default='', max_length=50),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=device_face_to_slug,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='device',
|
||||
name='face',
|
||||
field=models.CharField(blank=True, max_length=50),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='device',
|
||||
name='status',
|
||||
field=models.CharField(default='active', max_length=50),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=device_status_to_slug,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='interfacetemplate',
|
||||
name='type',
|
||||
field=models.CharField(max_length=50),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=interfacetemplate_type_to_slug,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='interface',
|
||||
name='type',
|
||||
field=models.CharField(max_length=50),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=interface_type_to_slug,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='interface',
|
||||
name='mode',
|
||||
field=models.CharField(blank=True, default='', max_length=50),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=interface_mode_to_slug,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='interface',
|
||||
name='mode',
|
||||
field=models.CharField(blank=True, max_length=50),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='frontporttemplate',
|
||||
name='type',
|
||||
field=models.CharField(max_length=50),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=frontporttemplate_type_to_slug,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='rearporttemplate',
|
||||
name='type',
|
||||
field=models.CharField(max_length=50),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=rearporttemplate_type_to_slug,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='frontport',
|
||||
name='type',
|
||||
field=models.CharField(max_length=50),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=frontport_type_to_slug,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='rearport',
|
||||
name='type',
|
||||
field=models.CharField(max_length=50),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=rearport_type_to_slug,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='cable',
|
||||
name='type',
|
||||
field=models.CharField(blank=True, default='', max_length=50),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=cable_type_to_slug,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='cable',
|
||||
name='type',
|
||||
field=models.CharField(blank=True, max_length=50),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='cable',
|
||||
name='status',
|
||||
field=models.CharField(default='connected', max_length=50),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=cable_status_to_slug,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='cable',
|
||||
name='length_unit',
|
||||
field=models.CharField(blank=True, default='', max_length=50),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=cable_length_unit_to_slug,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='cable',
|
||||
name='length_unit',
|
||||
field=models.CharField(blank=True, max_length=50),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='powerfeed',
|
||||
name='status',
|
||||
field=models.CharField(default='active', max_length=50),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=powerfeed_status_to_slug,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='powerfeed',
|
||||
name='type',
|
||||
field=models.CharField(default='primary', max_length=50),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=powerfeed_type_to_slug,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='powerfeed',
|
||||
name='supply',
|
||||
field=models.CharField(default='ac', max_length=50),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=powerfeed_supply_to_slug,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='powerfeed',
|
||||
name='phase',
|
||||
field=models.CharField(default='single-phase', max_length=50),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=powerfeed_phase_to_slug,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='poweroutlettemplate',
|
||||
name='feed_leg',
|
||||
field=models.CharField(blank=True, default='', max_length=50),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=poweroutlettemplate_feed_leg_to_slug,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='poweroutlettemplate',
|
||||
name='feed_leg',
|
||||
field=models.CharField(blank=True, max_length=50),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='poweroutlet',
|
||||
name='feed_leg',
|
||||
field=models.CharField(blank=True, default='', max_length=50),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=poweroutlet_feed_leg_to_slug,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='poweroutlet',
|
||||
name='feed_leg',
|
||||
field=models.CharField(blank=True, max_length=50),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='device',
|
||||
name='name',
|
||||
field=models.CharField(blank=True, max_length=64, null=True),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='device',
|
||||
unique_together={('rack', 'position', 'face'), ('site', 'tenant', 'name'), ('virtual_chassis', 'vc_position')},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='devicerole',
|
||||
name='description',
|
||||
field=models.CharField(blank=True, max_length=100),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='rackrole',
|
||||
name='description',
|
||||
field=models.CharField(blank=True, max_length=100),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='powerfeed',
|
||||
name='available_power',
|
||||
field=models.PositiveIntegerField(default=0, editable=False),
|
||||
),
|
||||
]
|
||||
@@ -24,8 +24,8 @@ CABLE_TYPE_CHOICES = (
|
||||
)
|
||||
|
||||
CABLE_STATUS_CHOICES = (
|
||||
(True, 'connected'),
|
||||
(False, 'planned'),
|
||||
('true', 'connected'),
|
||||
('false', 'planned'),
|
||||
)
|
||||
|
||||
CABLE_LENGTH_UNIT_CHOICES = (
|
||||
@@ -44,8 +44,8 @@ def cable_type_to_slug(apps, schema_editor):
|
||||
|
||||
def cable_status_to_slug(apps, schema_editor):
|
||||
Cable = apps.get_model('dcim', 'Cable')
|
||||
for bool, slug in CABLE_STATUS_CHOICES:
|
||||
Cable.objects.filter(status=str(bool)).update(status=slug)
|
||||
for bool_str, slug in CABLE_STATUS_CHOICES:
|
||||
Cable.objects.filter(status=bool_str).update(status=slug)
|
||||
|
||||
|
||||
def cable_length_unit_to_slug(apps, schema_editor):
|
||||
|
||||
21
netbox/dcim/migrations/0089_deterministic_ordering.py
Normal file
21
netbox/dcim/migrations/0089_deterministic_ordering.py
Normal file
@@ -0,0 +1,21 @@
|
||||
# Generated by Django 2.2.8 on 2020-01-15 18:10
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('dcim', '0088_powerfeed_available_power'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='device',
|
||||
options={'ordering': ('name', 'pk'), 'permissions': (('napalm_read', 'Read-only access to devices via NAPALM'), ('napalm_write', 'Read/write access to devices via NAPALM'))},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='rack',
|
||||
options={'ordering': ('site', 'group', 'name', 'pk')},
|
||||
),
|
||||
]
|
||||
24
netbox/dcim/migrations/0090_cable_termination_models.py
Normal file
24
netbox/dcim/migrations/0090_cable_termination_models.py
Normal file
@@ -0,0 +1,24 @@
|
||||
# Generated by Django 2.2.8 on 2020-01-15 20:51
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('dcim', '0089_deterministic_ordering'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='cable',
|
||||
name='termination_a_type',
|
||||
field=models.ForeignKey(limit_choices_to=models.Q(models.Q(models.Q(('app_label', 'circuits'), ('model__in', ('circuittermination',))), models.Q(('app_label', 'dcim'), ('model__in', ('consoleport', 'consoleserverport', 'frontport', 'interface', 'powerfeed', 'poweroutlet', 'powerport', 'rearport'))), _connector='OR')), on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.ContentType'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='cable',
|
||||
name='termination_b_type',
|
||||
field=models.ForeignKey(limit_choices_to=models.Q(models.Q(models.Q(('app_label', 'circuits'), ('model__in', ('circuittermination',))), models.Q(('app_label', 'dcim'), ('model__in', ('consoleport', 'consoleserverport', 'frontport', 'interface', 'powerfeed', 'poweroutlet', 'powerport', 'rearport'))), _connector='OR')), on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.ContentType'),
|
||||
),
|
||||
]
|
||||
File diff suppressed because it is too large
Load Diff
400
netbox/dcim/models/device_component_templates.py
Normal file
400
netbox/dcim/models/device_component_templates.py
Normal file
@@ -0,0 +1,400 @@
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||
from django.db import models
|
||||
|
||||
from dcim.choices import *
|
||||
from dcim.constants import *
|
||||
from dcim.managers import InterfaceManager
|
||||
from extras.models import ObjectChange
|
||||
from utilities.managers import NaturalOrderingManager
|
||||
from utilities.utils import serialize_object
|
||||
from .device_components import (
|
||||
ConsolePort, ConsoleServerPort, DeviceBay, FrontPort, Interface, PowerOutlet, PowerPort, RearPort,
|
||||
)
|
||||
|
||||
|
||||
__all__ = (
|
||||
'ConsolePortTemplate',
|
||||
'ConsoleServerPortTemplate',
|
||||
'DeviceBayTemplate',
|
||||
'FrontPortTemplate',
|
||||
'InterfaceTemplate',
|
||||
'PowerOutletTemplate',
|
||||
'PowerPortTemplate',
|
||||
'RearPortTemplate',
|
||||
)
|
||||
|
||||
|
||||
class ComponentTemplateModel(models.Model):
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def instantiate(self, device):
|
||||
"""
|
||||
Instantiate a new component on the specified Device.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def to_objectchange(self, action):
|
||||
return ObjectChange(
|
||||
changed_object=self,
|
||||
object_repr=str(self),
|
||||
action=action,
|
||||
related_object=self.device_type,
|
||||
object_data=serialize_object(self)
|
||||
)
|
||||
|
||||
|
||||
class ConsolePortTemplate(ComponentTemplateModel):
|
||||
"""
|
||||
A template for a ConsolePort to be created for a new Device.
|
||||
"""
|
||||
device_type = models.ForeignKey(
|
||||
to='dcim.DeviceType',
|
||||
on_delete=models.CASCADE,
|
||||
related_name='consoleport_templates'
|
||||
)
|
||||
name = models.CharField(
|
||||
max_length=50
|
||||
)
|
||||
type = models.CharField(
|
||||
max_length=50,
|
||||
choices=ConsolePortTypeChoices,
|
||||
blank=True
|
||||
)
|
||||
|
||||
objects = NaturalOrderingManager()
|
||||
|
||||
class Meta:
|
||||
ordering = ['device_type', 'name']
|
||||
unique_together = ['device_type', 'name']
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def instantiate(self, device):
|
||||
return ConsolePort(
|
||||
device=device,
|
||||
name=self.name,
|
||||
type=self.type
|
||||
)
|
||||
|
||||
|
||||
class ConsoleServerPortTemplate(ComponentTemplateModel):
|
||||
"""
|
||||
A template for a ConsoleServerPort to be created for a new Device.
|
||||
"""
|
||||
device_type = models.ForeignKey(
|
||||
to='dcim.DeviceType',
|
||||
on_delete=models.CASCADE,
|
||||
related_name='consoleserverport_templates'
|
||||
)
|
||||
name = models.CharField(
|
||||
max_length=50
|
||||
)
|
||||
type = models.CharField(
|
||||
max_length=50,
|
||||
choices=ConsolePortTypeChoices,
|
||||
blank=True
|
||||
)
|
||||
|
||||
objects = NaturalOrderingManager()
|
||||
|
||||
class Meta:
|
||||
ordering = ['device_type', 'name']
|
||||
unique_together = ['device_type', 'name']
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def instantiate(self, device):
|
||||
return ConsoleServerPort(
|
||||
device=device,
|
||||
name=self.name,
|
||||
type=self.type
|
||||
)
|
||||
|
||||
|
||||
class PowerPortTemplate(ComponentTemplateModel):
|
||||
"""
|
||||
A template for a PowerPort to be created for a new Device.
|
||||
"""
|
||||
device_type = models.ForeignKey(
|
||||
to='dcim.DeviceType',
|
||||
on_delete=models.CASCADE,
|
||||
related_name='powerport_templates'
|
||||
)
|
||||
name = models.CharField(
|
||||
max_length=50
|
||||
)
|
||||
type = models.CharField(
|
||||
max_length=50,
|
||||
choices=PowerPortTypeChoices,
|
||||
blank=True
|
||||
)
|
||||
maximum_draw = models.PositiveSmallIntegerField(
|
||||
blank=True,
|
||||
null=True,
|
||||
validators=[MinValueValidator(1)],
|
||||
help_text="Maximum power draw (watts)"
|
||||
)
|
||||
allocated_draw = models.PositiveSmallIntegerField(
|
||||
blank=True,
|
||||
null=True,
|
||||
validators=[MinValueValidator(1)],
|
||||
help_text="Allocated power draw (watts)"
|
||||
)
|
||||
|
||||
objects = NaturalOrderingManager()
|
||||
|
||||
class Meta:
|
||||
ordering = ['device_type', 'name']
|
||||
unique_together = ['device_type', 'name']
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def instantiate(self, device):
|
||||
return PowerPort(
|
||||
device=device,
|
||||
name=self.name,
|
||||
maximum_draw=self.maximum_draw,
|
||||
allocated_draw=self.allocated_draw
|
||||
)
|
||||
|
||||
|
||||
class PowerOutletTemplate(ComponentTemplateModel):
|
||||
"""
|
||||
A template for a PowerOutlet to be created for a new Device.
|
||||
"""
|
||||
device_type = models.ForeignKey(
|
||||
to='dcim.DeviceType',
|
||||
on_delete=models.CASCADE,
|
||||
related_name='poweroutlet_templates'
|
||||
)
|
||||
name = models.CharField(
|
||||
max_length=50
|
||||
)
|
||||
type = models.CharField(
|
||||
max_length=50,
|
||||
choices=PowerOutletTypeChoices,
|
||||
blank=True
|
||||
)
|
||||
power_port = models.ForeignKey(
|
||||
to='dcim.PowerPortTemplate',
|
||||
on_delete=models.SET_NULL,
|
||||
blank=True,
|
||||
null=True,
|
||||
related_name='poweroutlet_templates'
|
||||
)
|
||||
feed_leg = models.CharField(
|
||||
max_length=50,
|
||||
choices=PowerOutletFeedLegChoices,
|
||||
blank=True,
|
||||
help_text="Phase (for three-phase feeds)"
|
||||
)
|
||||
|
||||
objects = NaturalOrderingManager()
|
||||
|
||||
class Meta:
|
||||
ordering = ['device_type', 'name']
|
||||
unique_together = ['device_type', 'name']
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def clean(self):
|
||||
|
||||
# Validate power port assignment
|
||||
if self.power_port and self.power_port.device_type != self.device_type:
|
||||
raise ValidationError(
|
||||
"Parent power port ({}) must belong to the same device type".format(self.power_port)
|
||||
)
|
||||
|
||||
def instantiate(self, device):
|
||||
if self.power_port:
|
||||
power_port = PowerPort.objects.get(device=device, name=self.power_port.name)
|
||||
else:
|
||||
power_port = None
|
||||
return PowerOutlet(
|
||||
device=device,
|
||||
name=self.name,
|
||||
power_port=power_port,
|
||||
feed_leg=self.feed_leg
|
||||
)
|
||||
|
||||
|
||||
class InterfaceTemplate(ComponentTemplateModel):
|
||||
"""
|
||||
A template for a physical data interface on a new Device.
|
||||
"""
|
||||
device_type = models.ForeignKey(
|
||||
to='dcim.DeviceType',
|
||||
on_delete=models.CASCADE,
|
||||
related_name='interface_templates'
|
||||
)
|
||||
name = models.CharField(
|
||||
max_length=64
|
||||
)
|
||||
type = models.CharField(
|
||||
max_length=50,
|
||||
choices=InterfaceTypeChoices
|
||||
)
|
||||
mgmt_only = models.BooleanField(
|
||||
default=False,
|
||||
verbose_name='Management only'
|
||||
)
|
||||
|
||||
objects = InterfaceManager()
|
||||
|
||||
class Meta:
|
||||
ordering = ['device_type', 'name']
|
||||
unique_together = ['device_type', 'name']
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def instantiate(self, device):
|
||||
return Interface(
|
||||
device=device,
|
||||
name=self.name,
|
||||
type=self.type,
|
||||
mgmt_only=self.mgmt_only
|
||||
)
|
||||
|
||||
|
||||
class FrontPortTemplate(ComponentTemplateModel):
|
||||
"""
|
||||
Template for a pass-through port on the front of a new Device.
|
||||
"""
|
||||
device_type = models.ForeignKey(
|
||||
to='dcim.DeviceType',
|
||||
on_delete=models.CASCADE,
|
||||
related_name='frontport_templates'
|
||||
)
|
||||
name = models.CharField(
|
||||
max_length=64
|
||||
)
|
||||
type = models.CharField(
|
||||
max_length=50,
|
||||
choices=PortTypeChoices
|
||||
)
|
||||
rear_port = models.ForeignKey(
|
||||
to='dcim.RearPortTemplate',
|
||||
on_delete=models.CASCADE,
|
||||
related_name='frontport_templates'
|
||||
)
|
||||
rear_port_position = models.PositiveSmallIntegerField(
|
||||
default=1,
|
||||
validators=[MinValueValidator(1), MaxValueValidator(64)]
|
||||
)
|
||||
|
||||
objects = NaturalOrderingManager()
|
||||
|
||||
class Meta:
|
||||
ordering = ['device_type', 'name']
|
||||
unique_together = [
|
||||
['device_type', 'name'],
|
||||
['rear_port', 'rear_port_position'],
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def clean(self):
|
||||
|
||||
# Validate rear port assignment
|
||||
if self.rear_port.device_type != self.device_type:
|
||||
raise ValidationError(
|
||||
"Rear port ({}) must belong to the same device type".format(self.rear_port)
|
||||
)
|
||||
|
||||
# Validate rear port position assignment
|
||||
if self.rear_port_position > self.rear_port.positions:
|
||||
raise ValidationError(
|
||||
"Invalid rear port position ({}); rear port {} has only {} positions".format(
|
||||
self.rear_port_position, self.rear_port.name, self.rear_port.positions
|
||||
)
|
||||
)
|
||||
|
||||
def instantiate(self, device):
|
||||
if self.rear_port:
|
||||
rear_port = RearPort.objects.get(device=device, name=self.rear_port.name)
|
||||
else:
|
||||
rear_port = None
|
||||
return FrontPort(
|
||||
device=device,
|
||||
name=self.name,
|
||||
type=self.type,
|
||||
rear_port=rear_port,
|
||||
rear_port_position=self.rear_port_position
|
||||
)
|
||||
|
||||
|
||||
class RearPortTemplate(ComponentTemplateModel):
|
||||
"""
|
||||
Template for a pass-through port on the rear of a new Device.
|
||||
"""
|
||||
device_type = models.ForeignKey(
|
||||
to='dcim.DeviceType',
|
||||
on_delete=models.CASCADE,
|
||||
related_name='rearport_templates'
|
||||
)
|
||||
name = models.CharField(
|
||||
max_length=64
|
||||
)
|
||||
type = models.CharField(
|
||||
max_length=50,
|
||||
choices=PortTypeChoices
|
||||
)
|
||||
positions = models.PositiveSmallIntegerField(
|
||||
default=1,
|
||||
validators=[MinValueValidator(1), MaxValueValidator(64)]
|
||||
)
|
||||
|
||||
objects = NaturalOrderingManager()
|
||||
|
||||
class Meta:
|
||||
ordering = ['device_type', 'name']
|
||||
unique_together = ['device_type', 'name']
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def instantiate(self, device):
|
||||
return RearPort(
|
||||
device=device,
|
||||
name=self.name,
|
||||
type=self.type,
|
||||
positions=self.positions
|
||||
)
|
||||
|
||||
|
||||
class DeviceBayTemplate(ComponentTemplateModel):
|
||||
"""
|
||||
A template for a DeviceBay to be created for a new parent Device.
|
||||
"""
|
||||
device_type = models.ForeignKey(
|
||||
to='dcim.DeviceType',
|
||||
on_delete=models.CASCADE,
|
||||
related_name='device_bay_templates'
|
||||
)
|
||||
name = models.CharField(
|
||||
max_length=50
|
||||
)
|
||||
|
||||
objects = NaturalOrderingManager()
|
||||
|
||||
class Meta:
|
||||
ordering = ['device_type', 'name']
|
||||
unique_together = ['device_type', 'name']
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def instantiate(self, device):
|
||||
return DeviceBay(
|
||||
device=device,
|
||||
name=self.name
|
||||
)
|
||||
1019
netbox/dcim/models/device_components.py
Normal file
1019
netbox/dcim/models/device_components.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -14,10 +14,93 @@ from dcim.models import (
|
||||
)
|
||||
from ipam.models import IPAddress, VLAN
|
||||
from extras.models import Graph
|
||||
from utilities.testing import APITestCase
|
||||
from utilities.testing import APITestCase, choices_to_dict
|
||||
from virtualization.models import Cluster, ClusterType
|
||||
|
||||
|
||||
class AppTest(APITestCase):
|
||||
|
||||
def test_root(self):
|
||||
|
||||
url = reverse('dcim-api:api-root')
|
||||
response = self.client.get('{}?format=api'.format(url), **self.header)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_choices(self):
|
||||
|
||||
url = reverse('dcim-api:field-choice-list')
|
||||
response = self.client.get(url, **self.header)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
# Cable
|
||||
self.assertEqual(choices_to_dict(response.data.get('cable:length_unit')), CableLengthUnitChoices.as_dict())
|
||||
self.assertEqual(choices_to_dict(response.data.get('cable:status')), CableStatusChoices.as_dict())
|
||||
content_types = ContentType.objects.filter(CABLE_TERMINATION_MODELS)
|
||||
cable_termination_choices = {
|
||||
"{}.{}".format(ct.app_label, ct.model): ct.name for ct in content_types
|
||||
}
|
||||
self.assertEqual(choices_to_dict(response.data.get('cable:termination_a_type')), cable_termination_choices)
|
||||
self.assertEqual(choices_to_dict(response.data.get('cable:termination_b_type')), cable_termination_choices)
|
||||
self.assertEqual(choices_to_dict(response.data.get('cable:type')), CableTypeChoices.as_dict())
|
||||
|
||||
# Console ports
|
||||
self.assertEqual(choices_to_dict(response.data.get('console-port:type')), ConsolePortTypeChoices.as_dict())
|
||||
self.assertEqual(choices_to_dict(response.data.get('console-port:connection_status')), dict(CONNECTION_STATUS_CHOICES))
|
||||
self.assertEqual(choices_to_dict(response.data.get('console-port-template:type')), ConsolePortTypeChoices.as_dict())
|
||||
|
||||
# Console server ports
|
||||
self.assertEqual(choices_to_dict(response.data.get('console-server-port:type')), ConsolePortTypeChoices.as_dict())
|
||||
self.assertEqual(choices_to_dict(response.data.get('console-server-port-template:type')), ConsolePortTypeChoices.as_dict())
|
||||
|
||||
# Device
|
||||
self.assertEqual(choices_to_dict(response.data.get('device:face')), DeviceFaceChoices.as_dict())
|
||||
self.assertEqual(choices_to_dict(response.data.get('device:status')), DeviceStatusChoices.as_dict())
|
||||
|
||||
# Device type
|
||||
self.assertEqual(choices_to_dict(response.data.get('device-type:subdevice_role')), SubdeviceRoleChoices.as_dict())
|
||||
|
||||
# Front ports
|
||||
self.assertEqual(choices_to_dict(response.data.get('front-port:type')), PortTypeChoices.as_dict())
|
||||
self.assertEqual(choices_to_dict(response.data.get('front-port-template:type')), PortTypeChoices.as_dict())
|
||||
|
||||
# Interfaces
|
||||
self.assertEqual(choices_to_dict(response.data.get('interface:type')), InterfaceTypeChoices.as_dict())
|
||||
self.assertEqual(choices_to_dict(response.data.get('interface:mode')), InterfaceModeChoices.as_dict())
|
||||
self.assertEqual(choices_to_dict(response.data.get('interface-template:type')), InterfaceTypeChoices.as_dict())
|
||||
|
||||
# Power feed
|
||||
self.assertEqual(choices_to_dict(response.data.get('power-feed:phase')), PowerFeedPhaseChoices.as_dict())
|
||||
self.assertEqual(choices_to_dict(response.data.get('power-feed:status')), PowerFeedStatusChoices.as_dict())
|
||||
self.assertEqual(choices_to_dict(response.data.get('power-feed:supply')), PowerFeedSupplyChoices.as_dict())
|
||||
self.assertEqual(choices_to_dict(response.data.get('power-feed:type')), PowerFeedTypeChoices.as_dict())
|
||||
|
||||
# Power outlets
|
||||
self.assertEqual(choices_to_dict(response.data.get('power-outlet:type')), PowerOutletTypeChoices.as_dict())
|
||||
self.assertEqual(choices_to_dict(response.data.get('power-outlet:feed_leg')), PowerOutletFeedLegChoices.as_dict())
|
||||
self.assertEqual(choices_to_dict(response.data.get('power-outlet-template:type')), PowerOutletTypeChoices.as_dict())
|
||||
self.assertEqual(choices_to_dict(response.data.get('power-outlet-template:feed_leg')), PowerOutletFeedLegChoices.as_dict())
|
||||
|
||||
# Power ports
|
||||
self.assertEqual(choices_to_dict(response.data.get('power-port:type')), PowerPortTypeChoices.as_dict())
|
||||
self.assertEqual(choices_to_dict(response.data.get('power-port:connection_status')), dict(CONNECTION_STATUS_CHOICES))
|
||||
self.assertEqual(choices_to_dict(response.data.get('power-port-template:type')), PowerPortTypeChoices.as_dict())
|
||||
|
||||
# Rack
|
||||
self.assertEqual(choices_to_dict(response.data.get('rack:type')), RackTypeChoices.as_dict())
|
||||
self.assertEqual(choices_to_dict(response.data.get('rack:width')), RackWidthChoices.as_dict())
|
||||
self.assertEqual(choices_to_dict(response.data.get('rack:status')), RackStatusChoices.as_dict())
|
||||
self.assertEqual(choices_to_dict(response.data.get('rack:outer_unit')), RackDimensionUnitChoices.as_dict())
|
||||
|
||||
# Rear ports
|
||||
self.assertEqual(choices_to_dict(response.data.get('rear-port:type')), PortTypeChoices.as_dict())
|
||||
self.assertEqual(choices_to_dict(response.data.get('rear-port-template:type')), PortTypeChoices.as_dict())
|
||||
|
||||
# Site
|
||||
self.assertEqual(choices_to_dict(response.data.get('site:status')), SiteStatusChoices.as_dict())
|
||||
|
||||
|
||||
class RegionTest(APITestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
2403
netbox/dcim/tests/test_filters.py
Normal file
2403
netbox/dcim/tests/test_filters.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,8 @@
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.test import TestCase
|
||||
|
||||
from dcim.choices import *
|
||||
from dcim.constants import CONNECTION_STATUS_CONNECTED, CONNECTION_STATUS_PLANNED
|
||||
from dcim.models import *
|
||||
from tenancy.models import Tenant
|
||||
|
||||
@@ -362,9 +365,12 @@ class CableTestCase(TestCase):
|
||||
|
||||
def test_cable_deletion(self):
|
||||
"""
|
||||
When a Cable is deleted, the `cable` field on its termination points must be nullified.
|
||||
When a Cable is deleted, the `cable` field on its termination points must be nullified. The str() method
|
||||
should still return the PK of the string even after being nullified.
|
||||
"""
|
||||
self.cable.delete()
|
||||
self.assertIsNone(self.cable.pk)
|
||||
self.assertNotEqual(str(self.cable), '#None')
|
||||
interface1 = Interface.objects.get(pk=self.interface1.pk)
|
||||
self.assertIsNone(interface1.cable)
|
||||
interface2 = Interface.objects.get(pk=self.interface2.pk)
|
||||
@@ -498,7 +504,7 @@ class CablePathTestCase(TestCase):
|
||||
self.assertEqual(interface1.connection_status, CONNECTION_STATUS_PLANNED)
|
||||
|
||||
# Switch third segment from planned to connected
|
||||
cable3.status = CONNECTION_STATUS_CONNECTED
|
||||
cable3.status = CableStatusChoices.STATUS_CONNECTED
|
||||
cable3.save()
|
||||
interface1 = Interface.objects.get(pk=self.interface1.pk)
|
||||
self.assertEqual(interface1.connected_endpoint, self.interface2)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import urllib.parse
|
||||
|
||||
import yaml
|
||||
from django.test import Client, TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
@@ -327,6 +328,17 @@ class DeviceTypeTestCase(TestCase):
|
||||
response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params)))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_devicetype_export(self):
|
||||
|
||||
url = reverse('dcim:devicetype_list')
|
||||
|
||||
response = self.client.get('{}?export'.format(url))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
data = list(yaml.load_all(response.content, Loader=yaml.SafeLoader))
|
||||
self.assertEqual(len(data), 3)
|
||||
self.assertEqual(data[0]['manufacturer'], 'Manufacturer 1')
|
||||
self.assertEqual(data[0]['model'], 'Device Type 1')
|
||||
|
||||
def test_devicetype(self):
|
||||
|
||||
devicetype = DeviceType.objects.first()
|
||||
|
||||
@@ -148,8 +148,8 @@ class RegionListView(PermissionRequiredMixin, ObjectListView):
|
||||
'site_count',
|
||||
cumulative=True
|
||||
)
|
||||
filter = filters.RegionFilter
|
||||
filter_form = forms.RegionFilterForm
|
||||
filterset = filters.RegionFilterSet
|
||||
filterset_form = forms.RegionFilterForm
|
||||
table = tables.RegionTable
|
||||
template_name = 'dcim/region_list.html'
|
||||
|
||||
@@ -175,7 +175,7 @@ class RegionBulkImportView(PermissionRequiredMixin, BulkImportView):
|
||||
class RegionBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
permission_required = 'dcim.delete_region'
|
||||
queryset = Region.objects.all()
|
||||
filter = filters.RegionFilter
|
||||
filterset = filters.RegionFilterSet
|
||||
table = tables.RegionTable
|
||||
default_return_url = 'dcim:region_list'
|
||||
|
||||
@@ -187,8 +187,8 @@ class RegionBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
class SiteListView(PermissionRequiredMixin, ObjectListView):
|
||||
permission_required = 'dcim.view_site'
|
||||
queryset = Site.objects.prefetch_related('region', 'tenant')
|
||||
filter = filters.SiteFilter
|
||||
filter_form = forms.SiteFilterForm
|
||||
filterset = filters.SiteFilterSet
|
||||
filterset_form = forms.SiteFilterForm
|
||||
table = tables.SiteTable
|
||||
template_name = 'dcim/site_list.html'
|
||||
|
||||
@@ -246,7 +246,7 @@ class SiteBulkImportView(PermissionRequiredMixin, BulkImportView):
|
||||
class SiteBulkEditView(PermissionRequiredMixin, BulkEditView):
|
||||
permission_required = 'dcim.change_site'
|
||||
queryset = Site.objects.prefetch_related('region', 'tenant')
|
||||
filter = filters.SiteFilter
|
||||
filterset = filters.SiteFilterSet
|
||||
table = tables.SiteTable
|
||||
form = forms.SiteBulkEditForm
|
||||
default_return_url = 'dcim:site_list'
|
||||
@@ -255,7 +255,7 @@ class SiteBulkEditView(PermissionRequiredMixin, BulkEditView):
|
||||
class SiteBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
permission_required = 'dcim.delete_site'
|
||||
queryset = Site.objects.prefetch_related('region', 'tenant')
|
||||
filter = filters.SiteFilter
|
||||
filterset = filters.SiteFilterSet
|
||||
table = tables.SiteTable
|
||||
default_return_url = 'dcim:site_list'
|
||||
|
||||
@@ -267,8 +267,8 @@ class SiteBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
class RackGroupListView(PermissionRequiredMixin, ObjectListView):
|
||||
permission_required = 'dcim.view_rackgroup'
|
||||
queryset = RackGroup.objects.prefetch_related('site').annotate(rack_count=Count('racks'))
|
||||
filter = filters.RackGroupFilter
|
||||
filter_form = forms.RackGroupFilterForm
|
||||
filterset = filters.RackGroupFilterSet
|
||||
filterset_form = forms.RackGroupFilterForm
|
||||
table = tables.RackGroupTable
|
||||
template_name = 'dcim/rackgroup_list.html'
|
||||
|
||||
@@ -294,7 +294,7 @@ class RackGroupBulkImportView(PermissionRequiredMixin, BulkImportView):
|
||||
class RackGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
permission_required = 'dcim.delete_rackgroup'
|
||||
queryset = RackGroup.objects.prefetch_related('site').annotate(rack_count=Count('racks'))
|
||||
filter = filters.RackGroupFilter
|
||||
filterset = filters.RackGroupFilterSet
|
||||
table = tables.RackGroupTable
|
||||
default_return_url = 'dcim:rackgroup_list'
|
||||
|
||||
@@ -346,8 +346,8 @@ class RackListView(PermissionRequiredMixin, ObjectListView):
|
||||
).annotate(
|
||||
device_count=Count('devices')
|
||||
)
|
||||
filter = filters.RackFilter
|
||||
filter_form = forms.RackFilterForm
|
||||
filterset = filters.RackFilterSet
|
||||
filterset_form = forms.RackFilterForm
|
||||
table = tables.RackDetailTable
|
||||
template_name = 'dcim/rack_list.html'
|
||||
|
||||
@@ -361,7 +361,7 @@ class RackElevationListView(PermissionRequiredMixin, View):
|
||||
def get(self, request):
|
||||
|
||||
racks = Rack.objects.prefetch_related('site', 'group', 'tenant', 'role', 'devices__device_type')
|
||||
racks = filters.RackFilter(request.GET, racks).qs
|
||||
racks = filters.RackFilterSet(request.GET, racks).qs
|
||||
total_count = racks.count()
|
||||
|
||||
# Pagination
|
||||
@@ -386,7 +386,7 @@ class RackElevationListView(PermissionRequiredMixin, View):
|
||||
'page': page,
|
||||
'total_count': total_count,
|
||||
'face_id': face_id,
|
||||
'filter_form': forms.RackFilterForm(request.GET),
|
||||
'filter_form': forms.RackElevationFilterForm(request.GET),
|
||||
})
|
||||
|
||||
|
||||
@@ -450,7 +450,7 @@ class RackBulkImportView(PermissionRequiredMixin, BulkImportView):
|
||||
class RackBulkEditView(PermissionRequiredMixin, BulkEditView):
|
||||
permission_required = 'dcim.change_rack'
|
||||
queryset = Rack.objects.prefetch_related('site', 'group', 'tenant', 'role')
|
||||
filter = filters.RackFilter
|
||||
filterset = filters.RackFilterSet
|
||||
table = tables.RackTable
|
||||
form = forms.RackBulkEditForm
|
||||
default_return_url = 'dcim:rack_list'
|
||||
@@ -459,7 +459,7 @@ class RackBulkEditView(PermissionRequiredMixin, BulkEditView):
|
||||
class RackBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
permission_required = 'dcim.delete_rack'
|
||||
queryset = Rack.objects.prefetch_related('site', 'group', 'tenant', 'role')
|
||||
filter = filters.RackFilter
|
||||
filterset = filters.RackFilterSet
|
||||
table = tables.RackTable
|
||||
default_return_url = 'dcim:rack_list'
|
||||
|
||||
@@ -471,8 +471,8 @@ class RackBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
class RackReservationListView(PermissionRequiredMixin, ObjectListView):
|
||||
permission_required = 'dcim.view_rackreservation'
|
||||
queryset = RackReservation.objects.prefetch_related('rack__site')
|
||||
filter = filters.RackReservationFilter
|
||||
filter_form = forms.RackReservationFilterForm
|
||||
filterset = filters.RackReservationFilterSet
|
||||
filterset_form = forms.RackReservationFilterForm
|
||||
table = tables.RackReservationTable
|
||||
template_name = 'dcim/rackreservation_list.html'
|
||||
|
||||
@@ -507,7 +507,7 @@ class RackReservationDeleteView(PermissionRequiredMixin, ObjectDeleteView):
|
||||
class RackReservationBulkEditView(PermissionRequiredMixin, BulkEditView):
|
||||
permission_required = 'dcim.change_rackreservation'
|
||||
queryset = RackReservation.objects.prefetch_related('rack', 'user')
|
||||
filter = filters.RackReservationFilter
|
||||
filterset = filters.RackReservationFilterSet
|
||||
table = tables.RackReservationTable
|
||||
form = forms.RackReservationBulkEditForm
|
||||
default_return_url = 'dcim:rackreservation_list'
|
||||
@@ -516,7 +516,7 @@ class RackReservationBulkEditView(PermissionRequiredMixin, BulkEditView):
|
||||
class RackReservationBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
permission_required = 'dcim.delete_rackreservation'
|
||||
queryset = RackReservation.objects.prefetch_related('rack', 'user')
|
||||
filter = filters.RackReservationFilter
|
||||
filterset = filters.RackReservationFilterSet
|
||||
table = tables.RackReservationTable
|
||||
default_return_url = 'dcim:rackreservation_list'
|
||||
|
||||
@@ -568,8 +568,8 @@ class ManufacturerBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
class DeviceTypeListView(PermissionRequiredMixin, ObjectListView):
|
||||
permission_required = 'dcim.view_devicetype'
|
||||
queryset = DeviceType.objects.prefetch_related('manufacturer').annotate(instance_count=Count('instances'))
|
||||
filter = filters.DeviceTypeFilter
|
||||
filter_form = forms.DeviceTypeFilterForm
|
||||
filterset = filters.DeviceTypeFilterSet
|
||||
filterset_form = forms.DeviceTypeFilterForm
|
||||
table = tables.DeviceTypeTable
|
||||
template_name = 'dcim/devicetype_list.html'
|
||||
|
||||
@@ -685,7 +685,7 @@ class DeviceTypeImportView(PermissionRequiredMixin, ObjectImportView):
|
||||
class DeviceTypeBulkEditView(PermissionRequiredMixin, BulkEditView):
|
||||
permission_required = 'dcim.change_devicetype'
|
||||
queryset = DeviceType.objects.prefetch_related('manufacturer').annotate(instance_count=Count('instances'))
|
||||
filter = filters.DeviceTypeFilter
|
||||
filterset = filters.DeviceTypeFilterSet
|
||||
table = tables.DeviceTypeTable
|
||||
form = forms.DeviceTypeBulkEditForm
|
||||
default_return_url = 'dcim:devicetype_list'
|
||||
@@ -694,7 +694,7 @@ class DeviceTypeBulkEditView(PermissionRequiredMixin, BulkEditView):
|
||||
class DeviceTypeBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
permission_required = 'dcim.delete_devicetype'
|
||||
queryset = DeviceType.objects.prefetch_related('manufacturer').annotate(instance_count=Count('instances'))
|
||||
filter = filters.DeviceTypeFilter
|
||||
filterset = filters.DeviceTypeFilterSet
|
||||
table = tables.DeviceTypeTable
|
||||
default_return_url = 'dcim:devicetype_list'
|
||||
|
||||
@@ -976,8 +976,8 @@ class DeviceListView(PermissionRequiredMixin, ObjectListView):
|
||||
queryset = Device.objects.prefetch_related(
|
||||
'device_type__manufacturer', 'device_role', 'tenant', 'site', 'rack', 'primary_ip4', 'primary_ip6'
|
||||
)
|
||||
filter = filters.DeviceFilter
|
||||
filter_form = forms.DeviceFilterForm
|
||||
filterset = filters.DeviceFilterSet
|
||||
filterset_form = forms.DeviceFilterForm
|
||||
table = tables.DeviceDetailTable
|
||||
template_name = 'dcim/device_list.html'
|
||||
|
||||
@@ -1176,7 +1176,7 @@ class ChildDeviceBulkImportView(PermissionRequiredMixin, BulkImportView):
|
||||
class DeviceBulkEditView(PermissionRequiredMixin, BulkEditView):
|
||||
permission_required = 'dcim.change_device'
|
||||
queryset = Device.objects.prefetch_related('tenant', 'site', 'rack', 'device_role', 'device_type__manufacturer')
|
||||
filter = filters.DeviceFilter
|
||||
filterset = filters.DeviceFilterSet
|
||||
table = tables.DeviceTable
|
||||
form = forms.DeviceBulkEditForm
|
||||
default_return_url = 'dcim:device_list'
|
||||
@@ -1185,7 +1185,7 @@ class DeviceBulkEditView(PermissionRequiredMixin, BulkEditView):
|
||||
class DeviceBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
permission_required = 'dcim.delete_device'
|
||||
queryset = Device.objects.prefetch_related('tenant', 'site', 'rack', 'device_role', 'device_type__manufacturer')
|
||||
filter = filters.DeviceFilter
|
||||
filterset = filters.DeviceFilterSet
|
||||
table = tables.DeviceTable
|
||||
default_return_url = 'dcim:device_list'
|
||||
|
||||
@@ -1197,8 +1197,8 @@ class DeviceBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
class ConsolePortListView(PermissionRequiredMixin, ObjectListView):
|
||||
permission_required = 'dcim.view_consoleport'
|
||||
queryset = ConsolePort.objects.prefetch_related('device', 'device__tenant', 'device__site', 'cable')
|
||||
filter = filters.ConsolePortFilter
|
||||
filter_form = forms.ConsolePortFilterForm
|
||||
filterset = filters.ConsolePortFilterSet
|
||||
filterset_form = forms.ConsolePortFilterForm
|
||||
table = tables.ConsolePortDetailTable
|
||||
template_name = 'dcim/device_component_list.html'
|
||||
|
||||
@@ -1245,8 +1245,8 @@ class ConsolePortBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
class ConsoleServerPortListView(PermissionRequiredMixin, ObjectListView):
|
||||
permission_required = 'dcim.view_consoleserverport'
|
||||
queryset = ConsoleServerPort.objects.prefetch_related('device', 'device__tenant', 'device__site', 'cable')
|
||||
filter = filters.ConsoleServerPortFilter
|
||||
filter_form = forms.ConsoleServerPortFilterForm
|
||||
filterset = filters.ConsoleServerPortFilterSet
|
||||
filterset_form = forms.ConsoleServerPortFilterForm
|
||||
table = tables.ConsoleServerPortDetailTable
|
||||
template_name = 'dcim/device_component_list.html'
|
||||
|
||||
@@ -1313,8 +1313,8 @@ class ConsoleServerPortBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
class PowerPortListView(PermissionRequiredMixin, ObjectListView):
|
||||
permission_required = 'dcim.view_powerport'
|
||||
queryset = PowerPort.objects.prefetch_related('device', 'device__tenant', 'device__site', 'cable')
|
||||
filter = filters.PowerPortFilter
|
||||
filter_form = forms.PowerPortFilterForm
|
||||
filterset = filters.PowerPortFilterSet
|
||||
filterset_form = forms.PowerPortFilterForm
|
||||
table = tables.PowerPortDetailTable
|
||||
template_name = 'dcim/device_component_list.html'
|
||||
|
||||
@@ -1361,8 +1361,8 @@ class PowerPortBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
class PowerOutletListView(PermissionRequiredMixin, ObjectListView):
|
||||
permission_required = 'dcim.view_poweroutlet'
|
||||
queryset = PowerOutlet.objects.prefetch_related('device', 'device__tenant', 'device__site', 'cable')
|
||||
filter = filters.PowerOutletFilter
|
||||
filter_form = forms.PowerOutletFilterForm
|
||||
filterset = filters.PowerOutletFilterSet
|
||||
filterset_form = forms.PowerOutletFilterForm
|
||||
table = tables.PowerOutletDetailTable
|
||||
template_name = 'dcim/device_component_list.html'
|
||||
|
||||
@@ -1429,8 +1429,8 @@ class PowerOutletBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
class InterfaceListView(PermissionRequiredMixin, ObjectListView):
|
||||
permission_required = 'dcim.view_interface'
|
||||
queryset = Interface.objects.prefetch_related('device', 'device__tenant', 'device__site', 'cable')
|
||||
filter = filters.InterfaceFilter
|
||||
filter_form = forms.InterfaceFilterForm
|
||||
filterset = filters.InterfaceFilterSet
|
||||
filterset_form = forms.InterfaceFilterForm
|
||||
table = tables.InterfaceDetailTable
|
||||
template_name = 'dcim/device_component_list.html'
|
||||
|
||||
@@ -1534,8 +1534,8 @@ class InterfaceBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
class FrontPortListView(PermissionRequiredMixin, ObjectListView):
|
||||
permission_required = 'dcim.view_frontport'
|
||||
queryset = FrontPort.objects.prefetch_related('device', 'device__tenant', 'device__site', 'cable')
|
||||
filter = filters.FrontPortFilter
|
||||
filter_form = forms.FrontPortFilterForm
|
||||
filterset = filters.FrontPortFilterSet
|
||||
filterset_form = forms.FrontPortFilterForm
|
||||
table = tables.FrontPortDetailTable
|
||||
template_name = 'dcim/device_component_list.html'
|
||||
|
||||
@@ -1602,8 +1602,8 @@ class FrontPortBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
class RearPortListView(PermissionRequiredMixin, ObjectListView):
|
||||
permission_required = 'dcim.view_rearport'
|
||||
queryset = RearPort.objects.prefetch_related('device', 'device__tenant', 'device__site', 'cable')
|
||||
filter = filters.RearPortFilter
|
||||
filter_form = forms.RearPortFilterForm
|
||||
filterset = filters.RearPortFilterSet
|
||||
filterset_form = forms.RearPortFilterForm
|
||||
table = tables.RearPortDetailTable
|
||||
template_name = 'dcim/device_component_list.html'
|
||||
|
||||
@@ -1672,8 +1672,8 @@ class DeviceBayListView(PermissionRequiredMixin, ObjectListView):
|
||||
queryset = DeviceBay.objects.prefetch_related(
|
||||
'device', 'device__site', 'installed_device', 'installed_device__site'
|
||||
)
|
||||
filter = filters.DeviceBayFilter
|
||||
filter_form = forms.DeviceBayFilterForm
|
||||
filterset = filters.DeviceBayFilterSet
|
||||
filterset_form = forms.DeviceBayFilterForm
|
||||
table = tables.DeviceBayDetailTable
|
||||
template_name = 'dcim/device_component_list.html'
|
||||
|
||||
@@ -1799,7 +1799,7 @@ class DeviceBulkAddConsolePortView(PermissionRequiredMixin, BulkComponentCreateV
|
||||
form = forms.DeviceBulkAddComponentForm
|
||||
model = ConsolePort
|
||||
model_form = forms.ConsolePortForm
|
||||
filter = filters.DeviceFilter
|
||||
filterset = filters.DeviceFilterSet
|
||||
table = tables.DeviceTable
|
||||
default_return_url = 'dcim:device_list'
|
||||
|
||||
@@ -1811,7 +1811,7 @@ class DeviceBulkAddConsoleServerPortView(PermissionRequiredMixin, BulkComponentC
|
||||
form = forms.DeviceBulkAddComponentForm
|
||||
model = ConsoleServerPort
|
||||
model_form = forms.ConsoleServerPortForm
|
||||
filter = filters.DeviceFilter
|
||||
filterset = filters.DeviceFilterSet
|
||||
table = tables.DeviceTable
|
||||
default_return_url = 'dcim:device_list'
|
||||
|
||||
@@ -1823,7 +1823,7 @@ class DeviceBulkAddPowerPortView(PermissionRequiredMixin, BulkComponentCreateVie
|
||||
form = forms.DeviceBulkAddComponentForm
|
||||
model = PowerPort
|
||||
model_form = forms.PowerPortForm
|
||||
filter = filters.DeviceFilter
|
||||
filterset = filters.DeviceFilterSet
|
||||
table = tables.DeviceTable
|
||||
default_return_url = 'dcim:device_list'
|
||||
|
||||
@@ -1835,7 +1835,7 @@ class DeviceBulkAddPowerOutletView(PermissionRequiredMixin, BulkComponentCreateV
|
||||
form = forms.DeviceBulkAddComponentForm
|
||||
model = PowerOutlet
|
||||
model_form = forms.PowerOutletForm
|
||||
filter = filters.DeviceFilter
|
||||
filterset = filters.DeviceFilterSet
|
||||
table = tables.DeviceTable
|
||||
default_return_url = 'dcim:device_list'
|
||||
|
||||
@@ -1847,7 +1847,7 @@ class DeviceBulkAddInterfaceView(PermissionRequiredMixin, BulkComponentCreateVie
|
||||
form = forms.DeviceBulkAddInterfaceForm
|
||||
model = Interface
|
||||
model_form = forms.InterfaceForm
|
||||
filter = filters.DeviceFilter
|
||||
filterset = filters.DeviceFilterSet
|
||||
table = tables.DeviceTable
|
||||
default_return_url = 'dcim:device_list'
|
||||
|
||||
@@ -1859,7 +1859,7 @@ class DeviceBulkAddDeviceBayView(PermissionRequiredMixin, BulkComponentCreateVie
|
||||
form = forms.DeviceBulkAddComponentForm
|
||||
model = DeviceBay
|
||||
model_form = forms.DeviceBayForm
|
||||
filter = filters.DeviceFilter
|
||||
filterset = filters.DeviceFilterSet
|
||||
table = tables.DeviceTable
|
||||
default_return_url = 'dcim:device_list'
|
||||
|
||||
@@ -1873,8 +1873,8 @@ class CableListView(PermissionRequiredMixin, ObjectListView):
|
||||
queryset = Cable.objects.prefetch_related(
|
||||
'termination_a', 'termination_b'
|
||||
)
|
||||
filter = filters.CableFilter
|
||||
filter_form = forms.CableFilterForm
|
||||
filterset = filters.CableFilterSet
|
||||
filterset_form = forms.CableFilterForm
|
||||
table = tables.CableTable
|
||||
template_name = 'dcim/cable_list.html'
|
||||
|
||||
@@ -1900,10 +1900,13 @@ class CableTraceView(PermissionRequiredMixin, View):
|
||||
def get(self, request, model, pk):
|
||||
|
||||
obj = get_object_or_404(model, pk=pk)
|
||||
trace = obj.trace(follow_circuits=True)
|
||||
total_length = sum([entry[1]._abs_length for entry in trace if entry[1] and entry[1]._abs_length])
|
||||
|
||||
return render(request, 'dcim/cable_trace.html', {
|
||||
'obj': obj,
|
||||
'trace': obj.trace(follow_circuits=True),
|
||||
'trace': trace,
|
||||
'total_length': total_length,
|
||||
})
|
||||
|
||||
|
||||
@@ -2007,7 +2010,7 @@ class CableBulkImportView(PermissionRequiredMixin, BulkImportView):
|
||||
class CableBulkEditView(PermissionRequiredMixin, BulkEditView):
|
||||
permission_required = 'dcim.change_cable'
|
||||
queryset = Cable.objects.prefetch_related('termination_a', 'termination_b')
|
||||
filter = filters.CableFilter
|
||||
filterset = filters.CableFilterSet
|
||||
table = tables.CableTable
|
||||
form = forms.CableBulkEditForm
|
||||
default_return_url = 'dcim:cable_list'
|
||||
@@ -2016,7 +2019,7 @@ class CableBulkEditView(PermissionRequiredMixin, BulkEditView):
|
||||
class CableBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
permission_required = 'dcim.delete_cable'
|
||||
queryset = Cable.objects.prefetch_related('termination_a', 'termination_b')
|
||||
filter = filters.CableFilter
|
||||
filterset = filters.CableFilterSet
|
||||
table = tables.CableTable
|
||||
default_return_url = 'dcim:cable_list'
|
||||
|
||||
@@ -2034,8 +2037,8 @@ class ConsoleConnectionsListView(PermissionRequiredMixin, ObjectListView):
|
||||
).order_by(
|
||||
'cable', 'connected_endpoint__device__name', 'connected_endpoint__name'
|
||||
)
|
||||
filter = filters.ConsoleConnectionFilter
|
||||
filter_form = forms.ConsoleConnectionFilterForm
|
||||
filterset = filters.ConsoleConnectionFilterSet
|
||||
filterset_form = forms.ConsoleConnectionFilterForm
|
||||
table = tables.ConsoleConnectionTable
|
||||
template_name = 'dcim/console_connections_list.html'
|
||||
|
||||
@@ -2053,7 +2056,8 @@ class ConsoleConnectionsListView(PermissionRequiredMixin, ObjectListView):
|
||||
obj.get_connection_status_display(),
|
||||
])
|
||||
csv_data.append(csv)
|
||||
return csv_data
|
||||
|
||||
return '\n'.join(csv_data)
|
||||
|
||||
|
||||
class PowerConnectionsListView(PermissionRequiredMixin, ObjectListView):
|
||||
@@ -2065,8 +2069,8 @@ class PowerConnectionsListView(PermissionRequiredMixin, ObjectListView):
|
||||
).order_by(
|
||||
'cable', '_connected_poweroutlet__device__name', '_connected_poweroutlet__name'
|
||||
)
|
||||
filter = filters.PowerConnectionFilter
|
||||
filter_form = forms.PowerConnectionFilterForm
|
||||
filterset = filters.PowerConnectionFilterSet
|
||||
filterset_form = forms.PowerConnectionFilterForm
|
||||
table = tables.PowerConnectionTable
|
||||
template_name = 'dcim/power_connections_list.html'
|
||||
|
||||
@@ -2084,7 +2088,8 @@ class PowerConnectionsListView(PermissionRequiredMixin, ObjectListView):
|
||||
obj.get_connection_status_display(),
|
||||
])
|
||||
csv_data.append(csv)
|
||||
return csv_data
|
||||
|
||||
return '\n'.join(csv_data)
|
||||
|
||||
|
||||
class InterfaceConnectionsListView(PermissionRequiredMixin, ObjectListView):
|
||||
@@ -2098,8 +2103,8 @@ class InterfaceConnectionsListView(PermissionRequiredMixin, ObjectListView):
|
||||
).order_by(
|
||||
'device'
|
||||
)
|
||||
filter = filters.InterfaceConnectionFilter
|
||||
filter_form = forms.InterfaceConnectionFilterForm
|
||||
filterset = filters.InterfaceConnectionFilterSet
|
||||
filterset_form = forms.InterfaceConnectionFilterForm
|
||||
table = tables.InterfaceConnectionTable
|
||||
template_name = 'dcim/interface_connections_list.html'
|
||||
|
||||
@@ -2123,7 +2128,8 @@ class InterfaceConnectionsListView(PermissionRequiredMixin, ObjectListView):
|
||||
obj.get_connection_status_display(),
|
||||
])
|
||||
csv_data.append(csv)
|
||||
return csv_data
|
||||
|
||||
return '\n'.join(csv_data)
|
||||
|
||||
|
||||
#
|
||||
@@ -2133,8 +2139,8 @@ class InterfaceConnectionsListView(PermissionRequiredMixin, ObjectListView):
|
||||
class InventoryItemListView(PermissionRequiredMixin, ObjectListView):
|
||||
permission_required = 'dcim.view_inventoryitem'
|
||||
queryset = InventoryItem.objects.prefetch_related('device', 'manufacturer')
|
||||
filter = filters.InventoryItemFilter
|
||||
filter_form = forms.InventoryItemFilterForm
|
||||
filterset = filters.InventoryItemFilterSet
|
||||
filterset_form = forms.InventoryItemFilterForm
|
||||
table = tables.InventoryItemTable
|
||||
template_name = 'dcim/inventoryitem_list.html'
|
||||
|
||||
@@ -2168,7 +2174,7 @@ class InventoryItemBulkImportView(PermissionRequiredMixin, BulkImportView):
|
||||
class InventoryItemBulkEditView(PermissionRequiredMixin, BulkEditView):
|
||||
permission_required = 'dcim.change_inventoryitem'
|
||||
queryset = InventoryItem.objects.prefetch_related('device', 'manufacturer')
|
||||
filter = filters.InventoryItemFilter
|
||||
filterset = filters.InventoryItemFilterSet
|
||||
table = tables.InventoryItemTable
|
||||
form = forms.InventoryItemBulkEditForm
|
||||
default_return_url = 'dcim:inventoryitem_list'
|
||||
@@ -2190,8 +2196,8 @@ class VirtualChassisListView(PermissionRequiredMixin, ObjectListView):
|
||||
permission_required = 'dcim.view_virtualchassis'
|
||||
queryset = VirtualChassis.objects.prefetch_related('master').annotate(member_count=Count('members'))
|
||||
table = tables.VirtualChassisTable
|
||||
filter = filters.VirtualChassisFilter
|
||||
filter_form = forms.VirtualChassisFilterForm
|
||||
filterset = filters.VirtualChassisFilterSet
|
||||
filterset_form = forms.VirtualChassisFilterForm
|
||||
template_name = 'dcim/virtualchassis_list.html'
|
||||
|
||||
|
||||
@@ -2433,8 +2439,8 @@ class PowerPanelListView(PermissionRequiredMixin, ObjectListView):
|
||||
).annotate(
|
||||
powerfeed_count=Count('powerfeeds')
|
||||
)
|
||||
filter = filters.PowerPanelFilter
|
||||
filter_form = forms.PowerPanelFilterForm
|
||||
filterset = filters.PowerPanelFilterSet
|
||||
filterset_form = forms.PowerPanelFilterForm
|
||||
table = tables.PowerPanelTable
|
||||
template_name = 'dcim/powerpanel_list.html'
|
||||
|
||||
@@ -2488,7 +2494,7 @@ class PowerPanelBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
).annotate(
|
||||
rack_count=Count('powerfeeds')
|
||||
)
|
||||
filter = filters.PowerPanelFilter
|
||||
filterset = filters.PowerPanelFilterSet
|
||||
table = tables.PowerPanelTable
|
||||
default_return_url = 'dcim:powerpanel_list'
|
||||
|
||||
@@ -2502,8 +2508,8 @@ class PowerFeedListView(PermissionRequiredMixin, ObjectListView):
|
||||
queryset = PowerFeed.objects.prefetch_related(
|
||||
'power_panel', 'rack'
|
||||
)
|
||||
filter = filters.PowerFeedFilter
|
||||
filter_form = forms.PowerFeedFilterForm
|
||||
filterset = filters.PowerFeedFilterSet
|
||||
filterset_form = forms.PowerFeedFilterForm
|
||||
table = tables.PowerFeedTable
|
||||
template_name = 'dcim/powerfeed_list.html'
|
||||
|
||||
@@ -2548,7 +2554,7 @@ class PowerFeedBulkImportView(PermissionRequiredMixin, BulkImportView):
|
||||
class PowerFeedBulkEditView(PermissionRequiredMixin, BulkEditView):
|
||||
permission_required = 'dcim.change_powerfeed'
|
||||
queryset = PowerFeed.objects.prefetch_related('power_panel', 'rack')
|
||||
filter = filters.PowerFeedFilter
|
||||
filterset = filters.PowerFeedFilterSet
|
||||
table = tables.PowerFeedTable
|
||||
form = forms.PowerFeedBulkEditForm
|
||||
default_return_url = 'dcim:powerfeed_list'
|
||||
@@ -2557,6 +2563,6 @@ class PowerFeedBulkEditView(PermissionRequiredMixin, BulkEditView):
|
||||
class PowerFeedBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
permission_required = 'dcim.delete_powerfeed'
|
||||
queryset = PowerFeed.objects.prefetch_related('power_panel', 'rack')
|
||||
filter = filters.PowerFeedFilter
|
||||
filterset = filters.PowerFeedFilterSet
|
||||
table = tables.PowerFeedTable
|
||||
default_return_url = 'dcim:powerfeed_list'
|
||||
|
||||
@@ -3,7 +3,8 @@ from django.contrib import admin
|
||||
|
||||
from netbox.admin import admin_site
|
||||
from utilities.forms import LaxURLField
|
||||
from .models import CustomField, CustomFieldChoice, CustomLink, Graph, ExportTemplate, Webhook
|
||||
from .models import CustomField, CustomFieldChoice, CustomLink, Graph, ExportTemplate, ReportResult, Webhook
|
||||
from .reports import get_report
|
||||
|
||||
|
||||
def order_content_types(field):
|
||||
@@ -130,10 +131,10 @@ class CustomLinkAdmin(admin.ModelAdmin):
|
||||
@admin.register(Graph, site=admin_site)
|
||||
class GraphAdmin(admin.ModelAdmin):
|
||||
list_display = [
|
||||
'name', 'type', 'weight', 'source',
|
||||
'name', 'type', 'weight', 'template_language', 'source',
|
||||
]
|
||||
list_filter = [
|
||||
'type',
|
||||
'type', 'template_language',
|
||||
]
|
||||
|
||||
|
||||
@@ -164,3 +165,33 @@ class ExportTemplateAdmin(admin.ModelAdmin):
|
||||
'content_type',
|
||||
]
|
||||
form = ExportTemplateForm
|
||||
|
||||
|
||||
#
|
||||
# Reports
|
||||
#
|
||||
|
||||
@admin.register(ReportResult, site=admin_site)
|
||||
class ReportResultAdmin(admin.ModelAdmin):
|
||||
list_display = [
|
||||
'report', 'active', 'created', 'user', 'passing',
|
||||
]
|
||||
fields = [
|
||||
'report', 'user', 'passing', 'data',
|
||||
]
|
||||
list_filter = [
|
||||
'failed',
|
||||
]
|
||||
readonly_fields = fields
|
||||
|
||||
def has_add_permission(self, request):
|
||||
return False
|
||||
|
||||
def active(self, obj):
|
||||
module, report_name = obj.report.split('.')
|
||||
return True if get_report(module, report_name) else False
|
||||
active.boolean = True
|
||||
|
||||
def passing(self, obj):
|
||||
return not obj.failed
|
||||
passing.boolean = True
|
||||
|
||||
@@ -22,7 +22,9 @@ class CustomFieldsSerializer(serializers.BaseSerializer):
|
||||
def to_internal_value(self, data):
|
||||
|
||||
content_type = ContentType.objects.get_for_model(self.parent.Meta.model)
|
||||
custom_fields = {field.name: field for field in CustomField.objects.filter(obj_type=content_type)}
|
||||
custom_fields = {
|
||||
field.name: field for field in CustomField.objects.filter(obj_type=content_type)
|
||||
}
|
||||
|
||||
for field_name, value in data.items():
|
||||
|
||||
@@ -107,11 +109,11 @@ class CustomFieldModelSerializer(ValidatedModelSerializer):
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
if self.instance is not None:
|
||||
# Retrieve the set of CustomFields which apply to this type of object
|
||||
content_type = ContentType.objects.get_for_model(self.Meta.model)
|
||||
fields = CustomField.objects.filter(obj_type=content_type)
|
||||
|
||||
# Retrieve the set of CustomFields which apply to this type of object
|
||||
content_type = ContentType.objects.get_for_model(self.Meta.model)
|
||||
fields = CustomField.objects.filter(obj_type=content_type)
|
||||
if self.instance is not None:
|
||||
|
||||
# Populate CustomFieldValues for each instance from database
|
||||
try:
|
||||
@@ -120,6 +122,26 @@ class CustomFieldModelSerializer(ValidatedModelSerializer):
|
||||
except TypeError:
|
||||
_populate_custom_fields(self.instance, fields)
|
||||
|
||||
else:
|
||||
|
||||
if not hasattr(self, 'initial_data'):
|
||||
self.initial_data = {}
|
||||
|
||||
# Populate default values
|
||||
if fields and 'custom_fields' not in self.initial_data:
|
||||
self.initial_data['custom_fields'] = {}
|
||||
|
||||
# Populate initial data using custom field default values
|
||||
for field in fields:
|
||||
if field.name not in self.initial_data['custom_fields'] and field.default:
|
||||
if field.type == CustomFieldTypeChoices.TYPE_SELECT:
|
||||
field_value = field.choices.get(value=field.default).pk
|
||||
elif field.type == CustomFieldTypeChoices.TYPE_BOOLEAN:
|
||||
field_value = bool(field.default)
|
||||
else:
|
||||
field_value = field.default
|
||||
self.initial_data['custom_fields'][field.name] = field_value
|
||||
|
||||
def _save_custom_fields(self, instance, custom_fields):
|
||||
content_type = ContentType.objects.get_for_model(self.Meta.model)
|
||||
for field_name, value in custom_fields.items():
|
||||
|
||||
@@ -29,12 +29,12 @@ from .nested_serializers import *
|
||||
|
||||
class GraphSerializer(ValidatedModelSerializer):
|
||||
type = ContentTypeField(
|
||||
queryset=ContentType.objects.all()
|
||||
queryset=ContentType.objects.filter(GRAPH_MODELS),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Graph
|
||||
fields = ['id', 'type', 'weight', 'name', 'source', 'link']
|
||||
fields = ['id', 'type', 'weight', 'name', 'template_language', 'source', 'link']
|
||||
|
||||
|
||||
class RenderedGraphSerializer(serializers.ModelSerializer):
|
||||
@@ -61,8 +61,8 @@ class RenderedGraphSerializer(serializers.ModelSerializer):
|
||||
|
||||
class ExportTemplateSerializer(ValidatedModelSerializer):
|
||||
template_language = ChoiceField(
|
||||
choices=ExportTemplateLanguageChoices,
|
||||
default=ExportTemplateLanguageChoices.LANGUAGE_JINJA2
|
||||
choices=TemplateLanguageChoices,
|
||||
default=TemplateLanguageChoices.LANGUAGE_JINJA2
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
||||
@@ -25,9 +25,9 @@ from . import serializers
|
||||
|
||||
class ExtrasFieldChoicesViewSet(FieldChoicesViewSet):
|
||||
fields = (
|
||||
(ExportTemplate, ['template_language']),
|
||||
(Graph, ['type']),
|
||||
(ObjectChange, ['action']),
|
||||
(serializers.ExportTemplateSerializer, ['template_language']),
|
||||
(serializers.GraphSerializer, ['type', 'template_language']),
|
||||
(serializers.ObjectChangeSerializer, ['action']),
|
||||
)
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ class CustomFieldModelViewSet(ModelViewSet):
|
||||
class GraphViewSet(ModelViewSet):
|
||||
queryset = Graph.objects.all()
|
||||
serializer_class = serializers.GraphSerializer
|
||||
filterset_class = filters.GraphFilter
|
||||
filterset_class = filters.GraphFilterSet
|
||||
|
||||
|
||||
#
|
||||
@@ -112,7 +112,7 @@ class GraphViewSet(ModelViewSet):
|
||||
class ExportTemplateViewSet(ModelViewSet):
|
||||
queryset = ExportTemplate.objects.all()
|
||||
serializer_class = serializers.ExportTemplateSerializer
|
||||
filterset_class = filters.ExportTemplateFilter
|
||||
filterset_class = filters.ExportTemplateFilterSet
|
||||
|
||||
|
||||
#
|
||||
@@ -124,7 +124,7 @@ class TagViewSet(ModelViewSet):
|
||||
tagged_items=Count('extras_taggeditem_items', distinct=True)
|
||||
)
|
||||
serializer_class = serializers.TagSerializer
|
||||
filterset_class = filters.TagFilter
|
||||
filterset_class = filters.TagFilterSet
|
||||
|
||||
|
||||
#
|
||||
@@ -145,7 +145,7 @@ class ConfigContextViewSet(ModelViewSet):
|
||||
'regions', 'sites', 'roles', 'platforms', 'tenant_groups', 'tenants',
|
||||
)
|
||||
serializer_class = serializers.ConfigContextSerializer
|
||||
filterset_class = filters.ConfigContextFilter
|
||||
filterset_class = filters.ConfigContextFilterSet
|
||||
|
||||
|
||||
#
|
||||
@@ -284,4 +284,4 @@ class ObjectChangeViewSet(ReadOnlyModelViewSet):
|
||||
"""
|
||||
queryset = ObjectChange.objects.prefetch_related('user')
|
||||
serializer_class = serializers.ObjectChangeSerializer
|
||||
filterset_class = filters.ObjectChangeFilter
|
||||
filterset_class = filters.ObjectChangeFilterSet
|
||||
|
||||
@@ -104,7 +104,7 @@ class ObjectChangeActionChoices(ChoiceSet):
|
||||
# ExportTemplates
|
||||
#
|
||||
|
||||
class ExportTemplateLanguageChoices(ChoiceSet):
|
||||
class TemplateLanguageChoices(ChoiceSet):
|
||||
|
||||
LANGUAGE_DJANGO = 'django'
|
||||
LANGUAGE_JINJA2 = 'jinja2'
|
||||
|
||||
@@ -1,77 +1,128 @@
|
||||
from django.db.models import Q
|
||||
|
||||
|
||||
# Models which support custom fields
|
||||
CUSTOMFIELD_MODELS = [
|
||||
'circuits.circuit',
|
||||
'circuits.provider',
|
||||
'dcim.device',
|
||||
'dcim.devicetype',
|
||||
'dcim.powerfeed',
|
||||
'dcim.rack',
|
||||
'dcim.site',
|
||||
'ipam.aggregate',
|
||||
'ipam.ipaddress',
|
||||
'ipam.prefix',
|
||||
'ipam.service',
|
||||
'ipam.vlan',
|
||||
'ipam.vrf',
|
||||
'secrets.secret',
|
||||
'tenancy.tenant',
|
||||
'virtualization.cluster',
|
||||
'virtualization.virtualmachine',
|
||||
]
|
||||
CUSTOMFIELD_MODELS = Q(
|
||||
Q(app_label='circuits', model__in=[
|
||||
'circuit',
|
||||
'provider',
|
||||
]) |
|
||||
Q(app_label='dcim', model__in=[
|
||||
'device',
|
||||
'devicetype',
|
||||
'powerfeed',
|
||||
'rack',
|
||||
'site',
|
||||
]) |
|
||||
Q(app_label='ipam', model__in=[
|
||||
'aggregate',
|
||||
'ipaddress',
|
||||
'prefix',
|
||||
'service',
|
||||
'vlan',
|
||||
'vrf',
|
||||
]) |
|
||||
Q(app_label='secrets', model__in=[
|
||||
'secret',
|
||||
]) |
|
||||
Q(app_label='tenancy', model__in=[
|
||||
'tenant',
|
||||
]) |
|
||||
Q(app_label='virtualization', model__in=[
|
||||
'cluster',
|
||||
'virtualmachine',
|
||||
])
|
||||
)
|
||||
|
||||
# Custom links
|
||||
CUSTOMLINK_MODELS = [
|
||||
'circuits.circuit',
|
||||
'circuits.provider',
|
||||
'dcim.cable',
|
||||
'dcim.device',
|
||||
'dcim.devicetype',
|
||||
'dcim.powerpanel',
|
||||
'dcim.powerfeed',
|
||||
'dcim.rack',
|
||||
'dcim.site',
|
||||
'ipam.aggregate',
|
||||
'ipam.ipaddress',
|
||||
'ipam.prefix',
|
||||
'ipam.service',
|
||||
'ipam.vlan',
|
||||
'ipam.vrf',
|
||||
'secrets.secret',
|
||||
'tenancy.tenant',
|
||||
'virtualization.cluster',
|
||||
'virtualization.virtualmachine',
|
||||
]
|
||||
CUSTOMLINK_MODELS = Q(
|
||||
Q(app_label='circuits', model__in=[
|
||||
'circuit',
|
||||
'provider',
|
||||
]) |
|
||||
Q(app_label='dcim', model__in=[
|
||||
'cable',
|
||||
'device',
|
||||
'devicetype',
|
||||
'powerpanel',
|
||||
'powerfeed',
|
||||
'rack',
|
||||
'site',
|
||||
]) |
|
||||
Q(app_label='ipam', model__in=[
|
||||
'aggregate',
|
||||
'ipaddress',
|
||||
'prefix',
|
||||
'service',
|
||||
'vlan',
|
||||
'vrf',
|
||||
]) |
|
||||
Q(app_label='secrets', model__in=[
|
||||
'secret',
|
||||
]) |
|
||||
Q(app_label='tenancy', model__in=[
|
||||
'tenant',
|
||||
]) |
|
||||
Q(app_label='virtualization', model__in=[
|
||||
'cluster',
|
||||
'virtualmachine',
|
||||
])
|
||||
)
|
||||
|
||||
# Models which can have Graphs associated with them
|
||||
GRAPH_MODELS = Q(
|
||||
Q(app_label='circuits', model__in=[
|
||||
'provider',
|
||||
]) |
|
||||
Q(app_label='dcim', model__in=[
|
||||
'device',
|
||||
'interface',
|
||||
'site',
|
||||
])
|
||||
)
|
||||
|
||||
# Models which support export templates
|
||||
EXPORTTEMPLATE_MODELS = [
|
||||
'circuits.circuit',
|
||||
'circuits.provider',
|
||||
'dcim.cable',
|
||||
'dcim.consoleport',
|
||||
'dcim.device',
|
||||
'dcim.devicetype',
|
||||
'dcim.interface',
|
||||
'dcim.inventoryitem',
|
||||
'dcim.manufacturer',
|
||||
'dcim.powerpanel',
|
||||
'dcim.powerport',
|
||||
'dcim.powerfeed',
|
||||
'dcim.rack',
|
||||
'dcim.rackgroup',
|
||||
'dcim.region',
|
||||
'dcim.site',
|
||||
'dcim.virtualchassis',
|
||||
'ipam.aggregate',
|
||||
'ipam.ipaddress',
|
||||
'ipam.prefix',
|
||||
'ipam.service',
|
||||
'ipam.vlan',
|
||||
'ipam.vrf',
|
||||
'secrets.secret',
|
||||
'tenancy.tenant',
|
||||
'virtualization.cluster',
|
||||
'virtualization.virtualmachine',
|
||||
]
|
||||
EXPORTTEMPLATE_MODELS = Q(
|
||||
Q(app_label='circuits', model__in=[
|
||||
'circuit',
|
||||
'provider',
|
||||
]) |
|
||||
Q(app_label='dcim', model__in=[
|
||||
'cable',
|
||||
'consoleport',
|
||||
'device',
|
||||
'devicetype',
|
||||
'interface',
|
||||
'inventoryitem',
|
||||
'manufacturer',
|
||||
'powerpanel',
|
||||
'powerport',
|
||||
'powerfeed',
|
||||
'rack',
|
||||
'rackgroup',
|
||||
'region',
|
||||
'site',
|
||||
'virtualchassis',
|
||||
]) |
|
||||
Q(app_label='ipam', model__in=[
|
||||
'aggregate',
|
||||
'ipaddress',
|
||||
'prefix',
|
||||
'service',
|
||||
'vlan',
|
||||
'vrf',
|
||||
]) |
|
||||
Q(app_label='secrets', model__in=[
|
||||
'secret',
|
||||
]) |
|
||||
Q(app_label='tenancy', model__in=[
|
||||
'tenant',
|
||||
]) |
|
||||
Q(app_label='virtualization', model__in=[
|
||||
'cluster',
|
||||
'virtualmachine',
|
||||
])
|
||||
)
|
||||
|
||||
# Report logging levels
|
||||
LOG_DEFAULT = 0
|
||||
@@ -88,36 +139,48 @@ LOG_LEVEL_CODES = {
|
||||
}
|
||||
|
||||
# Models which support registered webhooks
|
||||
WEBHOOK_MODELS = [
|
||||
'circuits.circuit',
|
||||
'circuits.provider',
|
||||
'dcim.cable',
|
||||
'dcim.consoleport',
|
||||
'dcim.consoleserverport',
|
||||
'dcim.device',
|
||||
'dcim.devicebay',
|
||||
'dcim.devicetype',
|
||||
'dcim.interface',
|
||||
'dcim.inventoryitem',
|
||||
'dcim.frontport',
|
||||
'dcim.manufacturer',
|
||||
'dcim.poweroutlet',
|
||||
'dcim.powerpanel',
|
||||
'dcim.powerport',
|
||||
'dcim.powerfeed',
|
||||
'dcim.rack',
|
||||
'dcim.rearport',
|
||||
'dcim.region',
|
||||
'dcim.site',
|
||||
'dcim.virtualchassis',
|
||||
'ipam.aggregate',
|
||||
'ipam.ipaddress',
|
||||
'ipam.prefix',
|
||||
'ipam.service',
|
||||
'ipam.vlan',
|
||||
'ipam.vrf',
|
||||
'secrets.secret',
|
||||
'tenancy.tenant',
|
||||
'virtualization.cluster',
|
||||
'virtualization.virtualmachine',
|
||||
]
|
||||
WEBHOOK_MODELS = Q(
|
||||
Q(app_label='circuits', model__in=[
|
||||
'circuit',
|
||||
'provider',
|
||||
]) |
|
||||
Q(app_label='dcim', model__in=[
|
||||
'cable',
|
||||
'consoleport',
|
||||
'consoleserverport',
|
||||
'device',
|
||||
'devicebay',
|
||||
'devicetype',
|
||||
'frontport',
|
||||
'interface',
|
||||
'inventoryitem',
|
||||
'manufacturer',
|
||||
'poweroutlet',
|
||||
'powerpanel',
|
||||
'powerport',
|
||||
'powerfeed',
|
||||
'rack',
|
||||
'rearport',
|
||||
'region',
|
||||
'site',
|
||||
'virtualchassis',
|
||||
]) |
|
||||
Q(app_label='ipam', model__in=[
|
||||
'aggregate',
|
||||
'ipaddress',
|
||||
'prefix',
|
||||
'service',
|
||||
'vlan',
|
||||
'vrf',
|
||||
]) |
|
||||
Q(app_label='secrets', model__in=[
|
||||
'secret',
|
||||
]) |
|
||||
Q(app_label='tenancy', model__in=[
|
||||
'tenant',
|
||||
]) |
|
||||
Q(app_label='virtualization', model__in=[
|
||||
'cluster',
|
||||
'virtualmachine',
|
||||
])
|
||||
)
|
||||
|
||||
@@ -8,6 +8,19 @@ from .choices import *
|
||||
from .models import ConfigContext, CustomField, Graph, ExportTemplate, ObjectChange, Tag
|
||||
|
||||
|
||||
__all__ = (
|
||||
'ConfigContextFilterSet',
|
||||
'CreatedUpdatedFilterSet',
|
||||
'CustomFieldFilter',
|
||||
'CustomFieldFilterSet',
|
||||
'ExportTemplateFilterSet',
|
||||
'GraphFilterSet',
|
||||
'LocalConfigContextFilterSet',
|
||||
'ObjectChangeFilterSet',
|
||||
'TagFilterSet',
|
||||
)
|
||||
|
||||
|
||||
class CustomFieldFilter(django_filters.Filter):
|
||||
"""
|
||||
Filter objects by the presence of a CustomFieldValue. The filter's name is used as the CustomField name.
|
||||
@@ -75,21 +88,21 @@ class CustomFieldFilterSet(django_filters.FilterSet):
|
||||
self.filters['cf_{}'.format(cf.name)] = CustomFieldFilter(field_name=cf.name, custom_field=cf)
|
||||
|
||||
|
||||
class GraphFilter(django_filters.FilterSet):
|
||||
class GraphFilterSet(django_filters.FilterSet):
|
||||
|
||||
class Meta:
|
||||
model = Graph
|
||||
fields = ['type', 'name']
|
||||
fields = ['type', 'name', 'template_language']
|
||||
|
||||
|
||||
class ExportTemplateFilter(django_filters.FilterSet):
|
||||
class ExportTemplateFilterSet(django_filters.FilterSet):
|
||||
|
||||
class Meta:
|
||||
model = ExportTemplate
|
||||
fields = ['content_type', 'name', 'template_language']
|
||||
|
||||
|
||||
class TagFilter(django_filters.FilterSet):
|
||||
class TagFilterSet(django_filters.FilterSet):
|
||||
q = django_filters.CharFilter(
|
||||
method='search',
|
||||
label='Search',
|
||||
@@ -108,7 +121,7 @@ class TagFilter(django_filters.FilterSet):
|
||||
)
|
||||
|
||||
|
||||
class ConfigContextFilter(django_filters.FilterSet):
|
||||
class ConfigContextFilterSet(django_filters.FilterSet):
|
||||
q = django_filters.CharFilter(
|
||||
method='search',
|
||||
label='Search',
|
||||
@@ -204,7 +217,7 @@ class ConfigContextFilter(django_filters.FilterSet):
|
||||
# Filter for Local Config Context Data
|
||||
#
|
||||
|
||||
class LocalConfigContextFilter(django_filters.FilterSet):
|
||||
class LocalConfigContextFilterSet(django_filters.FilterSet):
|
||||
local_context_data = django_filters.BooleanFilter(
|
||||
method='_local_context_data',
|
||||
label='Has local config context data',
|
||||
@@ -214,7 +227,7 @@ class LocalConfigContextFilter(django_filters.FilterSet):
|
||||
return queryset.exclude(local_context_data__isnull=value)
|
||||
|
||||
|
||||
class ObjectChangeFilter(django_filters.FilterSet):
|
||||
class ObjectChangeFilterSet(django_filters.FilterSet):
|
||||
q = django_filters.CharFilter(
|
||||
method='search',
|
||||
label='Search',
|
||||
|
||||
@@ -10,8 +10,8 @@ from dcim.models import DeviceRole, Platform, Region, Site
|
||||
from tenancy.models import Tenant, TenantGroup
|
||||
from utilities.forms import (
|
||||
add_blank_choice, APISelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect, ColorSelect,
|
||||
CommentField, ContentTypeSelect, FilterChoiceField, LaxURLField, JSONField, SlugField, StaticSelect2,
|
||||
BOOLEAN_WITH_BLANK_CHOICES,
|
||||
CommentField, ContentTypeSelect, DatePicker, DateTimePicker, FilterChoiceField, LaxURLField, JSONField,
|
||||
SlugField, StaticSelect2, BOOLEAN_WITH_BLANK_CHOICES,
|
||||
)
|
||||
from .choices import *
|
||||
from .models import ConfigContext, CustomField, CustomFieldValue, ImageAttachment, ObjectChange, Tag
|
||||
@@ -52,12 +52,12 @@ def get_custom_fields_for_model(content_type, filterable_only=False, bulk_edit=F
|
||||
else:
|
||||
initial = None
|
||||
field = forms.NullBooleanField(
|
||||
required=cf.required, initial=initial, widget=forms.Select(choices=choices)
|
||||
required=cf.required, initial=initial, widget=StaticSelect2(choices=choices)
|
||||
)
|
||||
|
||||
# Date
|
||||
elif cf.type == CustomFieldTypeChoices.TYPE_DATE:
|
||||
field = forms.DateField(required=cf.required, initial=initial, help_text="Date format: YYYY-MM-DD")
|
||||
field = forms.DateField(required=cf.required, initial=initial, widget=DatePicker())
|
||||
|
||||
# Select
|
||||
elif cf.type == CustomFieldTypeChoices.TYPE_SELECT:
|
||||
@@ -71,7 +71,9 @@ def get_custom_fields_for_model(content_type, filterable_only=False, bulk_edit=F
|
||||
default_choice = cf.choices.get(value=initial).pk
|
||||
except ObjectDoesNotExist:
|
||||
pass
|
||||
field = forms.TypedChoiceField(choices=choices, coerce=int, required=cf.required, initial=default_choice)
|
||||
field = forms.TypedChoiceField(
|
||||
choices=choices, coerce=int, required=cf.required, initial=default_choice, widget=StaticSelect2()
|
||||
)
|
||||
|
||||
# URL
|
||||
elif cf.type == CustomFieldTypeChoices.TYPE_URL:
|
||||
@@ -404,16 +406,12 @@ class ObjectChangeFilterForm(BootstrapMixin, forms.Form):
|
||||
time_after = forms.DateTimeField(
|
||||
label='After',
|
||||
required=False,
|
||||
widget=forms.TextInput(
|
||||
attrs={'placeholder': 'YYYY-MM-DD hh:mm:ss'}
|
||||
)
|
||||
widget=DateTimePicker()
|
||||
)
|
||||
time_before = forms.DateTimeField(
|
||||
label='Before',
|
||||
required=False,
|
||||
widget=forms.TextInput(
|
||||
attrs={'placeholder': 'YYYY-MM-DD hh:mm:ss'}
|
||||
)
|
||||
widget=DateTimePicker()
|
||||
)
|
||||
action = forms.ChoiceField(
|
||||
choices=add_blank_choice(ObjectChangeActionChoices),
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.14 on 2018-07-31 02:19
|
||||
|
||||
from django.conf import settings
|
||||
import django.contrib.postgres.fields.jsonb
|
||||
from django.db import connection, migrations, models
|
||||
import django.db.models.deletion
|
||||
import extras.models
|
||||
from django.conf import settings
|
||||
from django.db import connection, migrations, models
|
||||
from django.db.utils import OperationalError
|
||||
|
||||
import extras.models
|
||||
|
||||
|
||||
def verify_postgresql_version(apps, schema_editor):
|
||||
"""
|
||||
@@ -27,17 +25,57 @@ def verify_postgresql_version(apps, schema_editor):
|
||||
pass
|
||||
|
||||
|
||||
def is_filterable_to_filter_logic(apps, schema_editor):
|
||||
CustomField = apps.get_model('extras', 'CustomField')
|
||||
CustomField.objects.filter(is_filterable=False).update(filter_logic=0)
|
||||
CustomField.objects.filter(is_filterable=True).update(filter_logic=1)
|
||||
# Select fields match on primary key only
|
||||
CustomField.objects.filter(is_filterable=True, type=600).update(filter_logic=2)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
replaces = [('extras', '0001_initial'), ('extras', '0002_custom_fields'), ('extras', '0003_exporttemplate_add_description'), ('extras', '0004_topologymap_change_comma_to_semicolon'), ('extras', '0005_useraction_add_bulk_create'), ('extras', '0006_add_imageattachments'), ('extras', '0007_unicode_literals'), ('extras', '0008_reports'), ('extras', '0009_topologymap_type'), ('extras', '0010_customfield_filter_logic')]
|
||||
replaces = [('extras', '0001_initial'), ('extras', '0002_custom_fields'), ('extras', '0003_exporttemplate_add_description'), ('extras', '0004_topologymap_change_comma_to_semicolon'), ('extras', '0005_useraction_add_bulk_create'), ('extras', '0006_add_imageattachments'), ('extras', '0007_unicode_literals'), ('extras', '0008_reports'), ('extras', '0009_topologymap_type'), ('extras', '0010_customfield_filter_logic'), ('extras', '0011_django2'), ('extras', '0012_webhooks'), ('extras', '0013_objectchange')]
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('dcim', '0002_auto_20160622_1821'),
|
||||
('contenttypes', '0002_remove_content_type_name'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='CustomField',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('type', models.PositiveSmallIntegerField(choices=[(100, 'Text'), (200, 'Integer'), (300, 'Boolean (true/false)'), (400, 'Date'), (500, 'URL'), (600, 'Selection')], default=100)),
|
||||
('name', models.CharField(max_length=50, unique=True)),
|
||||
('label', models.CharField(blank=True, help_text="Name of the field as displayed to users (if not provided, the field's name will be used)", max_length=50)),
|
||||
('description', models.CharField(blank=True, max_length=100)),
|
||||
('required', models.BooleanField(default=False, help_text='Determines whether this field is required when creating new objects or editing an existing object.')),
|
||||
('is_filterable', models.BooleanField(default=True, help_text='This field can be used to filter objects.')),
|
||||
('default', models.CharField(blank=True, help_text='Default value for the field. Use "true" or "false" for booleans.', max_length=100)),
|
||||
('weight', models.PositiveSmallIntegerField(default=100, help_text='Fields with higher weights appear lower in a form')),
|
||||
('obj_type', models.ManyToManyField(help_text='The object(s) to which this field applies.', related_name='custom_fields', to='contenttypes.ContentType', verbose_name='Object(s)')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['weight', 'name'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CustomFieldValue',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('obj_id', models.PositiveIntegerField()),
|
||||
('serialized_value', models.CharField(max_length=255)),
|
||||
('field', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='values', to='extras.CustomField')),
|
||||
('obj_type', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.ContentType')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['obj_type', 'obj_id'],
|
||||
'unique_together': {('field', 'obj_type', 'obj_id')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ExportTemplate',
|
||||
fields=[
|
||||
@@ -51,6 +89,20 @@ class Migration(migrations.Migration):
|
||||
],
|
||||
options={
|
||||
'ordering': ['content_type', 'name'],
|
||||
'unique_together': {('content_type', 'name')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CustomFieldChoice',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('value', models.CharField(max_length=100)),
|
||||
('weight', models.PositiveSmallIntegerField(default=100, help_text='Higher weights appear lower in the list')),
|
||||
('field', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='choices', to='extras.CustomField')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['field', 'weight', 'value'],
|
||||
'unique_together': {('field', 'value')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
@@ -67,6 +119,22 @@ class Migration(migrations.Migration):
|
||||
'ordering': ['type', 'weight', 'name'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ImageAttachment',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('object_id', models.PositiveIntegerField()),
|
||||
('image', models.ImageField(height_field='image_height', upload_to=extras.models.image_upload, width_field='image_width')),
|
||||
('image_height', models.PositiveSmallIntegerField()),
|
||||
('image_width', models.PositiveSmallIntegerField()),
|
||||
('name', models.CharField(blank=True, max_length=50)),
|
||||
('created', models.DateTimeField(auto_now_add=True)),
|
||||
('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['name'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='TopologyMap',
|
||||
fields=[
|
||||
@@ -76,7 +144,6 @@ class Migration(migrations.Migration):
|
||||
('device_patterns', models.TextField(help_text='Identify devices to include in the diagram using regular expressions, one per line. Each line will result in a new tier of the drawing. Separate multiple regexes within a line using semicolons. Devices will be rendered in the order they are defined.')),
|
||||
('description', models.CharField(blank=True, max_length=100)),
|
||||
('site', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='topology_maps', to='dcim.Site')),
|
||||
('type', models.PositiveSmallIntegerField(choices=[(1, 'Network'), (2, 'Console'), (3, 'Power')], default=1)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['name'],
|
||||
@@ -97,77 +164,6 @@ class Migration(migrations.Migration):
|
||||
'ordering': ['-time'],
|
||||
},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='exporttemplate',
|
||||
unique_together=set([('content_type', 'name')]),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CustomField',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('type', models.PositiveSmallIntegerField(choices=[(100, 'Text'), (200, 'Integer'), (300, 'Boolean (true/false)'), (400, 'Date'), (500, 'URL'), (600, 'Selection')], default=100)),
|
||||
('name', models.CharField(max_length=50, unique=True)),
|
||||
('label', models.CharField(blank=True, help_text="Name of the field as displayed to users (if not provided, the field's name will be used)", max_length=50)),
|
||||
('description', models.CharField(blank=True, max_length=100)),
|
||||
('required', models.BooleanField(default=False, help_text='If true, this field is required when creating new objects or editing an existing object.')),
|
||||
('default', models.CharField(blank=True, help_text='Default value for the field. Use "true" or "false" for booleans.', max_length=100)),
|
||||
('weight', models.PositiveSmallIntegerField(default=100, help_text='Fields with higher weights appear lower in a form.')),
|
||||
('obj_type', models.ManyToManyField(help_text='The object(s) to which this field applies.', related_name='custom_fields', to='contenttypes.ContentType', verbose_name='Object(s)')),
|
||||
('filter_logic', models.PositiveSmallIntegerField(choices=[(0, 'Disabled'), (1, 'Loose'), (2, 'Exact')], default=1, help_text='Loose matches any instance of a given string; exact matches the entire field.')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['weight', 'name'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CustomFieldChoice',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('value', models.CharField(max_length=100)),
|
||||
('weight', models.PositiveSmallIntegerField(default=100, help_text='Higher weights appear lower in the list')),
|
||||
('field', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='choices', to='extras.CustomField')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['field', 'weight', 'value'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CustomFieldValue',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('obj_id', models.PositiveIntegerField()),
|
||||
('serialized_value', models.CharField(max_length=255)),
|
||||
('field', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='values', to='extras.CustomField')),
|
||||
('obj_type', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.ContentType')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['obj_type', 'obj_id'],
|
||||
},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='customfieldvalue',
|
||||
unique_together=set([('field', 'obj_type', 'obj_id')]),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='customfieldchoice',
|
||||
unique_together=set([('field', 'value')]),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ImageAttachment',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('object_id', models.PositiveIntegerField()),
|
||||
('image', models.ImageField(height_field='image_height', upload_to=extras.models.image_upload, width_field='image_width')),
|
||||
('image_height', models.PositiveSmallIntegerField()),
|
||||
('image_width', models.PositiveSmallIntegerField()),
|
||||
('name', models.CharField(blank=True, max_length=50)),
|
||||
('created', models.DateTimeField(auto_now_add=True)),
|
||||
('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['name'],
|
||||
},
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=verify_postgresql_version,
|
||||
),
|
||||
@@ -185,4 +181,85 @@ class Migration(migrations.Migration):
|
||||
'ordering': ['report'],
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='topologymap',
|
||||
name='type',
|
||||
field=models.PositiveSmallIntegerField(choices=[(1, 'Network'), (2, 'Console'), (3, 'Power')], default=1),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='customfield',
|
||||
name='filter_logic',
|
||||
field=models.PositiveSmallIntegerField(choices=[(0, 'Disabled'), (1, 'Loose'), (2, 'Exact')], default=1, help_text='Loose matches any instance of a given string; exact matches the entire field.'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='customfield',
|
||||
name='required',
|
||||
field=models.BooleanField(default=False, help_text='If true, this field is required when creating new objects or editing an existing object.'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='customfield',
|
||||
name='weight',
|
||||
field=models.PositiveSmallIntegerField(default=100, help_text='Fields with higher weights appear lower in a form.'),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=is_filterable_to_filter_logic,
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='customfield',
|
||||
name='is_filterable',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='customfield',
|
||||
name='obj_type',
|
||||
field=models.ManyToManyField(help_text='The object(s) to which this field applies.', limit_choices_to={'model__in': ('provider', 'circuit', 'site', 'rack', 'devicetype', 'device', 'aggregate', 'prefix', 'ipaddress', 'vlan', 'vrf', 'tenant', 'cluster', 'virtualmachine')}, related_name='custom_fields', to='contenttypes.ContentType', verbose_name='Object(s)'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='customfieldchoice',
|
||||
name='field',
|
||||
field=models.ForeignKey(limit_choices_to={'type': 600}, on_delete=django.db.models.deletion.CASCADE, related_name='choices', to='extras.CustomField'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='exporttemplate',
|
||||
name='content_type',
|
||||
field=models.ForeignKey(limit_choices_to={'model__in': ['provider', 'circuit', 'site', 'region', 'rack', 'rackgroup', 'manufacturer', 'devicetype', 'device', 'consoleport', 'powerport', 'interfaceconnection', 'aggregate', 'prefix', 'ipaddress', 'vlan', 'tenant', 'cluster', 'virtualmachine']}, on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Webhook',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=150, unique=True)),
|
||||
('type_create', models.BooleanField(default=False, help_text='Call this webhook when a matching object is created.')),
|
||||
('type_update', models.BooleanField(default=False, help_text='Call this webhook when a matching object is updated.')),
|
||||
('type_delete', models.BooleanField(default=False, help_text='Call this webhook when a matching object is deleted.')),
|
||||
('payload_url', models.CharField(help_text='A POST will be sent to this URL when the webhook is called.', max_length=500, verbose_name='URL')),
|
||||
('http_content_type', models.PositiveSmallIntegerField(choices=[(1, 'application/json'), (2, 'application/x-www-form-urlencoded')], default=1, verbose_name='HTTP content type')),
|
||||
('secret', models.CharField(blank=True, help_text="When provided, the request will include a 'X-Hook-Signature' header containing a HMAC hex digest of the payload body using the secret as the key. The secret is not transmitted in the request.", max_length=255)),
|
||||
('enabled', models.BooleanField(default=True)),
|
||||
('ssl_verification', models.BooleanField(default=True, help_text='Enable SSL certificate verification. Disable with caution!', verbose_name='SSL verification')),
|
||||
('obj_type', models.ManyToManyField(help_text='The object(s) to which this Webhook applies.', related_name='webhooks', to='contenttypes.ContentType', verbose_name='Object types')),
|
||||
],
|
||||
options={
|
||||
'unique_together': {('payload_url', 'type_create', 'type_update', 'type_delete')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ObjectChange',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('time', models.DateTimeField(auto_now_add=True)),
|
||||
('user_name', models.CharField(editable=False, max_length=150)),
|
||||
('request_id', models.UUIDField(editable=False)),
|
||||
('action', models.PositiveSmallIntegerField(choices=[(1, 'Created'), (2, 'Updated'), (3, 'Deleted')])),
|
||||
('changed_object_id', models.PositiveIntegerField()),
|
||||
('related_object_id', models.PositiveIntegerField(blank=True, null=True)),
|
||||
('object_repr', models.CharField(editable=False, max_length=200)),
|
||||
('object_data', django.contrib.postgres.fields.jsonb.JSONField(editable=False)),
|
||||
('changed_object_type', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.ContentType')),
|
||||
('related_object_type', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.ContentType')),
|
||||
('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='changes', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['-time'],
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,106 @@
|
||||
import django.contrib.postgres.fields.jsonb
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def set_template_language(apps, schema_editor):
|
||||
"""
|
||||
Set the language for all existing ExportTemplates to Django (Jinja2 is the default for new ExportTemplates).
|
||||
"""
|
||||
ExportTemplate = apps.get_model('extras', 'ExportTemplate')
|
||||
ExportTemplate.objects.update(template_language=10)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
replaces = [('extras', '0014_configcontexts'), ('extras', '0015_remove_useraction'), ('extras', '0016_exporttemplate_add_cable'), ('extras', '0017_exporttemplate_mime_type_length'), ('extras', '0018_exporttemplate_add_jinja2'), ('extras', '0019_tag_taggeditem')]
|
||||
|
||||
dependencies = [
|
||||
('extras', '0013_objectchange'),
|
||||
('tenancy', '0005_change_logging'),
|
||||
('dcim', '0061_platform_napalm_args'),
|
||||
('contenttypes', '0002_remove_content_type_name'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ConfigContext',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=100, unique=True)),
|
||||
('weight', models.PositiveSmallIntegerField(default=1000)),
|
||||
('description', models.CharField(blank=True, max_length=100)),
|
||||
('is_active', models.BooleanField(default=True)),
|
||||
('data', django.contrib.postgres.fields.jsonb.JSONField()),
|
||||
('platforms', models.ManyToManyField(blank=True, related_name='_configcontext_platforms_+', to='dcim.Platform')),
|
||||
('regions', models.ManyToManyField(blank=True, related_name='_configcontext_regions_+', to='dcim.Region')),
|
||||
('roles', models.ManyToManyField(blank=True, related_name='_configcontext_roles_+', to='dcim.DeviceRole')),
|
||||
('sites', models.ManyToManyField(blank=True, related_name='_configcontext_sites_+', to='dcim.Site')),
|
||||
('tenant_groups', models.ManyToManyField(blank=True, related_name='_configcontext_tenant_groups_+', to='tenancy.TenantGroup')),
|
||||
('tenants', models.ManyToManyField(blank=True, related_name='_configcontext_tenants_+', to='tenancy.Tenant')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['weight', 'name'],
|
||||
},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='customfield',
|
||||
name='obj_type',
|
||||
field=models.ManyToManyField(help_text='The object(s) to which this field applies.', limit_choices_to={'model__in': ('provider', 'circuit', 'site', 'rack', 'devicetype', 'device', 'aggregate', 'prefix', 'ipaddress', 'vlan', 'vrf', 'service', 'secret', 'tenant', 'cluster', 'virtualmachine')}, related_name='custom_fields', to='contenttypes.ContentType', verbose_name='Object(s)'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='exporttemplate',
|
||||
name='content_type',
|
||||
field=models.ForeignKey(limit_choices_to={'model__in': ['provider', 'circuit', 'site', 'region', 'rack', 'rackgroup', 'manufacturer', 'devicetype', 'device', 'consoleport', 'powerport', 'interfaceconnection', 'virtualchassis', 'aggregate', 'prefix', 'ipaddress', 'vlan', 'vrf', 'service', 'secret', 'tenant', 'cluster', 'virtualmachine']}, on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='webhook',
|
||||
name='obj_type',
|
||||
field=models.ManyToManyField(help_text='The object(s) to which this Webhook applies.', limit_choices_to={'model__in': ('provider', 'circuit', 'site', 'rack', 'devicetype', 'device', 'virtualchassis', 'consoleport', 'consoleserverport', 'powerport', 'poweroutlet', 'interface', 'devicebay', 'inventoryitem', 'aggregate', 'prefix', 'ipaddress', 'vlan', 'vrf', 'service', 'secret', 'tenant', 'cluster', 'virtualmachine')}, related_name='webhooks', to='contenttypes.ContentType', verbose_name='Object types'),
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='UserAction',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='exporttemplate',
|
||||
name='content_type',
|
||||
field=models.ForeignKey(limit_choices_to={'model__in': ['provider', 'circuit', 'site', 'region', 'rack', 'rackgroup', 'manufacturer', 'devicetype', 'device', 'consoleport', 'powerport', 'interface', 'cable', 'virtualchassis', 'aggregate', 'prefix', 'ipaddress', 'vlan', 'vrf', 'service', 'secret', 'tenant', 'cluster', 'virtualmachine']}, on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='exporttemplate',
|
||||
name='mime_type',
|
||||
field=models.CharField(blank=True, max_length=50),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='exporttemplate',
|
||||
name='template_language',
|
||||
field=models.PositiveSmallIntegerField(default=20),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=set_template_language,
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Tag',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=100, unique=True)),
|
||||
('slug', models.SlugField(max_length=100, unique=True)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='TaggedItem',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False)),
|
||||
('object_id', models.IntegerField(db_index=True)),
|
||||
('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='extras_taggeditem_tagged_items', to='contenttypes.ContentType')),
|
||||
('tag', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='extras_taggeditem_items', to='extras.Tag')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
'index_together': {('content_type', 'object_id')},
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,93 @@
|
||||
from django.db import migrations, models
|
||||
|
||||
import utilities.fields
|
||||
|
||||
|
||||
def copy_tags(apps, schema_editor):
|
||||
"""
|
||||
Copy data from taggit_tag to extras_tag
|
||||
"""
|
||||
TaggitTag = apps.get_model('taggit', 'Tag')
|
||||
ExtrasTag = apps.get_model('extras', 'Tag')
|
||||
|
||||
tags_values = TaggitTag.objects.all().values('id', 'name', 'slug')
|
||||
tags = [ExtrasTag(**tag) for tag in tags_values]
|
||||
ExtrasTag.objects.bulk_create(tags)
|
||||
|
||||
|
||||
def copy_taggeditems(apps, schema_editor):
|
||||
"""
|
||||
Copy data from taggit_taggeditem to extras_taggeditem
|
||||
"""
|
||||
TaggitTaggedItem = apps.get_model('taggit', 'TaggedItem')
|
||||
ExtrasTaggedItem = apps.get_model('extras', 'TaggedItem')
|
||||
|
||||
tagged_items_values = TaggitTaggedItem.objects.all().values('id', 'object_id', 'content_type_id', 'tag_id')
|
||||
tagged_items = [ExtrasTaggedItem(**tagged_item) for tagged_item in tagged_items_values]
|
||||
ExtrasTaggedItem.objects.bulk_create(tagged_items)
|
||||
|
||||
|
||||
def delete_taggit_taggeditems(apps, schema_editor):
|
||||
"""
|
||||
Delete all TaggedItem instances from taggit_taggeditem
|
||||
"""
|
||||
TaggitTaggedItem = apps.get_model('taggit', 'TaggedItem')
|
||||
TaggitTaggedItem.objects.all().delete()
|
||||
|
||||
|
||||
def delete_taggit_tags(apps, schema_editor):
|
||||
"""
|
||||
Delete all Tag instances from taggit_tag
|
||||
"""
|
||||
TaggitTag = apps.get_model('taggit', 'Tag')
|
||||
TaggitTag.objects.all().delete()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
replaces = [('extras', '0020_tag_data'), ('extras', '0021_add_color_comments_changelog_to_tag')]
|
||||
|
||||
dependencies = [
|
||||
('extras', '0019_tag_taggeditem'),
|
||||
('virtualization', '0009_custom_tag_models'),
|
||||
('tenancy', '0006_custom_tag_models'),
|
||||
('secrets', '0006_custom_tag_models'),
|
||||
('dcim', '0070_custom_tag_models'),
|
||||
('ipam', '0025_custom_tag_models'),
|
||||
('circuits', '0015_custom_tag_models'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(
|
||||
code=copy_tags,
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=copy_taggeditems,
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=delete_taggit_taggeditems,
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=delete_taggit_tags,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='tag',
|
||||
name='color',
|
||||
field=utilities.fields.ColorField(default='9e9e9e', max_length=6),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='tag',
|
||||
name='comments',
|
||||
field=models.TextField(blank=True, default=''),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='tag',
|
||||
name='created',
|
||||
field=models.DateField(auto_now_add=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='tag',
|
||||
name='last_updated',
|
||||
field=models.DateTimeField(auto_now=True, null=True),
|
||||
),
|
||||
]
|
||||
@@ -22,7 +22,7 @@ class Migration(migrations.Migration):
|
||||
('group_name', models.CharField(blank=True, max_length=50)),
|
||||
('button_class', models.CharField(default='default', max_length=30)),
|
||||
('new_window', models.BooleanField()),
|
||||
('content_type', models.ForeignKey(limit_choices_to=extras.models.get_custom_link_models, on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')),
|
||||
('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['group_name', 'weight', 'name'],
|
||||
@@ -33,16 +33,16 @@ class Migration(migrations.Migration):
|
||||
migrations.AlterField(
|
||||
model_name='customfield',
|
||||
name='obj_type',
|
||||
field=models.ManyToManyField(limit_choices_to=extras.models.get_custom_field_models, related_name='custom_fields', to='contenttypes.ContentType'),
|
||||
field=models.ManyToManyField(related_name='custom_fields', to='contenttypes.ContentType'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='exporttemplate',
|
||||
name='content_type',
|
||||
field=models.ForeignKey(limit_choices_to=extras.models.get_export_template_models, on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType'),
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='webhook',
|
||||
name='obj_type',
|
||||
field=models.ManyToManyField(limit_choices_to=extras.models.get_webhook_models, related_name='webhooks', to='contenttypes.ContentType'),
|
||||
field=models.ManyToManyField(related_name='webhooks', to='contenttypes.ContentType'),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
import django.contrib.postgres.fields.jsonb
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
import extras.models
|
||||
|
||||
CUSTOMFIELD_TYPE_CHOICES = (
|
||||
(100, 'text'),
|
||||
(200, 'integer'),
|
||||
(300, 'boolean'),
|
||||
(400, 'date'),
|
||||
(500, 'url'),
|
||||
(600, 'select')
|
||||
)
|
||||
|
||||
CUSTOMFIELD_FILTER_LOGIC_CHOICES = (
|
||||
(0, 'disabled'),
|
||||
(1, 'integer'),
|
||||
(2, 'exact'),
|
||||
)
|
||||
|
||||
OBJECTCHANGE_ACTION_CHOICES = (
|
||||
(1, 'create'),
|
||||
(2, 'update'),
|
||||
(3, 'delete'),
|
||||
)
|
||||
|
||||
EXPORTTEMPLATE_LANGUAGE_CHOICES = (
|
||||
(10, 'django'),
|
||||
(20, 'jinja2'),
|
||||
)
|
||||
|
||||
WEBHOOK_CONTENTTYPE_CHOICES = (
|
||||
(1, 'application/json'),
|
||||
(2, 'application/x-www-form-urlencoded'),
|
||||
)
|
||||
|
||||
GRAPH_TYPE_CHOICES = (
|
||||
(100, 'dcim', 'interface'),
|
||||
(150, 'dcim', 'device'),
|
||||
(200, 'circuits', 'provider'),
|
||||
(300, 'dcim', 'site'),
|
||||
)
|
||||
|
||||
|
||||
def customfield_type_to_slug(apps, schema_editor):
|
||||
CustomField = apps.get_model('extras', 'CustomField')
|
||||
for id, slug in CUSTOMFIELD_TYPE_CHOICES:
|
||||
CustomField.objects.filter(type=str(id)).update(type=slug)
|
||||
|
||||
|
||||
def customfield_filter_logic_to_slug(apps, schema_editor):
|
||||
CustomField = apps.get_model('extras', 'CustomField')
|
||||
for id, slug in CUSTOMFIELD_FILTER_LOGIC_CHOICES:
|
||||
CustomField.objects.filter(filter_logic=str(id)).update(filter_logic=slug)
|
||||
|
||||
|
||||
def objectchange_action_to_slug(apps, schema_editor):
|
||||
ObjectChange = apps.get_model('extras', 'ObjectChange')
|
||||
for id, slug in OBJECTCHANGE_ACTION_CHOICES:
|
||||
ObjectChange.objects.filter(action=str(id)).update(action=slug)
|
||||
|
||||
|
||||
def exporttemplate_language_to_slug(apps, schema_editor):
|
||||
ExportTemplate = apps.get_model('extras', 'ExportTemplate')
|
||||
for id, slug in EXPORTTEMPLATE_LANGUAGE_CHOICES:
|
||||
ExportTemplate.objects.filter(template_language=str(id)).update(template_language=slug)
|
||||
|
||||
|
||||
def webhook_contenttype_to_slug(apps, schema_editor):
|
||||
Webhook = apps.get_model('extras', 'Webhook')
|
||||
for id, slug in WEBHOOK_CONTENTTYPE_CHOICES:
|
||||
Webhook.objects.filter(http_content_type=str(id)).update(http_content_type=slug)
|
||||
|
||||
|
||||
def graph_type_to_fk(apps, schema_editor):
|
||||
Graph = apps.get_model('extras', 'Graph')
|
||||
ContentType = apps.get_model('contenttypes', 'ContentType')
|
||||
|
||||
# On a new installation (and during tests) content types might not yet exist. So, we only perform the bulk
|
||||
# updates if a Graph has been created, which implies that we're working with a populated database.
|
||||
if Graph.objects.exists():
|
||||
for id, app_label, model in GRAPH_TYPE_CHOICES:
|
||||
content_type = ContentType.objects.get(app_label=app_label, model=model)
|
||||
Graph.objects.filter(type=id).update(type=content_type.pk)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
replaces = [('extras', '0022_custom_links'), ('extras', '0023_fix_tag_sequences'), ('extras', '0024_scripts'), ('extras', '0025_objectchange_time_index'), ('extras', '0026_webhook_ca_file_path'), ('extras', '0027_webhook_additional_headers'), ('extras', '0028_remove_topology_maps'), ('extras', '0029_3569_customfield_fields'), ('extras', '0030_3569_objectchange_fields'), ('extras', '0031_3569_exporttemplate_fields'), ('extras', '0032_3569_webhook_fields'), ('extras', '0033_graph_type_template_language'), ('extras', '0034_configcontext_tags')]
|
||||
|
||||
dependencies = [
|
||||
('extras', '0021_add_color_comments_changelog_to_tag'),
|
||||
('contenttypes', '0002_remove_content_type_name'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='CustomLink',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False)),
|
||||
('name', models.CharField(max_length=100, unique=True)),
|
||||
('text', models.CharField(max_length=500)),
|
||||
('url', models.CharField(max_length=500)),
|
||||
('weight', models.PositiveSmallIntegerField(default=100)),
|
||||
('group_name', models.CharField(blank=True, max_length=50)),
|
||||
('button_class', models.CharField(default='default', max_length=30)),
|
||||
('new_window', models.BooleanField()),
|
||||
('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['group_name', 'weight', 'name'],
|
||||
},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='customfield',
|
||||
name='obj_type',
|
||||
field=models.ManyToManyField(related_name='custom_fields', to='contenttypes.ContentType'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='exporttemplate',
|
||||
name='content_type',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='webhook',
|
||||
name='obj_type',
|
||||
field=models.ManyToManyField(related_name='webhooks', to='contenttypes.ContentType'),
|
||||
),
|
||||
migrations.RunSQL(
|
||||
sql="SELECT setval('extras_tag_id_seq', (SELECT id FROM extras_tag ORDER BY id DESC LIMIT 1) + 1)",
|
||||
),
|
||||
migrations.RunSQL(
|
||||
sql="SELECT setval('extras_taggeditem_id_seq', (SELECT id FROM extras_taggeditem ORDER BY id DESC LIMIT 1) + 1)",
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Script',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False)),
|
||||
],
|
||||
options={
|
||||
'permissions': (('run_script', 'Can run script'),),
|
||||
'managed': False,
|
||||
},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='objectchange',
|
||||
name='time',
|
||||
field=models.DateTimeField(auto_now_add=True, db_index=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='webhook',
|
||||
name='ca_file_path',
|
||||
field=models.CharField(blank=True, max_length=4096, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='webhook',
|
||||
name='additional_headers',
|
||||
field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True),
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='TopologyMap',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='customfield',
|
||||
name='type',
|
||||
field=models.CharField(default='text', max_length=50),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=customfield_type_to_slug,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='customfieldchoice',
|
||||
name='field',
|
||||
field=models.ForeignKey(limit_choices_to={'type': 'select'}, on_delete=django.db.models.deletion.CASCADE, related_name='choices', to='extras.CustomField'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='customfield',
|
||||
name='filter_logic',
|
||||
field=models.CharField(default='loose', max_length=50),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=customfield_filter_logic_to_slug,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='objectchange',
|
||||
name='action',
|
||||
field=models.CharField(max_length=50),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=objectchange_action_to_slug,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='exporttemplate',
|
||||
name='template_language',
|
||||
field=models.CharField(default='jinja2', max_length=50),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=exporttemplate_language_to_slug,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='webhook',
|
||||
name='http_content_type',
|
||||
field=models.CharField(default='application/json', max_length=50),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=webhook_contenttype_to_slug,
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=graph_type_to_fk,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='graph',
|
||||
name='type',
|
||||
field=models.ForeignKey(limit_choices_to={'model__in': ['provider', 'device', 'interface', 'site']}, on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='graph',
|
||||
name='template_language',
|
||||
field=models.CharField(default='jinja2', max_length=50),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='configcontext',
|
||||
name='tags',
|
||||
field=models.ManyToManyField(blank=True, related_name='_configcontext_tags_+', to='extras.Tag'),
|
||||
),
|
||||
]
|
||||
@@ -38,9 +38,22 @@ class Migration(migrations.Migration):
|
||||
model_name='graph',
|
||||
name='type',
|
||||
field=models.ForeignKey(
|
||||
limit_choices_to={'model__in': ['device', 'interface', 'provider', 'site']},
|
||||
limit_choices_to={'model__in': ['provider', 'device', 'interface', 'site']},
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to='contenttypes.ContentType'
|
||||
),
|
||||
),
|
||||
|
||||
# Add the template_language field with an initial default of Django to preserve current behavior. Then,
|
||||
# alter the field to set the default for any *new* Graphs to Jinja2.
|
||||
migrations.AddField(
|
||||
model_name='graph',
|
||||
name='template_language',
|
||||
field=models.CharField(default='django', max_length=50),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='graph',
|
||||
name='template_language',
|
||||
field=models.CharField(default='jinja2', max_length=50),
|
||||
),
|
||||
]
|
||||
@@ -6,7 +6,7 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('extras', '0033_graph_type_to_fk'),
|
||||
('extras', '0033_graph_type_template_language'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
|
||||
29
netbox/extras/migrations/0035_deterministic_ordering.py
Normal file
29
netbox/extras/migrations/0035_deterministic_ordering.py
Normal file
@@ -0,0 +1,29 @@
|
||||
# Generated by Django 2.2.8 on 2020-01-15 18:10
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('extras', '0034_configcontext_tags'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='customfieldvalue',
|
||||
options={'ordering': ('obj_type', 'obj_id', 'pk')},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='graph',
|
||||
options={'ordering': ('type', 'weight', 'name', 'pk')},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='imageattachment',
|
||||
options={'ordering': ('name', 'pk')},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='webhook',
|
||||
options={'ordering': ('name',)},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,39 @@
|
||||
# Generated by Django 2.2.8 on 2020-01-15 21:18
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('extras', '0035_deterministic_ordering'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='customfield',
|
||||
name='obj_type',
|
||||
field=models.ManyToManyField(limit_choices_to=models.Q(models.Q(models.Q(('app_label', 'circuits'), ('model__in', ['circuit', 'provider'])), models.Q(('app_label', 'dcim'), ('model__in', ['device', 'devicetype', 'powerfeed', 'rack', 'site'])), models.Q(('app_label', 'ipam'), ('model__in', ['aggregate', 'ipaddress', 'prefix', 'service', 'vlan', 'vrf'])), models.Q(('app_label', 'secrets'), ('model__in', ['secret'])), models.Q(('app_label', 'tenancy'), ('model__in', ['tenant'])), models.Q(('app_label', 'virtualization'), ('model__in', ['cluster', 'virtualmachine'])), _connector='OR')), related_name='custom_fields', to='contenttypes.ContentType'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='customlink',
|
||||
name='content_type',
|
||||
field=models.ForeignKey(limit_choices_to=models.Q(models.Q(models.Q(('app_label', 'circuits'), ('model__in', ['circuit', 'provider'])), models.Q(('app_label', 'dcim'), ('model__in', ['cable', 'device', 'devicetype', 'powerpanel', 'powerfeed', 'rack', 'site'])), models.Q(('app_label', 'ipam'), ('model__in', ['aggregate', 'ipaddress', 'prefix', 'service', 'vlan', 'vrf'])), models.Q(('app_label', 'secrets'), ('model__in', ['secret'])), models.Q(('app_label', 'tenancy'), ('model__in', ['tenant'])), models.Q(('app_label', 'virtualization'), ('model__in', ['cluster', 'virtualmachine'])), _connector='OR')), on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='exporttemplate',
|
||||
name='content_type',
|
||||
field=models.ForeignKey(limit_choices_to=models.Q(models.Q(models.Q(('app_label', 'circuits'), ('model__in', ['circuit', 'provider'])), models.Q(('app_label', 'dcim'), ('model__in', ['cable', 'consoleport', 'device', 'devicetype', 'interface', 'inventoryitem', 'manufacturer', 'powerpanel', 'powerport', 'powerfeed', 'rack', 'rackgroup', 'region', 'site', 'virtualchassis'])), models.Q(('app_label', 'ipam'), ('model__in', ['aggregate', 'ipaddress', 'prefix', 'service', 'vlan', 'vrf'])), models.Q(('app_label', 'secrets'), ('model__in', ['secret'])), models.Q(('app_label', 'tenancy'), ('model__in', ['tenant'])), models.Q(('app_label', 'virtualization'), ('model__in', ['cluster', 'virtualmachine'])), _connector='OR')), on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='graph',
|
||||
name='type',
|
||||
field=models.ForeignKey(limit_choices_to=models.Q(models.Q(models.Q(('app_label', 'circuits'), ('model__in', ['provider'])), models.Q(('app_label', 'dcim'), ('model__in', ['device', 'interface', 'site'])), _connector='OR')), on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='webhook',
|
||||
name='obj_type',
|
||||
field=models.ManyToManyField(limit_choices_to=models.Q(models.Q(models.Q(('app_label', 'circuits'), ('model__in', ['circuit', 'provider'])), models.Q(('app_label', 'dcim'), ('model__in', ['cable', 'consoleport', 'consoleserverport', 'device', 'devicebay', 'devicetype', 'frontport', 'interface', 'inventoryitem', 'manufacturer', 'poweroutlet', 'powerpanel', 'powerport', 'powerfeed', 'rack', 'rearport', 'region', 'site', 'virtualchassis'])), models.Q(('app_label', 'ipam'), ('model__in', ['aggregate', 'ipaddress', 'prefix', 'service', 'vlan', 'vrf'])), models.Q(('app_label', 'secrets'), ('model__in', ['secret'])), models.Q(('app_label', 'tenancy'), ('model__in', ['tenant'])), models.Q(('app_label', 'virtualization'), ('model__in', ['cluster', 'virtualmachine'])), _connector='OR')), related_name='webhooks', to='contenttypes.ContentType'),
|
||||
),
|
||||
]
|
||||
@@ -10,24 +10,39 @@ from django.db import models
|
||||
from django.http import HttpResponse
|
||||
from django.template import Template, Context
|
||||
from django.urls import reverse
|
||||
from jinja2 import Environment
|
||||
from taggit.models import TagBase, GenericTaggedItemBase
|
||||
|
||||
from utilities.fields import ColorField
|
||||
from utilities.utils import deepmerge, model_names_to_filter_dict
|
||||
from utilities.utils import deepmerge, render_jinja2
|
||||
from .choices import *
|
||||
from .constants import *
|
||||
from .querysets import ConfigContextQuerySet
|
||||
|
||||
|
||||
__all__ = (
|
||||
'ConfigContext',
|
||||
'ConfigContextModel',
|
||||
'CustomField',
|
||||
'CustomFieldChoice',
|
||||
'CustomFieldModel',
|
||||
'CustomFieldValue',
|
||||
'CustomLink',
|
||||
'ExportTemplate',
|
||||
'Graph',
|
||||
'ImageAttachment',
|
||||
'ObjectChange',
|
||||
'ReportResult',
|
||||
'Script',
|
||||
'Tag',
|
||||
'TaggedItem',
|
||||
'Webhook',
|
||||
)
|
||||
|
||||
|
||||
#
|
||||
# Webhooks
|
||||
#
|
||||
|
||||
def get_webhook_models():
|
||||
return model_names_to_filter_dict(WEBHOOK_MODELS)
|
||||
|
||||
|
||||
class Webhook(models.Model):
|
||||
"""
|
||||
A Webhook defines a request that will be sent to a remote application when an object is created, updated, and/or
|
||||
@@ -39,7 +54,7 @@ class Webhook(models.Model):
|
||||
to=ContentType,
|
||||
related_name='webhooks',
|
||||
verbose_name='Object types',
|
||||
limit_choices_to=get_webhook_models,
|
||||
limit_choices_to=WEBHOOK_MODELS,
|
||||
help_text="The object(s) to which this Webhook applies."
|
||||
)
|
||||
name = models.CharField(
|
||||
@@ -101,6 +116,7 @@ class Webhook(models.Model):
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ('name',)
|
||||
unique_together = ('payload_url', 'type_create', 'type_update', 'type_delete',)
|
||||
|
||||
def __str__(self):
|
||||
@@ -172,16 +188,12 @@ class CustomFieldModel(models.Model):
|
||||
return OrderedDict([(field, None) for field in fields])
|
||||
|
||||
|
||||
def get_custom_field_models():
|
||||
return model_names_to_filter_dict(CUSTOMFIELD_MODELS)
|
||||
|
||||
|
||||
class CustomField(models.Model):
|
||||
obj_type = models.ManyToManyField(
|
||||
to=ContentType,
|
||||
related_name='custom_fields',
|
||||
verbose_name='Object(s)',
|
||||
limit_choices_to=get_custom_field_models,
|
||||
limit_choices_to=CUSTOMFIELD_MODELS,
|
||||
help_text='The object(s) to which this field applies.'
|
||||
)
|
||||
type = models.CharField(
|
||||
@@ -289,8 +301,8 @@ class CustomFieldValue(models.Model):
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ['obj_type', 'obj_id']
|
||||
unique_together = ['field', 'obj_type', 'obj_id']
|
||||
ordering = ('obj_type', 'obj_id', 'pk') # (obj_type, obj_id) may be non-unique
|
||||
unique_together = ('field', 'obj_type', 'obj_id')
|
||||
|
||||
def __str__(self):
|
||||
return '{} {}'.format(self.obj, self.field)
|
||||
@@ -351,10 +363,6 @@ class CustomFieldChoice(models.Model):
|
||||
# Custom links
|
||||
#
|
||||
|
||||
def get_custom_link_models():
|
||||
return model_names_to_filter_dict(CUSTOMLINK_MODELS)
|
||||
|
||||
|
||||
class CustomLink(models.Model):
|
||||
"""
|
||||
A custom link to an external representation of a NetBox object. The link text and URL fields accept Jinja2 template
|
||||
@@ -363,7 +371,7 @@ class CustomLink(models.Model):
|
||||
content_type = models.ForeignKey(
|
||||
to=ContentType,
|
||||
on_delete=models.CASCADE,
|
||||
limit_choices_to=get_custom_link_models
|
||||
limit_choices_to=CUSTOMLINK_MODELS
|
||||
)
|
||||
name = models.CharField(
|
||||
max_length=100,
|
||||
@@ -411,9 +419,7 @@ class Graph(models.Model):
|
||||
type = models.ForeignKey(
|
||||
to=ContentType,
|
||||
on_delete=models.CASCADE,
|
||||
limit_choices_to={
|
||||
'model__in': ['device', 'interface', 'provider', 'site']
|
||||
}
|
||||
limit_choices_to=GRAPH_MODELS
|
||||
)
|
||||
weight = models.PositiveSmallIntegerField(
|
||||
default=1000
|
||||
@@ -422,6 +428,11 @@ class Graph(models.Model):
|
||||
max_length=100,
|
||||
verbose_name='Name'
|
||||
)
|
||||
template_language = models.CharField(
|
||||
max_length=50,
|
||||
choices=TemplateLanguageChoices,
|
||||
default=TemplateLanguageChoices.LANGUAGE_JINJA2
|
||||
)
|
||||
source = models.CharField(
|
||||
max_length=500,
|
||||
verbose_name='Source URL'
|
||||
@@ -432,35 +443,46 @@ class Graph(models.Model):
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ['type', 'weight', 'name']
|
||||
ordering = ('type', 'weight', 'name', 'pk') # (type, weight, name) may be non-unique
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def embed_url(self, obj):
|
||||
template = Template(self.source)
|
||||
return template.render(Context({'obj': obj}))
|
||||
context = {'obj': obj}
|
||||
|
||||
# TODO: Remove in v2.8
|
||||
if self.template_language == TemplateLanguageChoices.LANGUAGE_DJANGO:
|
||||
template = Template(self.source)
|
||||
return template.render(Context(context))
|
||||
|
||||
elif self.template_language == TemplateLanguageChoices.LANGUAGE_JINJA2:
|
||||
return render_jinja2(self.source, context)
|
||||
|
||||
def embed_link(self, obj):
|
||||
if self.link is None:
|
||||
return ''
|
||||
template = Template(self.link)
|
||||
return template.render(Context({'obj': obj}))
|
||||
|
||||
context = {'obj': obj}
|
||||
|
||||
# TODO: Remove in v2.8
|
||||
if self.template_language == TemplateLanguageChoices.LANGUAGE_DJANGO:
|
||||
template = Template(self.link)
|
||||
return template.render(Context(context))
|
||||
|
||||
elif self.template_language == TemplateLanguageChoices.LANGUAGE_JINJA2:
|
||||
return render_jinja2(self.link, context)
|
||||
|
||||
|
||||
#
|
||||
# Export templates
|
||||
#
|
||||
|
||||
def get_export_template_models():
|
||||
return model_names_to_filter_dict(EXPORTTEMPLATE_MODELS)
|
||||
|
||||
|
||||
class ExportTemplate(models.Model):
|
||||
content_type = models.ForeignKey(
|
||||
to=ContentType,
|
||||
on_delete=models.CASCADE,
|
||||
limit_choices_to=get_export_template_models
|
||||
limit_choices_to=EXPORTTEMPLATE_MODELS
|
||||
)
|
||||
name = models.CharField(
|
||||
max_length=100
|
||||
@@ -471,8 +493,8 @@ class ExportTemplate(models.Model):
|
||||
)
|
||||
template_language = models.CharField(
|
||||
max_length=50,
|
||||
choices=ExportTemplateLanguageChoices,
|
||||
default=ExportTemplateLanguageChoices.LANGUAGE_JINJA2
|
||||
choices=TemplateLanguageChoices,
|
||||
default=TemplateLanguageChoices.LANGUAGE_JINJA2
|
||||
)
|
||||
template_code = models.TextField(
|
||||
help_text='The list of objects being exported is passed as a context variable named <code>queryset</code>.'
|
||||
@@ -506,13 +528,12 @@ class ExportTemplate(models.Model):
|
||||
'queryset': queryset
|
||||
}
|
||||
|
||||
if self.template_language == ExportTemplateLanguageChoices.LANGUAGE_DJANGO:
|
||||
if self.template_language == TemplateLanguageChoices.LANGUAGE_DJANGO:
|
||||
template = Template(self.template_code)
|
||||
output = template.render(Context(context))
|
||||
|
||||
elif self.template_language == ExportTemplateLanguageChoices.LANGUAGE_JINJA2:
|
||||
template = Environment().from_string(source=self.template_code)
|
||||
output = template.render(**context)
|
||||
elif self.template_language == TemplateLanguageChoices.LANGUAGE_JINJA2:
|
||||
output = render_jinja2(self.template_code, context)
|
||||
|
||||
else:
|
||||
return None
|
||||
@@ -587,7 +608,7 @@ class ImageAttachment(models.Model):
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
ordering = ('name', 'pk') # name may be non-unique
|
||||
|
||||
def __str__(self):
|
||||
if self.name:
|
||||
@@ -792,6 +813,13 @@ class ReportResult(models.Model):
|
||||
class Meta:
|
||||
ordering = ['report']
|
||||
|
||||
def __str__(self):
|
||||
return "{} {} at {}".format(
|
||||
self.report,
|
||||
"passed" if not self.failed else "failed",
|
||||
self.created
|
||||
)
|
||||
|
||||
|
||||
#
|
||||
# Change logging
|
||||
|
||||
@@ -235,6 +235,9 @@ class BaseScript:
|
||||
# Initiate the log
|
||||
self.log = []
|
||||
|
||||
# Declare the placeholder for the current request
|
||||
self.request = None
|
||||
|
||||
# Grab some info about the script
|
||||
self.filename = inspect.getfile(self.__class__)
|
||||
self.source = inspect.getsource(self.__class__)
|
||||
@@ -265,12 +268,12 @@ class BaseScript:
|
||||
def run(self, data):
|
||||
raise NotImplementedError("The script must define a run() method.")
|
||||
|
||||
def as_form(self, data=None, files=None):
|
||||
def as_form(self, data=None, files=None, initial=None):
|
||||
"""
|
||||
Return a Django form suitable for populating the context data required to run this Script.
|
||||
"""
|
||||
vars = self._get_vars()
|
||||
form = ScriptForm(vars, data, files, commit_default=getattr(self.Meta, 'commit_default', True))
|
||||
form = ScriptForm(vars, data, files, initial=initial, commit_default=getattr(self.Meta, 'commit_default', True))
|
||||
|
||||
return form
|
||||
|
||||
@@ -342,7 +345,7 @@ def is_variable(obj):
|
||||
return isinstance(obj, ScriptVariable)
|
||||
|
||||
|
||||
def run_script(script, data, files, commit=True):
|
||||
def run_script(script, data, request, commit=True):
|
||||
"""
|
||||
A wrapper for calling Script.run(). This performs error handling and provides a hook for committing changes. It
|
||||
exists outside of the Script class to ensure it cannot be overridden by a script author.
|
||||
@@ -352,9 +355,13 @@ def run_script(script, data, files, commit=True):
|
||||
end_time = None
|
||||
|
||||
# Add files to form data
|
||||
files = request.FILES
|
||||
for field_name, fileobj in files.items():
|
||||
data[field_name] = fileobj
|
||||
|
||||
# Add the current request as a property of the script
|
||||
script.request = request
|
||||
|
||||
try:
|
||||
with transaction.atomic():
|
||||
start_time = time.time()
|
||||
|
||||
@@ -3,9 +3,9 @@ from collections import OrderedDict
|
||||
from django import template
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.utils.safestring import mark_safe
|
||||
from jinja2 import Environment
|
||||
|
||||
from extras.models import CustomLink
|
||||
from utilities.utils import render_jinja2
|
||||
|
||||
|
||||
register = template.Library()
|
||||
@@ -46,12 +46,17 @@ def custom_links(obj):
|
||||
|
||||
# Add non-grouped links
|
||||
else:
|
||||
text_rendered = Environment().from_string(source=cl.text).render(**context)
|
||||
if text_rendered:
|
||||
link_target = ' target="_blank"' if cl.new_window else ''
|
||||
template_code += LINK_BUTTON.format(
|
||||
cl.url, link_target, cl.button_class, text_rendered
|
||||
)
|
||||
try:
|
||||
text_rendered = render_jinja2(cl.text, context)
|
||||
if text_rendered:
|
||||
link_rendered = render_jinja2(cl.url, context)
|
||||
link_target = ' target="_blank"' if cl.new_window else ''
|
||||
template_code += LINK_BUTTON.format(
|
||||
link_rendered, link_target, cl.button_class, text_rendered
|
||||
)
|
||||
except Exception as e:
|
||||
template_code += '<a class="btn btn-sm btn-default" disabled="disabled" title="{}">' \
|
||||
'<i class="fa fa-warning"></i> {}</a>\n'.format(e, cl.name)
|
||||
|
||||
# Add grouped links to template
|
||||
for group, links in group_names.items():
|
||||
@@ -59,11 +64,18 @@ def custom_links(obj):
|
||||
links_rendered = []
|
||||
|
||||
for cl in links:
|
||||
text_rendered = Environment().from_string(source=cl.text).render(**context)
|
||||
if text_rendered:
|
||||
link_target = ' target="_blank"' if cl.new_window else ''
|
||||
try:
|
||||
text_rendered = render_jinja2(cl.text, context)
|
||||
if text_rendered:
|
||||
link_target = ' target="_blank"' if cl.new_window else ''
|
||||
link_rendered = render_jinja2(cl.url, context)
|
||||
links_rendered.append(
|
||||
GROUP_LINK.format(link_rendered, link_target, text_rendered)
|
||||
)
|
||||
except Exception as e:
|
||||
links_rendered.append(
|
||||
GROUP_LINK.format(cl.url, link_target, cl.text)
|
||||
'<li><a disabled="disabled" title="{}"><span class="text-muted">'
|
||||
'<i class="fa fa-warning"></i> {}</span></a></li>'.format(e, cl.name)
|
||||
)
|
||||
|
||||
if links_rendered:
|
||||
@@ -71,7 +83,4 @@ def custom_links(obj):
|
||||
links[0].button_class, group, ''.join(links_rendered)
|
||||
)
|
||||
|
||||
# Render template
|
||||
rendered = Environment().from_string(source=template_code).render(**context)
|
||||
|
||||
return mark_safe(rendered)
|
||||
return mark_safe(template_code)
|
||||
|
||||
@@ -1,13 +1,49 @@
|
||||
import datetime
|
||||
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
from rest_framework import status
|
||||
|
||||
from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Platform, Region, Site
|
||||
from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Platform, Rack, RackGroup, RackRole, Region, Site
|
||||
from extras.api.views import ScriptViewSet
|
||||
from extras.choices import *
|
||||
from extras.constants import GRAPH_MODELS
|
||||
from extras.models import ConfigContext, Graph, ExportTemplate, Tag
|
||||
from extras.scripts import BooleanVar, IntegerVar, Script, StringVar
|
||||
from tenancy.models import Tenant, TenantGroup
|
||||
from utilities.testing import APITestCase
|
||||
from utilities.testing import APITestCase, choices_to_dict
|
||||
|
||||
|
||||
class AppTest(APITestCase):
|
||||
|
||||
def test_root(self):
|
||||
|
||||
url = reverse('extras-api:api-root')
|
||||
response = self.client.get('{}?format=api'.format(url), **self.header)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_choices(self):
|
||||
|
||||
url = reverse('extras-api:field-choice-list')
|
||||
response = self.client.get(url, **self.header)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
# ExportTemplate
|
||||
self.assertEqual(choices_to_dict(response.data.get('export-template:template_language')), TemplateLanguageChoices.as_dict())
|
||||
|
||||
# Graph
|
||||
content_types = ContentType.objects.filter(GRAPH_MODELS)
|
||||
graph_type_choices = {
|
||||
"{}.{}".format(ct.app_label, ct.model): ct.name for ct in content_types
|
||||
}
|
||||
self.assertEqual(choices_to_dict(response.data.get('graph:type')), graph_type_choices)
|
||||
self.assertEqual(choices_to_dict(response.data.get('graph:template_language')), TemplateLanguageChoices.as_dict())
|
||||
|
||||
# ObjectChange
|
||||
self.assertEqual(choices_to_dict(response.data.get('object-change:action')), ObjectChangeActionChoices.as_dict())
|
||||
|
||||
|
||||
class GraphTest(APITestCase):
|
||||
@@ -598,3 +634,68 @@ class ScriptTest(APITestCase):
|
||||
self.assertEqual(response.data['log'][2]['status'], 'failure')
|
||||
self.assertEqual(response.data['log'][2]['message'], script_data['var3'])
|
||||
self.assertEqual(response.data['output'], 'Script complete')
|
||||
|
||||
|
||||
class CreatedUpdatedFilterTest(APITestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
super().setUp()
|
||||
|
||||
self.site1 = Site.objects.create(name='Test Site 1', slug='test-site-1')
|
||||
self.rackgroup1 = RackGroup.objects.create(site=self.site1, name='Test Rack Group 1', slug='test-rack-group-1')
|
||||
self.rackrole1 = RackRole.objects.create(name='Test Rack Role 1', slug='test-rack-role-1', color='ff0000')
|
||||
self.rack1 = Rack.objects.create(
|
||||
site=self.site1, group=self.rackgroup1, role=self.rackrole1, name='Test Rack 1', u_height=42,
|
||||
)
|
||||
self.rack2 = Rack.objects.create(
|
||||
site=self.site1, group=self.rackgroup1, role=self.rackrole1, name='Test Rack 2', u_height=42,
|
||||
)
|
||||
|
||||
# change the created and last_updated of one
|
||||
Rack.objects.filter(pk=self.rack2.pk).update(
|
||||
last_updated=datetime.datetime(2001, 2, 3, 1, 2, 3, 4, tzinfo=timezone.utc),
|
||||
created=datetime.datetime(2001, 2, 3)
|
||||
)
|
||||
|
||||
def test_get_rack_created(self):
|
||||
url = reverse('dcim-api:rack-list')
|
||||
response = self.client.get('{}?created=2001-02-03'.format(url), **self.header)
|
||||
|
||||
self.assertEqual(response.data['count'], 1)
|
||||
self.assertEqual(response.data['results'][0]['id'], self.rack2.pk)
|
||||
|
||||
def test_get_rack_created_gte(self):
|
||||
url = reverse('dcim-api:rack-list')
|
||||
response = self.client.get('{}?created__gte=2001-02-04'.format(url), **self.header)
|
||||
|
||||
self.assertEqual(response.data['count'], 1)
|
||||
self.assertEqual(response.data['results'][0]['id'], self.rack1.pk)
|
||||
|
||||
def test_get_rack_created_lte(self):
|
||||
url = reverse('dcim-api:rack-list')
|
||||
response = self.client.get('{}?created__lte=2001-02-04'.format(url), **self.header)
|
||||
|
||||
self.assertEqual(response.data['count'], 1)
|
||||
self.assertEqual(response.data['results'][0]['id'], self.rack2.pk)
|
||||
|
||||
def test_get_rack_last_updated(self):
|
||||
url = reverse('dcim-api:rack-list')
|
||||
response = self.client.get('{}?last_updated=2001-02-03%2001:02:03.000004'.format(url), **self.header)
|
||||
|
||||
self.assertEqual(response.data['count'], 1)
|
||||
self.assertEqual(response.data['results'][0]['id'], self.rack2.pk)
|
||||
|
||||
def test_get_rack_last_updated_gte(self):
|
||||
url = reverse('dcim-api:rack-list')
|
||||
response = self.client.get('{}?last_updated__gte=2001-02-04%2001:02:03.000004'.format(url), **self.header)
|
||||
|
||||
self.assertEqual(response.data['count'], 1)
|
||||
self.assertEqual(response.data['results'][0]['id'], self.rack1.pk)
|
||||
|
||||
def test_get_rack_last_updated_lte(self):
|
||||
url = reverse('dcim-api:rack-list')
|
||||
response = self.client.get('{}?last_updated__lte=2001-02-04%2001:02:03.000004'.format(url), **self.header)
|
||||
|
||||
self.assertEqual(response.data['count'], 1)
|
||||
self.assertEqual(response.data['results'][0]['id'], self.rack2.pk)
|
||||
|
||||
@@ -301,6 +301,40 @@ class CustomFieldAPITest(APITestCase):
|
||||
cfv = self.site.custom_field_values.get(field=self.cf_select)
|
||||
self.assertEqual(cfv.value.pk, data['custom_fields']['magic_choice'])
|
||||
|
||||
def test_set_custom_field_defaults(self):
|
||||
"""
|
||||
Create a new object with no custom field data. Custom field values should be created using the custom fields'
|
||||
default values.
|
||||
"""
|
||||
CUSTOM_FIELD_DEFAULTS = {
|
||||
'magic_word': 'foobar',
|
||||
'magic_number': '123',
|
||||
'is_magic': 'true',
|
||||
'magic_date': '2019-12-13',
|
||||
'magic_url': 'http://example.com/',
|
||||
'magic_choice': self.cf_select_choice1.value,
|
||||
}
|
||||
|
||||
# Update CustomFields to set default values
|
||||
for field_name, default_value in CUSTOM_FIELD_DEFAULTS.items():
|
||||
CustomField.objects.filter(name=field_name).update(default=default_value)
|
||||
|
||||
data = {
|
||||
'name': 'Test Site X',
|
||||
'slug': 'test-site-x',
|
||||
}
|
||||
|
||||
url = reverse('dcim-api:site-list')
|
||||
response = self.client.post(url, data, format='json', **self.header)
|
||||
|
||||
self.assertHttpStatus(response, status.HTTP_201_CREATED)
|
||||
self.assertEqual(response.data['custom_fields']['magic_word'], CUSTOM_FIELD_DEFAULTS['magic_word'])
|
||||
self.assertEqual(response.data['custom_fields']['magic_number'], str(CUSTOM_FIELD_DEFAULTS['magic_number']))
|
||||
self.assertEqual(response.data['custom_fields']['is_magic'], bool(CUSTOM_FIELD_DEFAULTS['is_magic']))
|
||||
self.assertEqual(response.data['custom_fields']['magic_date'], CUSTOM_FIELD_DEFAULTS['magic_date'])
|
||||
self.assertEqual(response.data['custom_fields']['magic_url'], CUSTOM_FIELD_DEFAULTS['magic_url'])
|
||||
self.assertEqual(response.data['custom_fields']['magic_choice'], self.cf_select_choice1.pk)
|
||||
|
||||
|
||||
class CustomFieldChoiceAPITest(APITestCase):
|
||||
def setUp(self):
|
||||
|
||||
191
netbox/extras/tests/test_filters.py
Normal file
191
netbox/extras/tests/test_filters.py
Normal file
@@ -0,0 +1,191 @@
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.test import TestCase
|
||||
|
||||
from dcim.models import DeviceRole, Platform, Region, Site
|
||||
from extras.choices import *
|
||||
from extras.constants import GRAPH_MODELS
|
||||
from extras.filters import *
|
||||
from extras.models import ConfigContext, ExportTemplate, Graph
|
||||
from tenancy.models import Tenant, TenantGroup
|
||||
|
||||
|
||||
class GraphTestCase(TestCase):
|
||||
queryset = Graph.objects.all()
|
||||
filterset = GraphFilterSet
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
|
||||
# Get the first three available types
|
||||
content_types = ContentType.objects.filter(GRAPH_MODELS)[:3]
|
||||
|
||||
graphs = (
|
||||
Graph(name='Graph 1', type=content_types[0], template_language=TemplateLanguageChoices.LANGUAGE_DJANGO, source='http://example.com/1'),
|
||||
Graph(name='Graph 2', type=content_types[1], template_language=TemplateLanguageChoices.LANGUAGE_JINJA2, source='http://example.com/2'),
|
||||
Graph(name='Graph 3', type=content_types[2], template_language=TemplateLanguageChoices.LANGUAGE_JINJA2, source='http://example.com/3'),
|
||||
)
|
||||
Graph.objects.bulk_create(graphs)
|
||||
|
||||
def test_name(self):
|
||||
params = {'name': 'Graph 1'}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||
|
||||
def test_type(self):
|
||||
content_type = ContentType.objects.filter(GRAPH_MODELS).first()
|
||||
params = {'type': content_type.pk}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||
|
||||
# TODO: Remove in v2.8
|
||||
def test_template_language(self):
|
||||
params = {'template_language': TemplateLanguageChoices.LANGUAGE_JINJA2}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
|
||||
class ExportTemplateTestCase(TestCase):
|
||||
queryset = ExportTemplate.objects.all()
|
||||
filterset = ExportTemplateFilterSet
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
|
||||
content_types = ContentType.objects.filter(model__in=['site', 'rack', 'device'])
|
||||
|
||||
export_templates = (
|
||||
ExportTemplate(name='Export Template 1', content_type=content_types[0], template_language=TemplateLanguageChoices.LANGUAGE_DJANGO, template_code='TESTING'),
|
||||
ExportTemplate(name='Export Template 2', content_type=content_types[1], template_language=TemplateLanguageChoices.LANGUAGE_JINJA2, template_code='TESTING'),
|
||||
ExportTemplate(name='Export Template 3', content_type=content_types[2], template_language=TemplateLanguageChoices.LANGUAGE_JINJA2, template_code='TESTING'),
|
||||
)
|
||||
ExportTemplate.objects.bulk_create(export_templates)
|
||||
|
||||
def test_name(self):
|
||||
params = {'name': 'Export Template 1'}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||
|
||||
def test_content_type(self):
|
||||
params = {'content_type': ContentType.objects.get(model='site').pk}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||
|
||||
def test_template_language(self):
|
||||
params = {'template_language': TemplateLanguageChoices.LANGUAGE_JINJA2}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
|
||||
class ConfigContextTestCase(TestCase):
|
||||
queryset = ConfigContext.objects.all()
|
||||
filterset = ConfigContextFilterSet
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
|
||||
regions = (
|
||||
Region(name='Test Region 1', slug='test-region-1'),
|
||||
Region(name='Test Region 2', slug='test-region-2'),
|
||||
Region(name='Test Region 3', slug='test-region-3'),
|
||||
)
|
||||
# Can't use bulk_create for models with MPTT fields
|
||||
for r in regions:
|
||||
r.save()
|
||||
|
||||
sites = (
|
||||
Site(name='Test Site 1', slug='test-site-1'),
|
||||
Site(name='Test Site 2', slug='test-site-2'),
|
||||
Site(name='Test Site 3', slug='test-site-3'),
|
||||
)
|
||||
Site.objects.bulk_create(sites)
|
||||
|
||||
device_roles = (
|
||||
DeviceRole(name='Device Role 1', slug='device-role-1'),
|
||||
DeviceRole(name='Device Role 2', slug='device-role-2'),
|
||||
DeviceRole(name='Device Role 3', slug='device-role-3'),
|
||||
)
|
||||
DeviceRole.objects.bulk_create(device_roles)
|
||||
|
||||
platforms = (
|
||||
Platform(name='Platform 1', slug='platform-1'),
|
||||
Platform(name='Platform 2', slug='platform-2'),
|
||||
Platform(name='Platform 3', slug='platform-3'),
|
||||
)
|
||||
Platform.objects.bulk_create(platforms)
|
||||
|
||||
tenant_groups = (
|
||||
TenantGroup(name='Tenant Group 1', slug='tenant-group-1'),
|
||||
TenantGroup(name='Tenant Group 2', slug='tenant-group-2'),
|
||||
TenantGroup(name='Tenant Group 3', slug='tenant-group-3'),
|
||||
)
|
||||
TenantGroup.objects.bulk_create(tenant_groups)
|
||||
|
||||
tenants = (
|
||||
Tenant(name='Tenant 1', slug='tenant-1'),
|
||||
Tenant(name='Tenant 2', slug='tenant-2'),
|
||||
Tenant(name='Tenant 3', slug='tenant-3'),
|
||||
)
|
||||
Tenant.objects.bulk_create(tenants)
|
||||
|
||||
for i in range(0, 3):
|
||||
is_active = bool(i % 2)
|
||||
c = ConfigContext.objects.create(
|
||||
name='Config Context {}'.format(i + 1),
|
||||
is_active=is_active,
|
||||
data='{"foo": 123}'
|
||||
)
|
||||
c.regions.set([regions[i]])
|
||||
c.sites.set([sites[i]])
|
||||
c.roles.set([device_roles[i]])
|
||||
c.platforms.set([platforms[i]])
|
||||
c.tenant_groups.set([tenant_groups[i]])
|
||||
c.tenants.set([tenants[i]])
|
||||
|
||||
def test_name(self):
|
||||
params = {'name': 'Config Context 1'}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||
|
||||
def test_is_active(self):
|
||||
params = {'is_active': True}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||
params = {'is_active': False}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_region(self):
|
||||
regions = Region.objects.all()[:2]
|
||||
params = {'region_id': [regions[0].pk, regions[1].pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
params = {'region': [regions[0].slug, regions[1].slug]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_site(self):
|
||||
sites = Site.objects.all()[:2]
|
||||
params = {'site_id': [sites[0].pk, sites[1].pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
params = {'site': [sites[0].slug, sites[1].slug]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_role(self):
|
||||
device_roles = DeviceRole.objects.all()[:2]
|
||||
params = {'role_id': [device_roles[0].pk, device_roles[1].pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
params = {'role': [device_roles[0].slug, device_roles[1].slug]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_platform(self):
|
||||
platforms = Platform.objects.all()[:2]
|
||||
params = {'platform_id': [platforms[0].pk, platforms[1].pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
params = {'platform': [platforms[0].slug, platforms[1].slug]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_tenant_group(self):
|
||||
tenant_groups = TenantGroup.objects.all()[:2]
|
||||
params = {'tenant_group_id': [tenant_groups[0].pk, tenant_groups[1].pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
params = {'tenant_group': [tenant_groups[0].slug, tenant_groups[1].slug]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_tenant_(self):
|
||||
tenants = Tenant.objects.all()[:2]
|
||||
params = {'tenant_id': [tenants[0].pk, tenants[1].pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
params = {'tenant': [tenants[0].slug, tenants[1].slug]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
|
||||
# TODO: ObjectChangeFilter test
|
||||
46
netbox/extras/tests/test_models.py
Normal file
46
netbox/extras/tests/test_models.py
Normal file
@@ -0,0 +1,46 @@
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.test import TestCase
|
||||
|
||||
from dcim.models import Site
|
||||
from extras.choices import TemplateLanguageChoices
|
||||
from extras.models import Graph
|
||||
|
||||
|
||||
class GraphTest(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
self.site = Site(name='Site 1', slug='site-1')
|
||||
|
||||
def test_graph_render_django(self):
|
||||
|
||||
# Using the pluralize filter as a sanity check (it's only available in Django)
|
||||
TEMPLATE_TEXT = "{{ obj.name|lower }} thing{{ 2|pluralize }}"
|
||||
RENDERED_TEXT = "site 1 things"
|
||||
|
||||
graph = Graph(
|
||||
type=ContentType.objects.get(app_label='dcim', model='site'),
|
||||
name='Graph 1',
|
||||
template_language=TemplateLanguageChoices.LANGUAGE_DJANGO,
|
||||
source=TEMPLATE_TEXT,
|
||||
link=TEMPLATE_TEXT
|
||||
)
|
||||
|
||||
self.assertEqual(graph.embed_url(self.site), RENDERED_TEXT)
|
||||
self.assertEqual(graph.embed_link(self.site), RENDERED_TEXT)
|
||||
|
||||
def test_graph_render_jinja2(self):
|
||||
|
||||
TEMPLATE_TEXT = "{{ [obj.name, obj.slug]|join(',') }}"
|
||||
RENDERED_TEXT = "Site 1,site-1"
|
||||
|
||||
graph = Graph(
|
||||
type=ContentType.objects.get(app_label='dcim', model='site'),
|
||||
name='Graph 1',
|
||||
template_language=TemplateLanguageChoices.LANGUAGE_JINJA2,
|
||||
source=TEMPLATE_TEXT,
|
||||
link=TEMPLATE_TEXT
|
||||
)
|
||||
|
||||
self.assertEqual(graph.embed_url(self.site), RENDERED_TEXT)
|
||||
self.assertEqual(graph.embed_link(self.site), RENDERED_TEXT)
|
||||
@@ -31,8 +31,8 @@ class TagListView(PermissionRequiredMixin, ObjectListView):
|
||||
).order_by(
|
||||
'name'
|
||||
)
|
||||
filter = filters.TagFilter
|
||||
filter_form = forms.TagFilterForm
|
||||
filterset = filters.TagFilterSet
|
||||
filterset_form = forms.TagFilterForm
|
||||
table = TagTable
|
||||
template_name = 'extras/tag_list.html'
|
||||
|
||||
@@ -108,8 +108,8 @@ class TagBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
class ConfigContextListView(PermissionRequiredMixin, ObjectListView):
|
||||
permission_required = 'extras.view_configcontext'
|
||||
queryset = ConfigContext.objects.all()
|
||||
filter = filters.ConfigContextFilter
|
||||
filter_form = forms.ConfigContextFilterForm
|
||||
filterset = filters.ConfigContextFilterSet
|
||||
filterset_form = forms.ConfigContextFilterForm
|
||||
table = ConfigContextTable
|
||||
template_name = 'extras/configcontext_list.html'
|
||||
|
||||
@@ -141,7 +141,7 @@ class ConfigContextEditView(ConfigContextCreateView):
|
||||
class ConfigContextBulkEditView(PermissionRequiredMixin, BulkEditView):
|
||||
permission_required = 'extras.change_configcontext'
|
||||
queryset = ConfigContext.objects.all()
|
||||
filter = filters.ConfigContextFilter
|
||||
filterset = filters.ConfigContextFilterSet
|
||||
table = ConfigContextTable
|
||||
form = forms.ConfigContextBulkEditForm
|
||||
default_return_url = 'extras:configcontext_list'
|
||||
@@ -187,8 +187,8 @@ class ObjectConfigContextView(View):
|
||||
class ObjectChangeListView(PermissionRequiredMixin, ObjectListView):
|
||||
permission_required = 'extras.view_objectchange'
|
||||
queryset = ObjectChange.objects.prefetch_related('user', 'changed_object_type')
|
||||
filter = filters.ObjectChangeFilter
|
||||
filter_form = forms.ObjectChangeFilterForm
|
||||
filterset = filters.ObjectChangeFilterSet
|
||||
filterset_form = forms.ObjectChangeFilterForm
|
||||
table = ObjectChangeTable
|
||||
template_name = 'extras/objectchange_list.html'
|
||||
|
||||
@@ -392,7 +392,7 @@ class ScriptView(PermissionRequiredMixin, View):
|
||||
def get(self, request, module, name):
|
||||
|
||||
script = self._get_script(module, name)
|
||||
form = script.as_form()
|
||||
form = script.as_form(initial=request.GET)
|
||||
|
||||
return render(request, 'extras/script.html', {
|
||||
'module': module,
|
||||
@@ -413,7 +413,7 @@ class ScriptView(PermissionRequiredMixin, View):
|
||||
|
||||
if form.is_valid():
|
||||
commit = form.cleaned_data.pop('_commit')
|
||||
output, execution_time = run_script(script, form.cleaned_data, request.FILES, commit)
|
||||
output, execution_time = run_script(script, form.cleaned_data, request, commit)
|
||||
|
||||
return render(request, 'extras/script.html', {
|
||||
'module': module,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import datetime
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
from extras.models import Webhook
|
||||
@@ -14,7 +13,10 @@ def enqueue_webhooks(instance, user, request_id, action):
|
||||
Find Webhook(s) assigned to this instance + action and enqueue them
|
||||
to be processed
|
||||
"""
|
||||
if instance._meta.label.lower() not in WEBHOOK_MODELS:
|
||||
obj_type = ContentType.objects.get_for_model(instance.__class__)
|
||||
|
||||
webhook_models = ContentType.objects.filter(WEBHOOK_MODELS)
|
||||
if obj_type not in webhook_models:
|
||||
return
|
||||
|
||||
# Retrieve any applicable Webhooks
|
||||
@@ -23,7 +25,6 @@ def enqueue_webhooks(instance, user, request_id, action):
|
||||
ObjectChangeActionChoices.ACTION_UPDATE: 'type_update',
|
||||
ObjectChangeActionChoices.ACTION_DELETE: 'type_delete',
|
||||
}[action]
|
||||
obj_type = ContentType.objects.get_for_model(instance.__class__)
|
||||
webhooks = Webhook.objects.filter(obj_type=obj_type, enabled=True, **{action_flag: True})
|
||||
|
||||
if webhooks.exists():
|
||||
|
||||
@@ -61,5 +61,5 @@ def process_webhook(webhook, data, model_name, event, timestamp, username, reque
|
||||
return 'Status {} returned, webhook successfully processed.'.format(response.status_code)
|
||||
else:
|
||||
raise requests.exceptions.RequestException(
|
||||
"Status {} returned, webhook FAILED to process.".format(response.status_code)
|
||||
"Status {} returned with content '{}', webhook FAILED to process.".format(response.status_code, response.content)
|
||||
)
|
||||
|
||||
@@ -9,7 +9,7 @@ from dcim.api.nested_serializers import NestedDeviceSerializer, NestedSiteSerial
|
||||
from dcim.models import Interface
|
||||
from extras.api.customfields import CustomFieldModelSerializer
|
||||
from ipam.choices import *
|
||||
from ipam.models import AF_CHOICES, Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
|
||||
from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
|
||||
from tenancy.api.nested_serializers import NestedTenantSerializer
|
||||
from utilities.api import (
|
||||
ChoiceField, SerializedPKRelatedField, ValidatedModelSerializer, WritableNestedSerializer,
|
||||
@@ -49,6 +49,7 @@ class RIRSerializer(ValidatedModelSerializer):
|
||||
|
||||
|
||||
class AggregateSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
||||
family = ChoiceField(choices=IPAddressFamilyChoices, read_only=True)
|
||||
rir = NestedRIRSerializer()
|
||||
tags = TagListSerializerField(required=False)
|
||||
|
||||
@@ -135,7 +136,7 @@ class VLANSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
||||
#
|
||||
|
||||
class PrefixSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
||||
family = ChoiceField(choices=AF_CHOICES, read_only=True)
|
||||
family = ChoiceField(choices=IPAddressFamilyChoices, read_only=True)
|
||||
site = NestedSiteSerializer(required=False, allow_null=True)
|
||||
vrf = NestedVRFSerializer(required=False, allow_null=True)
|
||||
tenant = NestedTenantSerializer(required=False, allow_null=True)
|
||||
@@ -197,7 +198,7 @@ class IPAddressInterfaceSerializer(WritableNestedSerializer):
|
||||
|
||||
|
||||
class IPAddressSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
||||
family = ChoiceField(choices=AF_CHOICES, read_only=True)
|
||||
family = ChoiceField(choices=IPAddressFamilyChoices, read_only=True)
|
||||
vrf = NestedVRFSerializer(required=False, allow_null=True)
|
||||
tenant = NestedTenantSerializer(required=False, allow_null=True)
|
||||
status = ChoiceField(choices=IPAddressStatusChoices, required=False)
|
||||
|
||||
@@ -20,11 +20,11 @@ from . import serializers
|
||||
|
||||
class IPAMFieldChoicesViewSet(FieldChoicesViewSet):
|
||||
fields = (
|
||||
(Aggregate, ['family']),
|
||||
(Prefix, ['family', 'status']),
|
||||
(IPAddress, ['family', 'status', 'role']),
|
||||
(VLAN, ['status']),
|
||||
(Service, ['protocol']),
|
||||
(serializers.AggregateSerializer, ['family']),
|
||||
(serializers.PrefixSerializer, ['family', 'status']),
|
||||
(serializers.IPAddressSerializer, ['family', 'status', 'role']),
|
||||
(serializers.VLANSerializer, ['status']),
|
||||
(serializers.ServiceSerializer, ['protocol']),
|
||||
)
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ class VRFViewSet(CustomFieldModelViewSet):
|
||||
prefix_count=get_subquery(Prefix, 'vrf')
|
||||
)
|
||||
serializer_class = serializers.VRFSerializer
|
||||
filterset_class = filters.VRFFilter
|
||||
filterset_class = filters.VRFFilterSet
|
||||
|
||||
|
||||
#
|
||||
@@ -50,7 +50,7 @@ class RIRViewSet(ModelViewSet):
|
||||
aggregate_count=Count('aggregates')
|
||||
)
|
||||
serializer_class = serializers.RIRSerializer
|
||||
filterset_class = filters.RIRFilter
|
||||
filterset_class = filters.RIRFilterSet
|
||||
|
||||
|
||||
#
|
||||
@@ -60,7 +60,7 @@ class RIRViewSet(ModelViewSet):
|
||||
class AggregateViewSet(CustomFieldModelViewSet):
|
||||
queryset = Aggregate.objects.prefetch_related('rir').prefetch_related('tags')
|
||||
serializer_class = serializers.AggregateSerializer
|
||||
filterset_class = filters.AggregateFilter
|
||||
filterset_class = filters.AggregateFilterSet
|
||||
|
||||
|
||||
#
|
||||
@@ -73,7 +73,7 @@ class RoleViewSet(ModelViewSet):
|
||||
vlan_count=get_subquery(VLAN, 'role')
|
||||
)
|
||||
serializer_class = serializers.RoleSerializer
|
||||
filterset_class = filters.RoleFilter
|
||||
filterset_class = filters.RoleFilterSet
|
||||
|
||||
|
||||
#
|
||||
@@ -83,7 +83,7 @@ class RoleViewSet(ModelViewSet):
|
||||
class PrefixViewSet(CustomFieldModelViewSet):
|
||||
queryset = Prefix.objects.prefetch_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role', 'tags')
|
||||
serializer_class = serializers.PrefixSerializer
|
||||
filterset_class = filters.PrefixFilter
|
||||
filterset_class = filters.PrefixFilterSet
|
||||
|
||||
@action(detail=True, url_path='available-prefixes', methods=['get', 'post'])
|
||||
def available_prefixes(self, request, pk=None):
|
||||
@@ -264,7 +264,7 @@ class IPAddressViewSet(CustomFieldModelViewSet):
|
||||
'nat_outside', 'tags',
|
||||
)
|
||||
serializer_class = serializers.IPAddressSerializer
|
||||
filterset_class = filters.IPAddressFilter
|
||||
filterset_class = filters.IPAddressFilterSet
|
||||
|
||||
|
||||
#
|
||||
@@ -276,7 +276,7 @@ class VLANGroupViewSet(ModelViewSet):
|
||||
vlan_count=Count('vlans')
|
||||
)
|
||||
serializer_class = serializers.VLANGroupSerializer
|
||||
filterset_class = filters.VLANGroupFilter
|
||||
filterset_class = filters.VLANGroupFilterSet
|
||||
|
||||
|
||||
#
|
||||
@@ -290,7 +290,7 @@ class VLANViewSet(CustomFieldModelViewSet):
|
||||
prefix_count=get_subquery(Prefix, 'role')
|
||||
)
|
||||
serializer_class = serializers.VLANSerializer
|
||||
filterset_class = filters.VLANFilter
|
||||
filterset_class = filters.VLANFilterSet
|
||||
|
||||
|
||||
#
|
||||
@@ -300,4 +300,4 @@ class VLANViewSet(CustomFieldModelViewSet):
|
||||
class ServiceViewSet(ModelViewSet):
|
||||
queryset = Service.objects.prefetch_related('device').prefetch_related('tags')
|
||||
serializer_class = serializers.ServiceSerializer
|
||||
filterset_class = filters.ServiceFilter
|
||||
filterset_class = filters.ServiceFilterSet
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
from utilities.choices import ChoiceSet
|
||||
|
||||
|
||||
class IPAddressFamilyChoices(ChoiceSet):
|
||||
|
||||
FAMILY_4 = 4
|
||||
FAMILY_6 = 6
|
||||
|
||||
CHOICES = (
|
||||
(FAMILY_4, 'IPv4'),
|
||||
(FAMILY_6, 'IPv6'),
|
||||
)
|
||||
|
||||
|
||||
#
|
||||
# Prefixes
|
||||
#
|
||||
@@ -111,7 +122,7 @@ class VLANStatusChoices(ChoiceSet):
|
||||
|
||||
|
||||
#
|
||||
# VLANs
|
||||
# Services
|
||||
#
|
||||
|
||||
class ServiceProtocolChoices(ChoiceSet):
|
||||
|
||||
19
netbox/ipam/constants.py
Normal file
19
netbox/ipam/constants.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from .choices import IPAddressRoleChoices
|
||||
|
||||
# BGP ASN bounds
|
||||
BGP_ASN_MIN = 1
|
||||
BGP_ASN_MAX = 2**32 - 1
|
||||
|
||||
#
|
||||
# IP addresses
|
||||
#
|
||||
|
||||
IPADDRESS_ROLES_NONUNIQUE = (
|
||||
# IPAddress roles which are exempt from unique address enforcement
|
||||
IPAddressRoleChoices.ROLE_ANYCAST,
|
||||
IPAddressRoleChoices.ROLE_VIP,
|
||||
IPAddressRoleChoices.ROLE_VRRP,
|
||||
IPAddressRoleChoices.ROLE_HSRP,
|
||||
IPAddressRoleChoices.ROLE_GLBP,
|
||||
IPAddressRoleChoices.ROLE_CARP,
|
||||
)
|
||||
@@ -1,6 +1,6 @@
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import models
|
||||
from netaddr import AddrFormatError, IPNetwork
|
||||
from netaddr import AddrFormatError, IPNetwork, IPAddress
|
||||
|
||||
from . import lookups
|
||||
from .formfields import IPFormField
|
||||
@@ -23,7 +23,10 @@ class BaseIPField(models.Field):
|
||||
if not value:
|
||||
return value
|
||||
try:
|
||||
return IPNetwork(value)
|
||||
if '/' in str(value):
|
||||
return IPNetwork(value)
|
||||
else:
|
||||
return IPAddress(value)
|
||||
except AddrFormatError as e:
|
||||
raise ValidationError("Invalid IP address format: {}".format(value))
|
||||
except (TypeError, ValueError) as e:
|
||||
@@ -32,6 +35,8 @@ class BaseIPField(models.Field):
|
||||
def get_prep_value(self, value):
|
||||
if not value:
|
||||
return None
|
||||
if isinstance(value, list):
|
||||
return [str(self.to_python(v)) for v in value]
|
||||
return str(self.to_python(value))
|
||||
|
||||
def form_class(self):
|
||||
@@ -90,5 +95,6 @@ IPAddressField.register_lookup(lookups.NetContainedOrEqual)
|
||||
IPAddressField.register_lookup(lookups.NetContains)
|
||||
IPAddressField.register_lookup(lookups.NetContainsOrEquals)
|
||||
IPAddressField.register_lookup(lookups.NetHost)
|
||||
IPAddressField.register_lookup(lookups.NetIn)
|
||||
IPAddressField.register_lookup(lookups.NetHostContained)
|
||||
IPAddressField.register_lookup(lookups.NetMaskLength)
|
||||
|
||||
@@ -4,16 +4,31 @@ from django.core.exceptions import ValidationError
|
||||
from django.db.models import Q
|
||||
from netaddr.core import AddrFormatError
|
||||
|
||||
from dcim.models import Site, Device, Interface
|
||||
from dcim.models import Device, Interface, Region, Site
|
||||
from extras.filters import CustomFieldFilterSet, CreatedUpdatedFilterSet
|
||||
from tenancy.filtersets import TenancyFilterSet
|
||||
from utilities.filters import NameSlugSearchFilterSet, NumericInFilter, TagFilter
|
||||
from tenancy.filters import TenancyFilterSet
|
||||
from utilities.filters import (
|
||||
MultiValueCharFilter, NameSlugSearchFilterSet, NumericInFilter, TagFilter, TreeNodeMultipleChoiceFilter,
|
||||
)
|
||||
from virtualization.models import VirtualMachine
|
||||
from .choices import *
|
||||
from .models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
|
||||
|
||||
|
||||
class VRFFilter(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet):
|
||||
__all__ = (
|
||||
'AggregateFilterSet',
|
||||
'IPAddressFilterSet',
|
||||
'PrefixFilterSet',
|
||||
'RIRFilterSet',
|
||||
'RoleFilterSet',
|
||||
'ServiceFilterSet',
|
||||
'VLANFilterSet',
|
||||
'VLANGroupFilterSet',
|
||||
'VRFFilterSet',
|
||||
)
|
||||
|
||||
|
||||
class VRFFilterSet(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet):
|
||||
id__in = NumericInFilter(
|
||||
field_name='id',
|
||||
lookup_expr='in'
|
||||
@@ -38,7 +53,7 @@ class VRFFilter(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet)
|
||||
fields = ['name', 'rd', 'enforce_unique']
|
||||
|
||||
|
||||
class RIRFilter(NameSlugSearchFilterSet):
|
||||
class RIRFilterSet(NameSlugSearchFilterSet):
|
||||
id__in = NumericInFilter(
|
||||
field_name='id',
|
||||
lookup_expr='in'
|
||||
@@ -49,7 +64,7 @@ class RIRFilter(NameSlugSearchFilterSet):
|
||||
fields = ['name', 'slug', 'is_private']
|
||||
|
||||
|
||||
class AggregateFilter(CustomFieldFilterSet, CreatedUpdatedFilterSet):
|
||||
class AggregateFilterSet(CustomFieldFilterSet, CreatedUpdatedFilterSet):
|
||||
id__in = NumericInFilter(
|
||||
field_name='id',
|
||||
lookup_expr='in'
|
||||
@@ -99,7 +114,7 @@ class AggregateFilter(CustomFieldFilterSet, CreatedUpdatedFilterSet):
|
||||
return queryset.none()
|
||||
|
||||
|
||||
class RoleFilter(NameSlugSearchFilterSet):
|
||||
class RoleFilterSet(NameSlugSearchFilterSet):
|
||||
q = django_filters.CharFilter(
|
||||
method='search',
|
||||
label='Search',
|
||||
@@ -110,7 +125,7 @@ class RoleFilter(NameSlugSearchFilterSet):
|
||||
fields = ['id', 'name', 'slug']
|
||||
|
||||
|
||||
class PrefixFilter(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet):
|
||||
class PrefixFilterSet(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet):
|
||||
id__in = NumericInFilter(
|
||||
field_name='id',
|
||||
lookup_expr='in'
|
||||
@@ -149,6 +164,17 @@ class PrefixFilter(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterS
|
||||
to_field_name='rd',
|
||||
label='VRF (RD)',
|
||||
)
|
||||
region_id = TreeNodeMultipleChoiceFilter(
|
||||
queryset=Region.objects.all(),
|
||||
field_name='site__region__in',
|
||||
label='Region (ID)',
|
||||
)
|
||||
region = TreeNodeMultipleChoiceFilter(
|
||||
queryset=Region.objects.all(),
|
||||
field_name='site__region__in',
|
||||
to_field_name='slug',
|
||||
label='Region (slug)',
|
||||
)
|
||||
site_id = django_filters.ModelMultipleChoiceFilter(
|
||||
queryset=Site.objects.all(),
|
||||
label='Site (ID)',
|
||||
@@ -247,7 +273,7 @@ class PrefixFilter(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterS
|
||||
return queryset.filter(prefix__net_mask_length=value)
|
||||
|
||||
|
||||
class IPAddressFilter(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet):
|
||||
class IPAddressFilterSet(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet):
|
||||
id__in = NumericInFilter(
|
||||
field_name='id',
|
||||
lookup_expr='in'
|
||||
@@ -260,7 +286,7 @@ class IPAddressFilter(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilt
|
||||
method='search_by_parent',
|
||||
label='Parent prefix',
|
||||
)
|
||||
address = django_filters.CharFilter(
|
||||
address = MultiValueCharFilter(
|
||||
method='filter_address',
|
||||
label='Address',
|
||||
)
|
||||
@@ -309,6 +335,10 @@ class IPAddressFilter(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilt
|
||||
queryset=Interface.objects.all(),
|
||||
label='Interface (ID)',
|
||||
)
|
||||
assigned_to_interface = django_filters.BooleanFilter(
|
||||
method='_assigned_to_interface',
|
||||
label='Is assigned to an interface',
|
||||
)
|
||||
status = django_filters.MultipleChoiceFilter(
|
||||
choices=IPAddressStatusChoices,
|
||||
null_value=None
|
||||
@@ -343,13 +373,8 @@ class IPAddressFilter(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilt
|
||||
return queryset.none()
|
||||
|
||||
def filter_address(self, queryset, name, value):
|
||||
if not value.strip():
|
||||
return queryset
|
||||
try:
|
||||
# Match address and subnet mask
|
||||
if '/' in value:
|
||||
return queryset.filter(address=value)
|
||||
return queryset.filter(address__net_host=value)
|
||||
return queryset.filter(address__net_in=value)
|
||||
except ValidationError:
|
||||
return queryset.none()
|
||||
|
||||
@@ -366,8 +391,22 @@ class IPAddressFilter(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilt
|
||||
except Device.DoesNotExist:
|
||||
return queryset.none()
|
||||
|
||||
def _assigned_to_interface(self, queryset, name, value):
|
||||
return queryset.exclude(interface__isnull=value)
|
||||
|
||||
class VLANGroupFilter(NameSlugSearchFilterSet):
|
||||
|
||||
class VLANGroupFilterSet(NameSlugSearchFilterSet):
|
||||
region_id = TreeNodeMultipleChoiceFilter(
|
||||
queryset=Region.objects.all(),
|
||||
field_name='site__region__in',
|
||||
label='Region (ID)',
|
||||
)
|
||||
region = TreeNodeMultipleChoiceFilter(
|
||||
queryset=Region.objects.all(),
|
||||
field_name='site__region__in',
|
||||
to_field_name='slug',
|
||||
label='Region (slug)',
|
||||
)
|
||||
site_id = django_filters.ModelMultipleChoiceFilter(
|
||||
queryset=Site.objects.all(),
|
||||
label='Site (ID)',
|
||||
@@ -384,7 +423,7 @@ class VLANGroupFilter(NameSlugSearchFilterSet):
|
||||
fields = ['id', 'name', 'slug']
|
||||
|
||||
|
||||
class VLANFilter(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet):
|
||||
class VLANFilterSet(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet):
|
||||
id__in = NumericInFilter(
|
||||
field_name='id',
|
||||
lookup_expr='in'
|
||||
@@ -393,6 +432,17 @@ class VLANFilter(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet
|
||||
method='search',
|
||||
label='Search',
|
||||
)
|
||||
region_id = TreeNodeMultipleChoiceFilter(
|
||||
queryset=Region.objects.all(),
|
||||
field_name='site__region__in',
|
||||
label='Region (ID)',
|
||||
)
|
||||
region = TreeNodeMultipleChoiceFilter(
|
||||
queryset=Region.objects.all(),
|
||||
field_name='site__region__in',
|
||||
to_field_name='slug',
|
||||
label='Region (slug)',
|
||||
)
|
||||
site_id = django_filters.ModelMultipleChoiceFilter(
|
||||
queryset=Site.objects.all(),
|
||||
label='Site (ID)',
|
||||
@@ -444,7 +494,7 @@ class VLANFilter(TenancyFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterSet
|
||||
return queryset.filter(qs_filter)
|
||||
|
||||
|
||||
class ServiceFilter(CreatedUpdatedFilterSet):
|
||||
class ServiceFilterSet(CreatedUpdatedFilterSet):
|
||||
q = django_filters.CharFilter(
|
||||
method='search',
|
||||
label='Search',
|
||||
|
||||
@@ -3,14 +3,14 @@ from django.core.exceptions import MultipleObjectsReturned
|
||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||
from taggit.forms import TagField
|
||||
|
||||
from dcim.models import Site, Rack, Device, Interface
|
||||
from dcim.models import Device, Interface, Rack, Region, Site
|
||||
from extras.forms import AddRemoveTagsForm, CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm
|
||||
from tenancy.forms import TenancyFilterForm, TenancyForm
|
||||
from tenancy.models import Tenant
|
||||
from utilities.forms import (
|
||||
add_blank_choice, APISelect, APISelectMultiple, BootstrapMixin, BulkEditNullBooleanSelect, ChainedModelChoiceField,
|
||||
CSVChoiceField, ExpandableIPAddressField, FilterChoiceField, FlexibleModelChoiceField, ReturnURLForm, SlugField,
|
||||
StaticSelect2, StaticSelect2Multiple, BOOLEAN_WITH_BLANK_CHOICES
|
||||
CSVChoiceField, DatePicker, ExpandableIPAddressField, FilterChoiceField, FlexibleModelChoiceField, ReturnURLForm,
|
||||
SlugField, StaticSelect2, StaticSelect2Multiple, BOOLEAN_WITH_BLANK_CHOICES
|
||||
)
|
||||
from virtualization.models import VirtualMachine
|
||||
from .choices import *
|
||||
@@ -156,12 +156,12 @@ class AggregateForm(BootstrapMixin, CustomFieldForm):
|
||||
help_texts = {
|
||||
'prefix': "IPv4 or IPv6 network",
|
||||
'rir': "Regional Internet Registry responsible for this prefix",
|
||||
'date_added': "Format: YYYY-MM-DD",
|
||||
}
|
||||
widgets = {
|
||||
'rir': APISelect(
|
||||
api_url="/api/ipam/rirs/"
|
||||
)
|
||||
),
|
||||
'date_added': DatePicker(),
|
||||
}
|
||||
|
||||
|
||||
@@ -205,6 +205,9 @@ class AggregateBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEd
|
||||
nullable_fields = [
|
||||
'date_added', 'description',
|
||||
]
|
||||
widgets = {
|
||||
'date_added': DatePicker(),
|
||||
}
|
||||
|
||||
|
||||
class AggregateFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
||||
@@ -489,8 +492,8 @@ class PrefixBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditF
|
||||
class PrefixFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
|
||||
model = Prefix
|
||||
field_order = [
|
||||
'q', 'within_include', 'family', 'mask_length', 'vrf_id', 'status', 'site', 'role', 'tenant_group', 'tenant',
|
||||
'is_pool', 'expand',
|
||||
'q', 'within_include', 'family', 'mask_length', 'vrf_id', 'status', 'region', 'site', 'role', 'tenant_group',
|
||||
'tenant', 'is_pool', 'expand',
|
||||
]
|
||||
q = forms.CharField(
|
||||
required=False,
|
||||
@@ -531,6 +534,18 @@ class PrefixFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm)
|
||||
required=False,
|
||||
widget=StaticSelect2Multiple()
|
||||
)
|
||||
region = FilterChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
to_field_name='slug',
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
api_url="/api/dcim/regions/",
|
||||
value_field="slug",
|
||||
filter_for={
|
||||
'site': 'region'
|
||||
}
|
||||
)
|
||||
)
|
||||
site = FilterChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
to_field_name='slug',
|
||||
@@ -918,7 +933,7 @@ class IPAddressBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEd
|
||||
|
||||
|
||||
class IPAddressAssignForm(BootstrapMixin, forms.Form):
|
||||
vrf = forms.ModelChoiceField(
|
||||
vrf_id = forms.ModelChoiceField(
|
||||
queryset=VRF.objects.all(),
|
||||
required=False,
|
||||
label='VRF',
|
||||
@@ -927,15 +942,17 @@ class IPAddressAssignForm(BootstrapMixin, forms.Form):
|
||||
api_url="/api/ipam/vrfs/"
|
||||
)
|
||||
)
|
||||
address = forms.CharField(
|
||||
label='IP Address'
|
||||
q = forms.CharField(
|
||||
required=False,
|
||||
label='Search',
|
||||
)
|
||||
|
||||
|
||||
class IPAddressFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
|
||||
model = IPAddress
|
||||
field_order = [
|
||||
'q', 'parent', 'family', 'mask_length', 'vrf_id', 'status', 'role', 'tenant_group', 'tenant',
|
||||
'q', 'parent', 'family', 'mask_length', 'vrf_id', 'status', 'role', 'assigned_to_interface', 'tenant_group',
|
||||
'tenant',
|
||||
]
|
||||
q = forms.CharField(
|
||||
required=False,
|
||||
@@ -981,6 +998,13 @@ class IPAddressFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterFo
|
||||
required=False,
|
||||
widget=StaticSelect2Multiple()
|
||||
)
|
||||
assigned_to_interface = forms.NullBooleanField(
|
||||
required=False,
|
||||
label='Assigned to an interface',
|
||||
widget=StaticSelect2(
|
||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
#
|
||||
@@ -1023,6 +1047,18 @@ class VLANGroupCSVForm(forms.ModelForm):
|
||||
|
||||
|
||||
class VLANGroupFilterForm(BootstrapMixin, forms.Form):
|
||||
region = FilterChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
to_field_name='slug',
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
api_url="/api/dcim/regions/",
|
||||
value_field="slug",
|
||||
filter_for={
|
||||
'site': 'region',
|
||||
}
|
||||
)
|
||||
)
|
||||
site = FilterChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
to_field_name='slug',
|
||||
@@ -1204,11 +1240,24 @@ class VLANBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEditFor
|
||||
|
||||
class VLANFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm):
|
||||
model = VLAN
|
||||
field_order = ['q', 'site', 'group_id', 'status', 'role', 'tenant_group', 'tenant']
|
||||
field_order = ['q', 'region', 'site', 'group_id', 'status', 'role', 'tenant_group', 'tenant']
|
||||
q = forms.CharField(
|
||||
required=False,
|
||||
label='Search'
|
||||
)
|
||||
region = FilterChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
to_field_name='slug',
|
||||
required=False,
|
||||
widget=APISelectMultiple(
|
||||
api_url="/api/dcim/regions/",
|
||||
value_field="slug",
|
||||
filter_for={
|
||||
'site': 'region',
|
||||
'group_id': 'region'
|
||||
}
|
||||
)
|
||||
)
|
||||
site = FilterChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
to_field_name='slug',
|
||||
|
||||
@@ -100,6 +100,42 @@ class NetHost(Lookup):
|
||||
return 'HOST(%s) = %s' % (lhs, rhs), params
|
||||
|
||||
|
||||
class NetIn(Lookup):
|
||||
lookup_name = 'net_in'
|
||||
|
||||
def as_sql(self, qn, connection):
|
||||
lhs, lhs_params = self.process_lhs(qn, connection)
|
||||
rhs, rhs_params = self.process_rhs(qn, connection)
|
||||
with_mask, without_mask = [], []
|
||||
for address in rhs_params[0]:
|
||||
if '/' in address:
|
||||
with_mask.append(address)
|
||||
else:
|
||||
without_mask.append(address)
|
||||
|
||||
address_in_clause = self.create_in_clause('{} IN ('.format(lhs), len(with_mask))
|
||||
host_in_clause = self.create_in_clause('HOST({}) IN ('.format(lhs), len(without_mask))
|
||||
|
||||
if with_mask and not without_mask:
|
||||
return address_in_clause, with_mask
|
||||
elif not with_mask and without_mask:
|
||||
return host_in_clause, without_mask
|
||||
|
||||
in_clause = '({}) OR ({})'.format(address_in_clause, host_in_clause)
|
||||
with_mask.extend(without_mask)
|
||||
return in_clause, with_mask
|
||||
|
||||
@staticmethod
|
||||
def create_in_clause(clause_part, max_size):
|
||||
clause_elements = [clause_part]
|
||||
for offset in range(0, max_size):
|
||||
if offset > 0:
|
||||
clause_elements.append(', ')
|
||||
clause_elements.append('%s')
|
||||
clause_elements.append(')')
|
||||
return ''.join(clause_elements)
|
||||
|
||||
|
||||
class NetHostContained(Lookup):
|
||||
"""
|
||||
Check for the host portion of an IP address without regard to its mask. This allows us to find e.g. 192.0.2.1/24
|
||||
|
||||
16
netbox/ipam/managers.py
Normal file
16
netbox/ipam/managers.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from django.db import models
|
||||
from django.db.models.expressions import RawSQL
|
||||
|
||||
|
||||
class IPAddressManager(models.Manager):
|
||||
|
||||
def get_queryset(self):
|
||||
"""
|
||||
By default, PostgreSQL will order INETs with shorter (larger) prefix lengths ahead of those with longer
|
||||
(smaller) masks. This makes no sense when ordering IPs, which should be ordered solely by family and host
|
||||
address. We can use HOST() to extract just the host portion of the address (ignoring its mask), but we must
|
||||
then re-cast this value to INET() so that records will be ordered properly. We are essentially re-casting each
|
||||
IP address as a /32 or /128.
|
||||
"""
|
||||
qs = super().get_queryset()
|
||||
return qs.annotate(host=RawSQL('INET(HOST(ipam_ipaddress.address))', [])).order_by('family', 'host')
|
||||
@@ -0,0 +1,100 @@
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
import ipam.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
replaces = [('ipam', '0003_ipam_add_vlangroups'), ('ipam', '0004_ipam_vlangroup_uniqueness'), ('ipam', '0005_auto_20160725_1842'), ('ipam', '0006_vrf_vlan_add_tenant'), ('ipam', '0007_prefix_ipaddress_add_tenant'), ('ipam', '0008_prefix_change_order'), ('ipam', '0009_ipaddress_add_status'), ('ipam', '0010_ipaddress_help_texts'), ('ipam', '0011_rir_add_is_private')]
|
||||
|
||||
dependencies = [
|
||||
('tenancy', '0001_initial'),
|
||||
('dcim', '0010_devicebay_installed_device_set_null'),
|
||||
('ipam', '0002_vrf_add_enforce_unique'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='VLANGroup',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=50)),
|
||||
('slug', models.SlugField()),
|
||||
('site', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='vlan_groups', to='dcim.Site')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['site', 'name'],
|
||||
'unique_together': {('site', 'name'), ('site', 'slug')},
|
||||
'verbose_name': 'VLAN group',
|
||||
'verbose_name_plural': 'VLAN groups',
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='vlan',
|
||||
name='group',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='vlans', to='ipam.VLANGroup'),
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='vlan',
|
||||
options={'ordering': ['site', 'group', 'vid'], 'verbose_name': 'VLAN', 'verbose_name_plural': 'VLANs'},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='vlan',
|
||||
unique_together={('group', 'vid'), ('group', 'name')},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='vlan',
|
||||
name='description',
|
||||
field=models.CharField(blank=True, max_length=100),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='vlan',
|
||||
name='name',
|
||||
field=models.CharField(max_length=64),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='vlan',
|
||||
name='tenant',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='vlans', to='tenancy.Tenant'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='vrf',
|
||||
name='tenant',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='vrfs', to='tenancy.Tenant'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='ipaddress',
|
||||
name='tenant',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='ip_addresses', to='tenancy.Tenant'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='prefix',
|
||||
name='tenant',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='prefixes', to='tenancy.Tenant'),
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='prefix',
|
||||
options={'ordering': ['vrf', 'family', 'prefix'], 'verbose_name_plural': 'prefixes'},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='ipaddress',
|
||||
name='status',
|
||||
field=models.PositiveSmallIntegerField(choices=[(1, b'Active'), (2, b'Reserved'), (5, b'DHCP')], default=1, verbose_name=b'Status'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='ipaddress',
|
||||
name='address',
|
||||
field=ipam.fields.IPAddressField(help_text=b'IPv4 or IPv6 address (with mask)'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='ipaddress',
|
||||
name='nat_inside',
|
||||
field=models.OneToOneField(blank=True, help_text=b'The IP for which this address is the "outside" IP', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='nat_outside', to='ipam.IPAddress', verbose_name=b'NAT (Inside)'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='rir',
|
||||
name='is_private',
|
||||
field=models.BooleanField(default=False, help_text=b'IP space managed by this RIR is considered private', verbose_name=b'Private'),
|
||||
),
|
||||
]
|
||||
@@ -1,141 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.14 on 2018-07-31 02:12
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
import ipam.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
replaces = [('ipam', '0002_vrf_add_enforce_unique'), ('ipam', '0003_ipam_add_vlangroups'), ('ipam', '0004_ipam_vlangroup_uniqueness'), ('ipam', '0005_auto_20160725_1842'), ('ipam', '0006_vrf_vlan_add_tenant'), ('ipam', '0007_prefix_ipaddress_add_tenant'), ('ipam', '0008_prefix_change_order'), ('ipam', '0009_ipaddress_add_status'), ('ipam', '0010_ipaddress_help_texts'), ('ipam', '0011_rir_add_is_private'), ('ipam', '0012_services'), ('ipam', '0013_prefix_add_is_pool'), ('ipam', '0014_ipaddress_status_add_deprecated'), ('ipam', '0015_global_vlans'), ('ipam', '0016_unicode_literals'), ('ipam', '0017_ipaddress_roles'), ('ipam', '0018_remove_service_uniqueness_constraint')]
|
||||
replaces = [('ipam', '0012_services'), ('ipam', '0013_prefix_add_is_pool'), ('ipam', '0014_ipaddress_status_add_deprecated'), ('ipam', '0015_global_vlans'), ('ipam', '0016_unicode_literals'), ('ipam', '0017_ipaddress_roles'), ('ipam', '0018_remove_service_uniqueness_constraint')]
|
||||
|
||||
dependencies = [
|
||||
('dcim', '0010_devicebay_installed_device_set_null'),
|
||||
('dcim', '0022_color_names_to_rgb'),
|
||||
('tenancy', '0001_initial'),
|
||||
('ipam', '0001_initial'),
|
||||
('ipam', '0011_rir_add_is_private'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='vrf',
|
||||
name='enforce_unique',
|
||||
field=models.BooleanField(default=True, help_text='Prevent duplicate prefixes/IP addresses within this VRF', verbose_name='Enforce unique space'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='VLANGroup',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=50)),
|
||||
('slug', models.SlugField()),
|
||||
('site', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='vlan_groups', to='dcim.Site')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['site', 'name'],
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='vlan',
|
||||
name='group',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='vlans', to='ipam.VLANGroup'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='vlangroup',
|
||||
unique_together=set([('site', 'slug'), ('site', 'name')]),
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='vlan',
|
||||
options={'ordering': ['site', 'group', 'vid'], 'verbose_name': 'VLAN', 'verbose_name_plural': 'VLANs'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='vlangroup',
|
||||
options={'ordering': ['site', 'name'], 'verbose_name': 'VLAN group', 'verbose_name_plural': 'VLAN groups'},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='vlan',
|
||||
name='description',
|
||||
field=models.CharField(blank=True, max_length=100),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='vlan',
|
||||
unique_together=set([('group', 'vid'), ('group', 'name')]),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='vlan',
|
||||
name='name',
|
||||
field=models.CharField(max_length=64),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='vlan',
|
||||
name='tenant',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='vlans', to='tenancy.Tenant'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='vrf',
|
||||
name='tenant',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='vrfs', to='tenancy.Tenant'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='ipaddress',
|
||||
name='tenant',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='ip_addresses', to='tenancy.Tenant'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='prefix',
|
||||
name='tenant',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='prefixes', to='tenancy.Tenant'),
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='prefix',
|
||||
options={'ordering': ['vrf', 'family', 'prefix'], 'verbose_name_plural': 'prefixes'},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='ipaddress',
|
||||
name='status',
|
||||
field=models.PositiveSmallIntegerField(choices=[(1, 'Active'), (2, 'Reserved'), (3, 'Deprecated'), (5, 'DHCP')], default=1, help_text='The operational status of this IP', verbose_name='Status'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='ipaddress',
|
||||
name='address',
|
||||
field=ipam.fields.IPAddressField(help_text=b'IPv4 or IPv6 address (with mask)'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='ipaddress',
|
||||
name='nat_inside',
|
||||
field=models.OneToOneField(blank=True, help_text=b'The IP for which this address is the "outside" IP', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='nat_outside', to='ipam.IPAddress', verbose_name=b'NAT (Inside)'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='rir',
|
||||
name='is_private',
|
||||
field=models.BooleanField(default=False, help_text='IP space managed by this RIR is considered private', verbose_name='Private'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Service',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created', models.DateField(auto_now_add=True)),
|
||||
('last_updated', models.DateTimeField(auto_now=True)),
|
||||
('name', models.CharField(max_length=30)),
|
||||
('protocol', models.PositiveSmallIntegerField(choices=[(6, 'TCP'), (17, 'UDP')])),
|
||||
('port', models.PositiveIntegerField(validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(65535)], verbose_name='Port number')),
|
||||
('description', models.CharField(blank=True, max_length=100)),
|
||||
('device', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='services', to='dcim.Device', verbose_name='device')),
|
||||
('ipaddresses', models.ManyToManyField(blank=True, related_name='services', to='ipam.IPAddress', verbose_name='IP addresses')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['device', 'protocol', 'port'],
|
||||
},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='service',
|
||||
unique_together=set([('device', 'protocol', 'port')]),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='prefix',
|
||||
name='is_pool',
|
||||
field=models.BooleanField(default=False, help_text='All IP addresses within this prefix are considered usable', verbose_name='Is a pool'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='prefix',
|
||||
name='prefix',
|
||||
@@ -146,11 +25,26 @@ class Migration(migrations.Migration):
|
||||
name='role',
|
||||
field=models.ForeignKey(blank=True, help_text=b'The primary function of this prefix', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='prefixes', to='ipam.Role'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='prefix',
|
||||
name='status',
|
||||
field=models.PositiveSmallIntegerField(choices=[(0, b'Container'), (1, b'Active'), (2, b'Reserved'), (3, b'Deprecated')], default=1, help_text=b'Operational status of this prefix', verbose_name=b'Status'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='ipaddress',
|
||||
name='status',
|
||||
field=models.PositiveSmallIntegerField(choices=[(1, b'Active'), (2, b'Reserved'), (3, b'Deprecated'), (5, b'DHCP')], default=1, verbose_name=b'Status'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='vlan',
|
||||
name='site',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='vlans', to='dcim.Site'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='vlangroup',
|
||||
name='site',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='vlan_groups', to='dcim.Site'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='aggregate',
|
||||
name='family',
|
||||
@@ -176,6 +70,11 @@ class Migration(migrations.Migration):
|
||||
name='nat_inside',
|
||||
field=models.OneToOneField(blank=True, help_text='The IP for which this address is the "outside" IP', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='nat_outside', to='ipam.IPAddress', verbose_name='NAT (Inside)'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='ipaddress',
|
||||
name='status',
|
||||
field=models.PositiveSmallIntegerField(choices=[(1, 'Active'), (2, 'Reserved'), (3, 'Deprecated'), (5, 'DHCP')], default=1, verbose_name='Status'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='ipaddress',
|
||||
name='vrf',
|
||||
@@ -186,6 +85,11 @@ class Migration(migrations.Migration):
|
||||
name='family',
|
||||
field=models.PositiveSmallIntegerField(choices=[(4, 'IPv4'), (6, 'IPv6')], editable=False),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='prefix',
|
||||
name='is_pool',
|
||||
field=models.BooleanField(default=False, help_text='All IP addresses within this prefix are considered usable', verbose_name='Is a pool'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='prefix',
|
||||
name='prefix',
|
||||
@@ -211,6 +115,11 @@ class Migration(migrations.Migration):
|
||||
name='vrf',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='prefixes', to='ipam.VRF', verbose_name='VRF'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='rir',
|
||||
name='is_private',
|
||||
field=models.BooleanField(default=False, help_text='IP space managed by this RIR is considered private', verbose_name='Private'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='vlan',
|
||||
name='status',
|
||||
@@ -221,6 +130,11 @@ class Migration(migrations.Migration):
|
||||
name='vid',
|
||||
field=models.PositiveSmallIntegerField(validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(4094)], verbose_name='ID'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='vrf',
|
||||
name='enforce_unique',
|
||||
field=models.BooleanField(default=True, help_text='Prevent duplicate prefixes/IP addresses within this VRF', verbose_name='Enforce unique space'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='vrf',
|
||||
name='rd',
|
||||
@@ -231,8 +145,27 @@ class Migration(migrations.Migration):
|
||||
name='role',
|
||||
field=models.PositiveSmallIntegerField(blank=True, choices=[(10, 'Loopback'), (20, 'Secondary'), (30, 'Anycast'), (40, 'VIP'), (41, 'VRRP'), (42, 'HSRP'), (43, 'GLBP')], help_text='The functional role of this IP', null=True, verbose_name='Role'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='service',
|
||||
unique_together=set([]),
|
||||
migrations.AlterField(
|
||||
model_name='ipaddress',
|
||||
name='status',
|
||||
field=models.PositiveSmallIntegerField(choices=[(1, 'Active'), (2, 'Reserved'), (3, 'Deprecated'), (5, 'DHCP')], default=1, help_text='The operational status of this IP', verbose_name='Status'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Service',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created', models.DateField(auto_now_add=True)),
|
||||
('last_updated', models.DateTimeField(auto_now=True)),
|
||||
('name', models.CharField(max_length=30)),
|
||||
('protocol', models.PositiveSmallIntegerField(choices=[(6, 'TCP'), (17, 'UDP')])),
|
||||
('port', models.PositiveIntegerField(validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(65535)], verbose_name='Port number')),
|
||||
('description', models.CharField(blank=True, max_length=100)),
|
||||
('device', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='services', to='dcim.Device', verbose_name='device')),
|
||||
('ipaddresses', models.ManyToManyField(blank=True, related_name='services', to='ipam.IPAddress', verbose_name='IP addresses')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['device', 'protocol', 'port'],
|
||||
'unique_together': set(),
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -1,7 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.14 on 2018-07-31 02:14
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
import taggit.managers
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
replaces = [('ipam', '0021_vrf_ordering'), ('ipam', '0022_tags'), ('ipam', '0023_change_logging'), ('ipam', '0024_vrf_allow_null_rd'), ('ipam', '0025_custom_tag_models')]
|
||||
|
||||
dependencies = [
|
||||
('ipam', '0020_ipaddress_add_role_carp'),
|
||||
('taggit', '0002_auto_20150616_2121'),
|
||||
('extras', '0019_tag_taggeditem'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='vrf',
|
||||
options={'ordering': ['name', 'rd'], 'verbose_name': 'VRF', 'verbose_name_plural': 'VRFs'},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='rir',
|
||||
name='created',
|
||||
field=models.DateField(auto_now_add=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='rir',
|
||||
name='last_updated',
|
||||
field=models.DateTimeField(auto_now=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='role',
|
||||
name='created',
|
||||
field=models.DateField(auto_now_add=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='role',
|
||||
name='last_updated',
|
||||
field=models.DateTimeField(auto_now=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='vlangroup',
|
||||
name='created',
|
||||
field=models.DateField(auto_now_add=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='vlangroup',
|
||||
name='last_updated',
|
||||
field=models.DateTimeField(auto_now=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='aggregate',
|
||||
name='created',
|
||||
field=models.DateField(auto_now_add=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='aggregate',
|
||||
name='last_updated',
|
||||
field=models.DateTimeField(auto_now=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='ipaddress',
|
||||
name='created',
|
||||
field=models.DateField(auto_now_add=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='ipaddress',
|
||||
name='last_updated',
|
||||
field=models.DateTimeField(auto_now=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='prefix',
|
||||
name='created',
|
||||
field=models.DateField(auto_now_add=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='prefix',
|
||||
name='last_updated',
|
||||
field=models.DateTimeField(auto_now=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='service',
|
||||
name='created',
|
||||
field=models.DateField(auto_now_add=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='service',
|
||||
name='last_updated',
|
||||
field=models.DateTimeField(auto_now=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='vlan',
|
||||
name='created',
|
||||
field=models.DateField(auto_now_add=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='vlan',
|
||||
name='last_updated',
|
||||
field=models.DateTimeField(auto_now=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='vrf',
|
||||
name='created',
|
||||
field=models.DateField(auto_now_add=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='vrf',
|
||||
name='last_updated',
|
||||
field=models.DateTimeField(auto_now=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='vrf',
|
||||
name='rd',
|
||||
field=models.CharField(blank=True, max_length=21, null=True, unique=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='aggregate',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='extras.TaggedItem', to='extras.Tag', verbose_name='Tags'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='ipaddress',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='extras.TaggedItem', to='extras.Tag', verbose_name='Tags'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='prefix',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='extras.TaggedItem', to='extras.Tag', verbose_name='Tags'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='service',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='extras.TaggedItem', to='extras.Tag', verbose_name='Tags'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='vlan',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='extras.TaggedItem', to='extras.Tag', verbose_name='Tags'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='vrf',
|
||||
name='tags',
|
||||
field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='extras.TaggedItem', to='extras.Tag', verbose_name='Tags'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,140 @@
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
import django.db.models.expressions
|
||||
|
||||
PREFIX_STATUS_CHOICES = (
|
||||
(0, 'container'),
|
||||
(1, 'active'),
|
||||
(2, 'reserved'),
|
||||
(3, 'deprecated'),
|
||||
)
|
||||
|
||||
IPADDRESS_STATUS_CHOICES = (
|
||||
(0, 'container'),
|
||||
(1, 'active'),
|
||||
(2, 'reserved'),
|
||||
(3, 'deprecated'),
|
||||
)
|
||||
|
||||
IPADDRESS_ROLE_CHOICES = (
|
||||
(10, 'loopback'),
|
||||
(20, 'secondary'),
|
||||
(30, 'anycast'),
|
||||
(40, 'vip'),
|
||||
(41, 'vrrp'),
|
||||
(42, 'hsrp'),
|
||||
(43, 'glbp'),
|
||||
(44, 'carp'),
|
||||
)
|
||||
|
||||
VLAN_STATUS_CHOICES = (
|
||||
(1, 'active'),
|
||||
(2, 'reserved'),
|
||||
(3, 'deprecated'),
|
||||
)
|
||||
|
||||
SERVICE_PROTOCOL_CHOICES = (
|
||||
(6, 'tcp'),
|
||||
(17, 'udp'),
|
||||
)
|
||||
|
||||
|
||||
def prefix_status_to_slug(apps, schema_editor):
|
||||
Prefix = apps.get_model('ipam', 'Prefix')
|
||||
for id, slug in PREFIX_STATUS_CHOICES:
|
||||
Prefix.objects.filter(status=str(id)).update(status=slug)
|
||||
|
||||
|
||||
def ipaddress_status_to_slug(apps, schema_editor):
|
||||
IPAddress = apps.get_model('ipam', 'IPAddress')
|
||||
for id, slug in IPADDRESS_STATUS_CHOICES:
|
||||
IPAddress.objects.filter(status=str(id)).update(status=slug)
|
||||
|
||||
|
||||
def ipaddress_role_to_slug(apps, schema_editor):
|
||||
IPAddress = apps.get_model('ipam', 'IPAddress')
|
||||
for id, slug in IPADDRESS_ROLE_CHOICES:
|
||||
IPAddress.objects.filter(role=str(id)).update(role=slug)
|
||||
|
||||
|
||||
def vlan_status_to_slug(apps, schema_editor):
|
||||
VLAN = apps.get_model('ipam', 'VLAN')
|
||||
for id, slug in VLAN_STATUS_CHOICES:
|
||||
VLAN.objects.filter(status=str(id)).update(status=slug)
|
||||
|
||||
|
||||
def service_protocol_to_slug(apps, schema_editor):
|
||||
Service = apps.get_model('ipam', 'Service')
|
||||
for id, slug in SERVICE_PROTOCOL_CHOICES:
|
||||
Service.objects.filter(protocol=str(id)).update(protocol=slug)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
replaces = [('ipam', '0026_prefix_ordering_vrf_nulls_first'), ('ipam', '0027_ipaddress_add_dns_name'), ('ipam', '0028_3569_prefix_fields'), ('ipam', '0029_3569_ipaddress_fields'), ('ipam', '0030_3569_vlan_fields'), ('ipam', '0031_3569_service_fields'), ('ipam', '0032_role_description')]
|
||||
|
||||
dependencies = [
|
||||
('ipam', '0025_custom_tag_models'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='prefix',
|
||||
options={'ordering': [django.db.models.expressions.OrderBy(django.db.models.expressions.F('vrf'), nulls_first=True), 'family', 'prefix'], 'verbose_name_plural': 'prefixes'},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='ipaddress',
|
||||
name='dns_name',
|
||||
field=models.CharField(blank=True, max_length=255, validators=[django.core.validators.RegexValidator(code='invalid', message='Only alphanumeric characters, hyphens, periods, and underscores are allowed in DNS names', regex='^[0-9A-Za-z._-]+$')]),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='prefix',
|
||||
name='status',
|
||||
field=models.CharField(default='active', max_length=50),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=prefix_status_to_slug,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='ipaddress',
|
||||
name='status',
|
||||
field=models.CharField(default='active', max_length=50),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=ipaddress_status_to_slug,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='ipaddress',
|
||||
name='role',
|
||||
field=models.CharField(blank=True, default='', max_length=50),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=ipaddress_role_to_slug,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='ipaddress',
|
||||
name='role',
|
||||
field=models.CharField(blank=True, max_length=50),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='vlan',
|
||||
name='status',
|
||||
field=models.CharField(default='active', max_length=50),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=vlan_status_to_slug,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='service',
|
||||
name='protocol',
|
||||
field=models.CharField(max_length=50),
|
||||
),
|
||||
migrations.RunPython(
|
||||
code=service_protocol_to_slug,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='role',
|
||||
name='description',
|
||||
field=models.CharField(blank=True, max_length=100),
|
||||
),
|
||||
]
|
||||
42
netbox/ipam/migrations/0033_deterministic_ordering.py
Normal file
42
netbox/ipam/migrations/0033_deterministic_ordering.py
Normal file
@@ -0,0 +1,42 @@
|
||||
# Generated by Django 2.2.8 on 2020-01-15 18:10
|
||||
|
||||
from django.db import migrations
|
||||
import django.db.models.expressions
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('ipam', '0032_role_description'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='aggregate',
|
||||
options={'ordering': ('family', 'prefix', 'pk')},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='ipaddress',
|
||||
options={'ordering': ('family', 'address', 'pk'), 'verbose_name': 'IP address', 'verbose_name_plural': 'IP addresses'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='prefix',
|
||||
options={'ordering': (django.db.models.expressions.OrderBy(django.db.models.expressions.F('vrf'), nulls_first=True), 'family', 'prefix', 'pk'), 'verbose_name_plural': 'prefixes'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='service',
|
||||
options={'ordering': ('protocol', 'port', 'pk')},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='vlan',
|
||||
options={'ordering': ('site', 'group', 'vid', 'pk'), 'verbose_name': 'VLAN', 'verbose_name_plural': 'VLANs'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='vlangroup',
|
||||
options={'ordering': ('site', 'name', 'pk'), 'verbose_name': 'VLAN group', 'verbose_name_plural': 'VLAN groups'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='vrf',
|
||||
options={'ordering': ('name', 'rd', 'pk'), 'verbose_name': 'VRF', 'verbose_name_plural': 'VRFs'},
|
||||
),
|
||||
]
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user