mirror of
https://github.com/netbox-community/netbox.git
synced 2025-12-28 08:07:45 -06:00
Compare commits
15 Commits
circuit-sw
...
20660-scri
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
285abe7cc0 | ||
|
|
c5124cb2e4 | ||
|
|
d01d7b4156 | ||
|
|
4db6123fb2 | ||
|
|
43648d629b | ||
|
|
0b97df0984 | ||
|
|
5334c8143c | ||
|
|
bbb330becf | ||
|
|
e4c74ce6a3 | ||
|
|
a4868f894d | ||
|
|
531ea34207 | ||
|
|
6747c82a1a | ||
|
|
e251ea10b5 | ||
|
|
a1aaf465ac | ||
|
|
2a1d315d85 |
4
.github/ISSUE_TEMPLATE/02-bug_report.yaml
vendored
4
.github/ISSUE_TEMPLATE/02-bug_report.yaml
vendored
@@ -35,9 +35,9 @@ body:
|
||||
label: Python Version
|
||||
description: What version of Python are you currently running?
|
||||
options:
|
||||
- "3.10"
|
||||
- "3.11"
|
||||
- "3.12"
|
||||
- "3.13"
|
||||
- "3.14"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
|
||||
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -31,7 +31,7 @@ jobs:
|
||||
NETBOX_CONFIGURATION: netbox.configuration_testing
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ['3.12', '3.13']
|
||||
python-version: ['3.10', '3.11', '3.12']
|
||||
node-version: ['20.x']
|
||||
services:
|
||||
redis:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.6.9
|
||||
rev: v0.14.1
|
||||
hooks:
|
||||
- id: ruff
|
||||
name: "Ruff linter"
|
||||
|
||||
12958
contrib/openapi.json
12958
contrib/openapi.json
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
||||
|
||||
## Local Authentication
|
||||
|
||||
Local user accounts and groups can be created in NetBox under the "Authentication" section in the "Admin" menu.
|
||||
Local user accounts and groups can be created in NetBox under the "Authentication" section in the "Admin" menu. This section is available only to users with the "staff" permission enabled.
|
||||
|
||||
At a minimum, each user account must have a username and password set. User accounts may also denote a first name, last name, and email address. [Permissions](../permissions.md) may also be assigned to individual users and/or groups as needed.
|
||||
|
||||
|
||||
@@ -1,15 +1,5 @@
|
||||
# GraphQL API Parameters
|
||||
|
||||
## GRAPHQL_DEFAULT_VERSION
|
||||
|
||||
!!! note "This parameter was introduced in NetBox v4.5."
|
||||
|
||||
Default: `1`
|
||||
|
||||
Designates the default version of the GraphQL API served by `/graphql/`. To access a specific version, append the version number to the URL, e.g. `/graphql/v2/`.
|
||||
|
||||
---
|
||||
|
||||
## GRAPHQL_ENABLED
|
||||
|
||||
!!! tip "Dynamic Configuration Parameter"
|
||||
|
||||
@@ -127,3 +127,19 @@ The list of groups that promote an remote User to Superuser on Login. If group i
|
||||
Default: `[]` (Empty list)
|
||||
|
||||
The list of users that get promoted to Superuser on Login. If user isn't present in list on next Login, the Role gets revoked. (Requires `REMOTE_AUTH_ENABLED` and `REMOTE_AUTH_GROUP_SYNC_ENABLED` )
|
||||
|
||||
---
|
||||
|
||||
## REMOTE_AUTH_STAFF_GROUPS
|
||||
|
||||
Default: `[]` (Empty list)
|
||||
|
||||
The list of groups that promote an remote User to Staff on Login. If group isn't present on next Login, the Role gets revoked. (Requires `REMOTE_AUTH_ENABLED` and `REMOTE_AUTH_GROUP_SYNC_ENABLED` )
|
||||
|
||||
---
|
||||
|
||||
## REMOTE_AUTH_STAFF_USERS
|
||||
|
||||
Default: `[]` (Empty list)
|
||||
|
||||
The list of users that get promoted to Staff on Login. If user isn't present in list on next Login, the Role gets revoked. (Requires `REMOTE_AUTH_ENABLED` and `REMOTE_AUTH_GROUP_SYNC_ENABLED` )
|
||||
|
||||
@@ -23,31 +23,6 @@ ALLOWED_HOSTS = ['*']
|
||||
|
||||
---
|
||||
|
||||
## API_TOKEN_PEPPERS
|
||||
|
||||
!!! info "This parameter was introduced in NetBox v4.5."
|
||||
|
||||
[Cryptographic peppers](https://en.wikipedia.org/wiki/Pepper_(cryptography)) are employed to generate hashes of sensitive values on the server. This parameter defines the peppers used to hash v2 API tokens in NetBox. You must define at least one pepper before creating a v2 API token. See the [API documentation](../integrations/rest-api.md#authentication) for further information about how peppers are used.
|
||||
|
||||
```python
|
||||
API_TOKEN_PEPPERS = {
|
||||
# DO NOT USE THIS EXAMPLE PEPPER IN PRODUCTION
|
||||
1: 'kp7ht*76fiQAhUi5dHfASLlYUE_S^gI^(7J^K5M!LfoH@vl&b_',
|
||||
}
|
||||
```
|
||||
|
||||
!!! warning "Peppers are sensitive"
|
||||
Treat pepper values as extremely sensitive. Consider populating peppers from environment variables at initialization time rather than defining them in the configuration file, if feasible.
|
||||
|
||||
Peppers must be at least 50 characters in length and should comprise a random string with a diverse character set. Consider using the Python script at `$INSTALL_ROOT/netbox/generate_secret_key.py` to generate a pepper value.
|
||||
|
||||
It is recommended to start with a pepper ID of `1`. Additional peppers can be introduced later as needed to begin rotating token hashes.
|
||||
|
||||
!!! tip
|
||||
Although NetBox will run without `API_TOKEN_PEPPERS` defined, the use of v2 API tokens will be unavailable.
|
||||
|
||||
---
|
||||
|
||||
## DATABASE
|
||||
|
||||
!!! warning "Legacy Configuration Parameter"
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
# Security & Authentication Parameters
|
||||
|
||||
## ALLOW_TOKEN_RETRIEVAL
|
||||
|
||||
Default: `False`
|
||||
|
||||
!!! note
|
||||
The default value of this parameter changed from `True` to `False` in NetBox v4.3.0.
|
||||
|
||||
If disabled, the values of API tokens will not be displayed after each token's initial creation. A user **must** record the value of a token prior to its creation, or it will be lost. Note that this affects _all_ users, regardless of assigned permissions.
|
||||
|
||||
---
|
||||
|
||||
## ALLOWED_URL_SCHEMES
|
||||
|
||||
!!! tip "Dynamic Configuration Parameter"
|
||||
|
||||
@@ -131,6 +131,17 @@ self.log_info(f"Running as user {username} (IP: {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:
|
||||
|
||||
* `load_yaml`
|
||||
* `load_json`
|
||||
|
||||
These two methods will load data in YAML or JSON format, respectively, from files within the local path (i.e. `SCRIPTS_ROOT`).
|
||||
|
||||
**Note:** These convenience methods are deprecated and will be removed in NetBox v4.4. These only work if running scripts within the local path, they will not work if using a storage other than ScriptFileSystemStorage.
|
||||
|
||||
## Logging
|
||||
|
||||
The Script object provides a set of convenient functions for recording messages at different severity levels:
|
||||
@@ -393,6 +404,61 @@ A complete date & time. Returns a `datetime.datetime` object.
|
||||
|
||||
Custom scripts can be run via the web UI by navigating to the script, completing any required form data, and clicking the "run script" button. It is possible to schedule a script to be executed at specified time in the future. A scheduled script can be canceled by deleting the associated job result object.
|
||||
|
||||
#### Prefilling variables via URL parameters
|
||||
|
||||
Script form fields can be prefilled by appending query parameters to the script URL. Each parameter name must match the variable name defined on the script class. Prefilled values are treated as initial values and can be edited before execution. Multiple values can be supplied by repeating the same parameter. Query values must be percent‑encoded where required (for example, spaces as `%20`).
|
||||
|
||||
Examples:
|
||||
|
||||
For string and integer variables, when a script defines:
|
||||
|
||||
```python
|
||||
from extras.scripts import Script, StringVar, IntegerVar
|
||||
|
||||
class MyScript(Script):
|
||||
name = StringVar()
|
||||
count = IntegerVar()
|
||||
```
|
||||
|
||||
the following URL prefills the `name` and `count` fields:
|
||||
|
||||
```
|
||||
https://<netbox>/extras/scripts/<script_id>/?name=Branch42&count=3
|
||||
```
|
||||
|
||||
For object variables (`ObjectVar`), supply the object’s primary key (PK):
|
||||
|
||||
```
|
||||
https://<netbox>/extras/scripts/<script_id>/?device=1
|
||||
```
|
||||
|
||||
If an object ID cannot be resolved or the object is not visible to the requesting user, the field remains unpopulated.
|
||||
|
||||
Supported variable types:
|
||||
|
||||
| Variable class | Expected input | Example query string |
|
||||
|--------------------------|---------------------------------|---------------------------------------------|
|
||||
| `StringVar` | string (percent‑encoded) | `?name=Branch42` |
|
||||
| `TextVar` | string (percent‑encoded) | `?notes=Initial%20value` |
|
||||
| `IntegerVar` | integer | `?count=3` |
|
||||
| `DecimalVar` | decimal number | `?ratio=0.75` |
|
||||
| `BooleanVar` | value → `True`; empty → `False` | `?enabled=true` (True), `?enabled=` (False) |
|
||||
| `ChoiceVar` | choice value (not label) | `?role=edge` |
|
||||
| `MultiChoiceVar` | choice values (repeat) | `?roles=edge&roles=core` |
|
||||
| `ObjectVar(Device)` | PK (integer) | `?device=1` |
|
||||
| `MultiObjectVar(Device)` | PKs (repeat) | `?devices=1&devices=2` |
|
||||
| `IPAddressVar` | IP address | `?ip=198.51.100.10` |
|
||||
| `IPAddressWithMaskVar` | IP address with mask | `?addr=192.0.2.1/24` |
|
||||
| `IPNetworkVar` | IP network prefix | `?network=2001:db8::/64` |
|
||||
| `DateVar` | date `YYYY-MM-DD` | `?date=2025-01-05` |
|
||||
| `DateTimeVar` | ISO datetime | `?when=2025-01-05T14:30:00` |
|
||||
| `FileVar` | — (not supported) | — |
|
||||
|
||||
!!! note
|
||||
- The parameter names above are examples; use the actual variable attribute names defined by the script.
|
||||
- For `BooleanVar`, only an empty value (`?enabled=`) unchecks the box; any other value including `false` or `0` checks it.
|
||||
- File uploads (`FileVar`) cannot be prefilled via URL parameters.
|
||||
|
||||
### Via the API
|
||||
|
||||
To run a script via the REST API, issue a POST request to the script's endpoint specifying the form data and commitment. For example, to run a script named `example.MyReport`, we would make a request such as the following:
|
||||
|
||||
@@ -7,7 +7,7 @@ Getting started with NetBox development is pretty straightforward, and should fe
|
||||
* A Linux system or compatible environment
|
||||
* A PostgreSQL server, which can be installed locally [per the documentation](../installation/1-postgresql.md)
|
||||
* A Redis server, which can also be [installed locally](../installation/2-redis.md)
|
||||
* Python 3.12 or later
|
||||
* Python 3.10 or later
|
||||
|
||||
### 1. Fork the Repo
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ NetBox's REST API, powered by the [Django REST Framework](https://www.django-res
|
||||
|
||||
```no-highlight
|
||||
curl -s -X POST \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Authorization: Token $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
http://netbox/api/ipam/prefixes/ \
|
||||
--data '{"prefix": "192.0.2.0/24", "site": {"name": "Branch 12"}}'
|
||||
|
||||
@@ -90,10 +90,3 @@ http://netbox:8000/api/extras/config-templates/123/render/ \
|
||||
"bar": 123
|
||||
}'
|
||||
```
|
||||
|
||||
!!! note "Permissions"
|
||||
Rendering configuration templates via the REST API requires appropriate permissions for the relevant object type:
|
||||
|
||||
* To render a device's configuration via `/api/dcim/devices/{id}/render-config/`, assign a permission for "DCIM > Device" with the `render_config` action.
|
||||
* To render a virtual machine's configuration via `/api/virtualization/virtual-machines/{id}/render-config/`, assign a permission for "Virtualization > Virtual Machine" with the `render_config` action.
|
||||
* To render a config template directly via `/api/extras/config-templates/{id}/render/`, assign a permission for "Extras > Config Template" with the `render` action.
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
# Resource Ownership
|
||||
|
||||
!!! info "This feature was introduced in NetBox v4.5."
|
||||
|
||||
Most objects in NetBox can be assigned an owner. An owner is a set of users and/or groups who are responsible for the administration of associated objects. For example, you might designate the operations team at a site as the owner for all prefixes and VLANs deployed at that site. The users and groups assigned to an owner are referred to as its members.
|
||||
|
||||
!!! note
|
||||
Ownership of an object should not be confused with the concept of [tenancy](./tenancy.md), which indicates the dedication of an object to a specific tenant. For instance, a tenant might represent a customer served by the object, whereas an owner typically represents a set of internal users responsible for the management of the object.
|
||||
|
||||
Owners can be organized into groups for easier management.
|
||||
@@ -1,6 +1,6 @@
|
||||
# Tenancy
|
||||
|
||||
Most core objects within NetBox's data model support _tenancy_. This is the association of an object with a particular tenant to convey assignment or dependency. For example, an enterprise might represent its internal business units as tenants, whereas a managed services provider might create a tenant in NetBox to represent each of its customers.
|
||||
Most core objects within NetBox's data model support _tenancy_. This is the association of an object with a particular tenant to convey ownership or dependency. For example, an enterprise might represent its internal business units as tenants, whereas a managed services provider might create a tenant in NetBox to represent each of its customers.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
@@ -19,36 +19,20 @@ Tenants can be grouped by any logic that your use case demands, and groups can b
|
||||
|
||||
Typically, the tenant model is used to represent a customer or internal organization, however it can be used for whatever purpose meets your needs.
|
||||
|
||||
Most core objects within NetBox can be assigned to a particular tenant, so this model provides a very convenient way to correlate resource allocation across object types. For example, each of your customers might have its own racks, devices, IP addresses, circuits and so on: These can all be easily tracked via tenant assignment.
|
||||
Most core objects within NetBox can be assigned to particular tenant, so this model provides a very convenient way to correlate ownership across object types. For example, each of your customers might have its own racks, devices, IP addresses, circuits and so on: These can all be easily tracked via tenant assignment.
|
||||
|
||||
The following objects can be assigned to tenants:
|
||||
|
||||
* Circuits
|
||||
* Circuit groups
|
||||
* Virtual circuits
|
||||
* Cables
|
||||
* Devices
|
||||
* Virtual device contexts
|
||||
* Power feeds
|
||||
* Sites
|
||||
* Racks
|
||||
* Rack reservations
|
||||
* Sites
|
||||
* Locations
|
||||
* ASNs
|
||||
* ASN ranges
|
||||
* Aggregates
|
||||
* Devices
|
||||
* VRFs
|
||||
* Prefixes
|
||||
* IP ranges
|
||||
* IP addresses
|
||||
* VLANs
|
||||
* VLAN groups
|
||||
* VRFs
|
||||
* Route targets
|
||||
* Circuits
|
||||
* Clusters
|
||||
* Virtual machines
|
||||
* L2VPNs
|
||||
* Tunnels
|
||||
* Wireless LANs
|
||||
* Wireless links
|
||||
|
||||
Tenancy represents the dedication of an object to a specific tenant. As such, each object may only be assigned to a single tenant. For example, if you have a firewall dedicated to a particular customer, you would assign it to the tenant which represents that customer. However, if the firewall serves multiple customers, it doesn't *belong* to any particular customer, so the assignment of a tenant would not be appropriate.
|
||||
Tenant assignment is used to signify the ownership of an object in NetBox. As such, each object may only be owned by a single tenant. For example, if you have a firewall dedicated to a particular customer, you would assign it to the tenant which represents that customer. However, if the firewall serves multiple customers, it doesn't *belong* to any particular customer, so tenant assignment would not be appropriate.
|
||||
|
||||
@@ -34,6 +34,9 @@ Sets the default number of rows displayed on paginated tables.
|
||||
### Paginator placement
|
||||
Controls where pagination controls are rendered relative to a table.
|
||||
|
||||
### HTMX navigation (experimental)
|
||||
Enables partial‑page navigation for supported views. Disable this preference if unexpected behavior is observed.
|
||||
|
||||
### Striped table rows
|
||||
Toggles alternating row backgrounds on tables.
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ This section of the documentation discusses installing and configuring the NetBo
|
||||
|
||||
Begin by installing all system packages required by NetBox and its dependencies.
|
||||
|
||||
!!! warning "Python 3.12 or later required"
|
||||
NetBox supports only Python 3.12 or later.
|
||||
!!! warning "Python 3.10 or later required"
|
||||
NetBox supports Python 3.10, 3.11, and 3.12.
|
||||
|
||||
```no-highlight
|
||||
sudo apt install -y python3 python3-pip python3-venv python3-dev \
|
||||
@@ -15,7 +15,7 @@ build-essential libxml2-dev libxslt1-dev libffi-dev libpq-dev \
|
||||
libssl-dev zlib1g-dev
|
||||
```
|
||||
|
||||
Before continuing, check that your installed Python version is at least 3.12:
|
||||
Before continuing, check that your installed Python version is at least 3.10:
|
||||
|
||||
```no-highlight
|
||||
python3 -V
|
||||
@@ -120,23 +120,6 @@ If you are not yet sure what the domain name and/or IP address of the NetBox ins
|
||||
ALLOWED_HOSTS = ['*']
|
||||
```
|
||||
|
||||
### API_TOKEN_PEPPERS
|
||||
|
||||
Define at least one random cryptographic pepper, identified by a numeric ID starting at 1. This will be used to generate SHA256 checksums for API tokens.
|
||||
|
||||
```python
|
||||
API_TOKEN_PEPPERS = {
|
||||
# DO NOT USE THIS EXAMPLE PEPPER IN PRODUCTION
|
||||
1: 'kp7ht*76fiQAhUi5dHfASLlYUE_S^gI^(7J^K5M!LfoH@vl&b_',
|
||||
}
|
||||
```
|
||||
|
||||
!!! tip
|
||||
As with [`SECRET_KEY`](#secret_key) below, you can use the `generate_secret_key.py` script to generate a random pepper:
|
||||
```no-highlight
|
||||
python3 ../generate_secret_key.py
|
||||
```
|
||||
|
||||
### DATABASES
|
||||
|
||||
This parameter holds the PostgreSQL database configuration details. The default database must be defined; additional databases may be defined as needed e.g. by plugins.
|
||||
@@ -252,10 +235,10 @@ Once NetBox has been configured, we're ready to proceed with the actual installa
|
||||
sudo /opt/netbox/upgrade.sh
|
||||
```
|
||||
|
||||
Note that **Python 3.12 or later is required** for NetBox v4.5 and later releases. If the default Python installation on your server is set to a lesser version, pass the path to the supported installation as an environment variable named `PYTHON`. (Note that the environment variable must be passed _after_ the `sudo` command.)
|
||||
Note that **Python 3.10 or later is required** for NetBox v4.0 and later releases. If the default Python installation on your server is set to a lesser version, pass the path to the supported installation as an environment variable named `PYTHON`. (Note that the environment variable must be passed _after_ the `sudo` command.)
|
||||
|
||||
```no-highlight
|
||||
sudo PYTHON=/usr/bin/python3.12 /opt/netbox/upgrade.sh
|
||||
sudo PYTHON=/usr/bin/python3.10 /opt/netbox/upgrade.sh
|
||||
```
|
||||
|
||||
!!! note
|
||||
|
||||
@@ -60,3 +60,6 @@ You should see output similar to the following:
|
||||
If the NetBox service fails to start, issue the command `journalctl -eu netbox` to check for log messages that may indicate the problem.
|
||||
|
||||
Once you've verified that the WSGI workers are up and running, move on to HTTP server setup.
|
||||
|
||||
!!! note
|
||||
There is a bug in the current stable release of gunicorn (v21.2.0) where automatic restarts of the worker processes can result in 502 errors under heavy load. (See [gunicorn bug #3038](https://github.com/benoitc/gunicorn/issues/3038) for more detail.) Users who encounter this issue may opt to downgrade to an earlier, unaffected release of gunicorn (`pip install gunicorn==20.1.0`). Note, however, that this earlier release does not officially support Python 3.11.
|
||||
|
||||
@@ -121,6 +121,7 @@ AUTH_LDAP_MIRROR_GROUPS = True
|
||||
# Define special user types using groups. Exercise great caution when assigning superuser status.
|
||||
AUTH_LDAP_USER_FLAGS_BY_GROUP = {
|
||||
"is_active": "cn=active,ou=groups,dc=example,dc=com",
|
||||
"is_staff": "cn=staff,ou=groups,dc=example,dc=com",
|
||||
"is_superuser": "cn=superuser,ou=groups,dc=example,dc=com"
|
||||
}
|
||||
|
||||
@@ -133,6 +134,7 @@ AUTH_LDAP_CACHE_TIMEOUT = 3600
|
||||
```
|
||||
|
||||
* `is_active` - All users must be mapped to at least this group to enable authentication. Without this, users cannot log in.
|
||||
* `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
|
||||
@@ -246,6 +248,7 @@ AUTH_LDAP_MIRROR_GROUPS = True
|
||||
# Define special user types using groups. Exercise great caution when assigning superuser status.
|
||||
AUTH_LDAP_USER_FLAGS_BY_GROUP = {
|
||||
"is_active": "cn=active,ou=groups,dc=example,dc=com",
|
||||
"is_staff": "cn=staff,ou=groups,dc=example,dc=com",
|
||||
"is_superuser": "cn=superuser,ou=groups,dc=example,dc=com"
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ The following sections detail how to set up a new instance of NetBox:
|
||||
|
||||
| Dependency | Supported Versions |
|
||||
|------------|--------------------|
|
||||
| Python | 3.12, 3.13, 3.14 |
|
||||
| Python | 3.10, 3.11, 3.12 |
|
||||
| PostgreSQL | 14+ |
|
||||
| Redis | 4.0+ |
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ NetBox requires the following dependencies:
|
||||
|
||||
| Dependency | Supported Versions |
|
||||
|------------|--------------------|
|
||||
| Python | 3.12, 3.13, 3.14 |
|
||||
| Python | 3.10, 3.11, 3.12 |
|
||||
| PostgreSQL | 14+ |
|
||||
| Redis | 4.0+ |
|
||||
|
||||
@@ -27,7 +27,6 @@ NetBox requires the following dependencies:
|
||||
|
||||
| NetBox Version | Python min | Python max | PostgreSQL min | Redis min | Documentation |
|
||||
|:--------------:|:----------:|:----------:|:--------------:|:---------:|:-----------------------------------------------------------------------------------------:|
|
||||
| 4.5 | 3.12 | 3.14 | 14 | 4.0 | [Link](https://github.com/netbox-community/netbox/blob/v4.5.0/docs/installation/index.md) |
|
||||
| 4.4 | 3.10 | 3.12 | 14 | 4.0 | [Link](https://github.com/netbox-community/netbox/blob/v4.4.0/docs/installation/index.md) |
|
||||
| 4.3 | 3.10 | 3.12 | 14 | 4.0 | [Link](https://github.com/netbox-community/netbox/blob/v4.3.0/docs/installation/index.md) |
|
||||
| 4.2 | 3.10 | 3.12 | 13 | 4.0 | [Link](https://github.com/netbox-community/netbox/blob/v4.2.0/docs/installation/index.md) |
|
||||
@@ -131,7 +130,7 @@ sudo ./upgrade.sh
|
||||
If the default version of Python is not at least 3.10, you'll need to pass the path to a supported Python version as an environment variable when calling the upgrade script. For example:
|
||||
|
||||
```no-highlight
|
||||
sudo PYTHON=/usr/bin/python3.12 ./upgrade.sh
|
||||
sudo PYTHON=/usr/bin/python3.10 ./upgrade.sh
|
||||
```
|
||||
|
||||
!!! note
|
||||
|
||||
@@ -80,7 +80,7 @@ Likewise, the site, rack, and device objects are located under the "DCIM" applic
|
||||
|
||||
The full hierarchy of available endpoints can be viewed by navigating to the API root in a web browser.
|
||||
|
||||
Each model generally has two views associated with it: a list view and a detail view. The list view is used to retrieve a list of multiple objects and to create new objects. The detail view is used to retrieve, update, or delete a single existing object. All objects are referenced by their numeric primary key (`id`).
|
||||
Each model generally has two views associated with it: a list view and a detail view. The list view is used to retrieve a list of multiple objects and to create new objects. The detail view is used to retrieve, update, or delete an single existing object. All objects are referenced by their numeric primary key (`id`).
|
||||
|
||||
* `/api/dcim/devices/` - List existing devices or create a new device
|
||||
* `/api/dcim/devices/123/` - Retrieve, update, or delete the device with ID 123
|
||||
@@ -653,22 +653,18 @@ The NetBox REST API primarily employs token-based authentication. For convenienc
|
||||
|
||||
### Tokens
|
||||
|
||||
A token is a secret, unique identifier mapped to a NetBox user account. Each user may have one or more tokens which he or she can use for authentication when making REST API requests. To create a token, navigate to the API tokens page under your user profile. When creating a token, NetBox will automatically populate a randomly-generated token value.
|
||||
|
||||
!!! note "Tokens cannot be retrieved once created"
|
||||
Once a token has been created, its plaintext value cannot be retrieved. For this reason, you must take care to securely record the token locally immediately upon its creation. If a token plaintext is lost, it cannot be recovered: A new token must be created.
|
||||
A token is a unique identifier mapped to a NetBox user account. Each user may have one or more tokens which he or she can use for authentication when making REST API requests. To create a token, navigate to the API tokens page under your user profile.
|
||||
|
||||
By default, all users can create and manage their own REST API tokens under the user control panel in the UI or via the REST API. This ability can be disabled by overriding the [`DEFAULT_PERMISSIONS`](../configuration/security.md#default_permissions) configuration parameter.
|
||||
|
||||
Each token contains a 160-bit key represented as 40 hexadecimal characters. When creating a token, you'll typically leave the key field blank so that a random key will be automatically generated. However, NetBox allows you to specify a key in case you need to restore a previously deleted token to operation.
|
||||
|
||||
Additionally, a token can be set to expire at a specific time. This can be useful if an external client needs to be granted temporary access to NetBox.
|
||||
|
||||
#### v1 and v2 Tokens
|
||||
!!! info "Restricting Token Retrieval"
|
||||
The ability to retrieve the key value of a previously-created API token can be restricted by disabling the [`ALLOW_TOKEN_RETRIEVAL`](../configuration/security.md#allow_token_retrieval) configuration parameter.
|
||||
|
||||
Beginning with NetBox v4.5, two versions of API token are supported, denoted as v1 and v2. Users are strongly encouraged to create only v2 tokens and to discontinue the use of v1 tokens. Support for v1 tokens will be removed in a future NetBox release.
|
||||
|
||||
v2 API tokens offer much stronger security. The token plaintext given at creation time is hashed together with a configured [cryptographic pepper](../configuration/required-parameters.md#api_token_peppers) to generate a unique checksum. This checksum is irreversible; the token plaintext is never stored on the server and thus cannot be retrieved even with database-level access.
|
||||
|
||||
#### Restricting Write Operations
|
||||
### Restricting Write Operations
|
||||
|
||||
By default, a token can be used to perform all actions via the API that a user would be permitted to do via the web UI. Deselecting the "write enabled" option will restrict API requests made with the token to read operations (e.g. GET) only.
|
||||
|
||||
@@ -685,22 +681,10 @@ It is possible to provision authentication tokens for other users via the REST A
|
||||
|
||||
### Authenticating to the API
|
||||
|
||||
An authentication token is included with a request in its `Authorization` header. The format of the header value depends on the version of token in use. v2 tokens use the following form, concatenating the token's prefix (`nbt_`) and key with its plaintext value, separated by a period:
|
||||
An authentication token is attached to a request by setting the `Authorization` header to the string `Token` followed by a space and the user's token:
|
||||
|
||||
```
|
||||
Authorization: Bearer nbt_<key>.<token>
|
||||
```
|
||||
|
||||
Legacy v1 tokens use the prefix `Token` rather than `Bearer`, and include only the token plaintext. (v1 tokens do not have a key.)
|
||||
|
||||
```
|
||||
Authorization: Token <token>
|
||||
```
|
||||
|
||||
Below is an example REST API request utilizing a v2 token.
|
||||
|
||||
```
|
||||
$ curl -H "Authorization: Bearer nbt_4F9DAouzURLb.zjebxBPzICiPbWz0Wtx0fTL7bCKXKGTYhNzkgC2S" \
|
||||
$ curl -H "Authorization: Token $TOKEN" \
|
||||
-H "Accept: application/json; indent=4" \
|
||||
https://netbox/api/dcim/sites/
|
||||
{
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
# Owner
|
||||
|
||||
An owner is a set of users and/or groups who are responsible for the administration of certain resources within NetBox. The users and groups assigned to an owner are referred to as its members. Owner assignments are useful for indicating which parties are responsible for the administration of a particular object.
|
||||
|
||||
Most objects within NetBox can be assigned an owner, although this is not required.
|
||||
|
||||
## Fields
|
||||
|
||||
### Name
|
||||
|
||||
The owner's name.
|
||||
|
||||
### Group
|
||||
|
||||
The [group](./ownergroup.md) to which the owner is assigned. The assignment of an owner to a group is optional.
|
||||
|
||||
### User Groups
|
||||
|
||||
Groups of users that are members of the owner.
|
||||
|
||||
### Users
|
||||
|
||||
Individual users that are members of the owner.
|
||||
@@ -1,9 +0,0 @@
|
||||
# Owner Groups
|
||||
|
||||
Groups are used to correlate and organize [owners](./owner.md). The assignment of an owner to a group has no bearing on the relationship of owned objects to their owners.
|
||||
|
||||
## Fields
|
||||
|
||||
### Name
|
||||
|
||||
The name of the group.
|
||||
@@ -173,12 +173,12 @@ classifiers=[
|
||||
'Intended Audience :: Developers',
|
||||
'Natural Language :: English',
|
||||
"Programming Language :: Python :: 3 :: Only",
|
||||
'Programming Language :: Python :: 3.10',
|
||||
'Programming Language :: Python :: 3.11',
|
||||
'Programming Language :: Python :: 3.12',
|
||||
'Programming Language :: Python :: 3.13',
|
||||
'Programming Language :: Python :: 3.14',
|
||||
]
|
||||
|
||||
requires-python = ">=3.12.0"
|
||||
requires-python = ">=3.10.0"
|
||||
|
||||
```
|
||||
|
||||
@@ -195,7 +195,7 @@ python3 -m venv ~/.virtualenvs/my_plugin
|
||||
You can make NetBox available within this environment by creating a path file pointing to its location. This will add NetBox to the Python path upon activation. (Be sure to adjust the command below to specify your actual virtual environment path, Python version, and NetBox installation.)
|
||||
|
||||
```shell
|
||||
echo /opt/netbox/netbox > $VENV/lib/python3.12/site-packages/netbox.pth
|
||||
echo /opt/netbox/netbox > $VENV/lib/python3.10/site-packages/netbox.pth
|
||||
```
|
||||
|
||||
## Development Installation
|
||||
|
||||
@@ -64,17 +64,14 @@ item1 = PluginMenuItem(
|
||||
|
||||
A `PluginMenuItem` has the following attributes:
|
||||
|
||||
| Attribute | Required | Description |
|
||||
|-----------------|----------|------------------------------------------------------|
|
||||
| `link` | Yes | Name of the URL path to which this menu item links |
|
||||
| `link_text` | Yes | The text presented to the user |
|
||||
| `permissions` | - | A list of permissions required to display this link |
|
||||
| `auth_required` | - | Display only for authenticated users |
|
||||
| `staff_only` | - | Display only for superusers |
|
||||
| `buttons` | - | An iterable of PluginMenuButton instances to include |
|
||||
|
||||
!!! note "Changed in NetBox v4.5"
|
||||
In releases prior to NetBox v4.5, `staff_only` restricted display of a menu item to only users with `is_staff` set to True. In NetBox v4.5, the `is_staff` flag was removed from the user model. Menu items with `staff_only` set to True are now displayed only for superusers.
|
||||
| Attribute | Required | Description |
|
||||
|-----------------|----------|----------------------------------------------------------------------------------------------------------|
|
||||
| `link` | Yes | Name of the URL path to which this menu item links |
|
||||
| `link_text` | Yes | The text presented to the user |
|
||||
| `permissions` | - | A list of permissions required to display this link |
|
||||
| `auth_required` | - | Display only for authenticated users |
|
||||
| `staff_only` | - | Display only for users who have `is_staff` set to true (any specified permissions will also be required) |
|
||||
| `buttons` | - | An iterable of PluginMenuButton instances to include |
|
||||
|
||||
## Menu Buttons
|
||||
|
||||
|
||||
@@ -77,7 +77,6 @@ nav:
|
||||
- Wireless: 'features/wireless.md'
|
||||
- Virtualization: 'features/virtualization.md'
|
||||
- VPN Tunnels: 'features/vpn-tunnels.md'
|
||||
- Resource Ownership: 'features/resource-ownership.md'
|
||||
- Tenancy: 'features/tenancy.md'
|
||||
- Contacts: 'features/contacts.md'
|
||||
- Search: 'features/search.md'
|
||||
@@ -274,9 +273,6 @@ nav:
|
||||
- ContactRole: 'models/tenancy/contactrole.md'
|
||||
- Tenant: 'models/tenancy/tenant.md'
|
||||
- TenantGroup: 'models/tenancy/tenantgroup.md'
|
||||
- Users:
|
||||
- Owner: 'models/users/owner.md'
|
||||
- OwnerGroup: 'models/users/ownergroup.md'
|
||||
- Virtualization:
|
||||
- Cluster: 'models/virtualization/cluster.md'
|
||||
- ClusterGroup: 'models/virtualization/clustergroup.md'
|
||||
|
||||
57
netbox/account/tables.py
Normal file
57
netbox/account/tables.py
Normal file
@@ -0,0 +1,57 @@
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from account.models import UserToken
|
||||
from netbox.tables import NetBoxTable, columns
|
||||
|
||||
__all__ = (
|
||||
'UserTokenTable',
|
||||
)
|
||||
|
||||
|
||||
TOKEN = """<samp><span id="token_{{ record.pk }}">{{ record }}</span></samp>"""
|
||||
|
||||
ALLOWED_IPS = """{{ value|join:", " }}"""
|
||||
|
||||
COPY_BUTTON = """
|
||||
{% if settings.ALLOW_TOKEN_RETRIEVAL %}
|
||||
{% copy_content record.pk prefix="token_" color="success" %}
|
||||
{% endif %}
|
||||
"""
|
||||
|
||||
|
||||
class UserTokenTable(NetBoxTable):
|
||||
"""
|
||||
Table for users to manager their own API tokens under account views.
|
||||
"""
|
||||
key = columns.TemplateColumn(
|
||||
verbose_name=_('Key'),
|
||||
template_code=TOKEN,
|
||||
)
|
||||
write_enabled = columns.BooleanColumn(
|
||||
verbose_name=_('Write Enabled')
|
||||
)
|
||||
created = columns.DateTimeColumn(
|
||||
timespec='minutes',
|
||||
verbose_name=_('Created'),
|
||||
)
|
||||
expires = columns.DateTimeColumn(
|
||||
timespec='minutes',
|
||||
verbose_name=_('Expires'),
|
||||
)
|
||||
last_used = columns.DateTimeColumn(
|
||||
verbose_name=_('Last Used'),
|
||||
)
|
||||
allowed_ips = columns.TemplateColumn(
|
||||
verbose_name=_('Allowed IPs'),
|
||||
template_code=ALLOWED_IPS
|
||||
)
|
||||
actions = columns.ActionsColumn(
|
||||
actions=('edit', 'delete'),
|
||||
extra_buttons=COPY_BUTTON
|
||||
)
|
||||
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = UserToken
|
||||
fields = (
|
||||
'pk', 'id', 'key', 'description', 'write_enabled', 'created', 'expires', 'last_used', 'allowed_ips',
|
||||
)
|
||||
@@ -26,9 +26,8 @@ from extras.tables import BookmarkTable, NotificationTable, SubscriptionTable
|
||||
from netbox.authentication import get_auth_backend_display, get_saml_idps
|
||||
from netbox.config import get_config
|
||||
from netbox.views import generic
|
||||
from users import forms
|
||||
from users import forms, tables
|
||||
from users.models import UserConfig
|
||||
from users.tables import TokenTable
|
||||
from utilities.request import safe_for_redirect
|
||||
from utilities.string import remove_linebreaks
|
||||
from utilities.views import register_model_view
|
||||
@@ -329,8 +328,7 @@ class UserTokenListView(LoginRequiredMixin, View):
|
||||
|
||||
def get(self, request):
|
||||
tokens = UserToken.objects.filter(user=request.user)
|
||||
table = TokenTable(tokens)
|
||||
table.columns.hide('user')
|
||||
table = tables.UserTokenTable(tokens)
|
||||
table.configure(request)
|
||||
|
||||
return render(request, 'account/token_list.html', {
|
||||
@@ -345,9 +343,11 @@ class UserTokenView(LoginRequiredMixin, View):
|
||||
|
||||
def get(self, request, pk):
|
||||
token = get_object_or_404(UserToken.objects.filter(user=request.user), pk=pk)
|
||||
key = token.key if settings.ALLOW_TOKEN_RETRIEVAL else None
|
||||
|
||||
return render(request, 'account/token.html', {
|
||||
'object': token,
|
||||
'key': key,
|
||||
})
|
||||
|
||||
|
||||
|
||||
@@ -11,9 +11,7 @@ from circuits.models import (
|
||||
from dcim.api.serializers_.device_components import InterfaceSerializer
|
||||
from dcim.api.serializers_.cables import CabledObjectSerializer
|
||||
from netbox.api.fields import ChoiceField, ContentTypeField, RelatedObjectCountField
|
||||
from netbox.api.serializers import (
|
||||
NetBoxModelSerializer, OrganizationalModelSerializer, PrimaryModelSerializer, WritableNestedSerializer,
|
||||
)
|
||||
from netbox.api.serializers import NetBoxModelSerializer, WritableNestedSerializer
|
||||
from netbox.choices import DistanceUnitChoices
|
||||
from tenancy.api.serializers_.tenants import TenantSerializer
|
||||
from utilities.api import get_serializer_for_model
|
||||
@@ -31,7 +29,7 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class CircuitTypeSerializer(OrganizationalModelSerializer):
|
||||
class CircuitTypeSerializer(NetBoxModelSerializer):
|
||||
|
||||
# Related object counts
|
||||
circuit_count = RelatedObjectCountField('circuits')
|
||||
@@ -39,8 +37,8 @@ class CircuitTypeSerializer(OrganizationalModelSerializer):
|
||||
class Meta:
|
||||
model = CircuitType
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'name', 'slug', 'color', 'description', 'owner', 'tags',
|
||||
'custom_fields', 'created', 'last_updated', 'circuit_count',
|
||||
'id', 'url', 'display_url', 'display', 'name', 'slug', 'color', 'description', 'tags', 'custom_fields',
|
||||
'created', 'last_updated', 'circuit_count',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'name', 'slug', 'description', 'circuit_count')
|
||||
|
||||
@@ -73,15 +71,15 @@ class CircuitCircuitTerminationSerializer(WritableNestedSerializer):
|
||||
return serializer(obj.termination, nested=True, context=context).data
|
||||
|
||||
|
||||
class CircuitGroupSerializer(OrganizationalModelSerializer):
|
||||
class CircuitGroupSerializer(NetBoxModelSerializer):
|
||||
tenant = TenantSerializer(nested=True, required=False, allow_null=True)
|
||||
circuit_count = RelatedObjectCountField('assignments')
|
||||
|
||||
class Meta:
|
||||
model = CircuitGroup
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'name', 'slug', 'description', 'tenant', 'owner', 'tags',
|
||||
'custom_fields', 'created', 'last_updated', 'circuit_count'
|
||||
'id', 'url', 'display_url', 'display', 'name', 'slug', 'description', 'tenant',
|
||||
'tags', 'custom_fields', 'created', 'last_updated', 'circuit_count'
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'name')
|
||||
|
||||
@@ -101,7 +99,7 @@ class CircuitGroupAssignmentSerializer_(NetBoxModelSerializer):
|
||||
brief_fields = ('id', 'url', 'display', 'group', 'priority')
|
||||
|
||||
|
||||
class CircuitSerializer(PrimaryModelSerializer):
|
||||
class CircuitSerializer(NetBoxModelSerializer):
|
||||
provider = ProviderSerializer(nested=True)
|
||||
provider_account = ProviderAccountSerializer(nested=True, required=False, allow_null=True, default=None)
|
||||
status = ChoiceField(choices=CircuitStatusChoices, required=False)
|
||||
@@ -117,7 +115,7 @@ class CircuitSerializer(PrimaryModelSerializer):
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'cid', 'provider', 'provider_account', 'type', 'status', 'tenant',
|
||||
'install_date', 'termination_date', 'commit_rate', 'description', 'distance', 'distance_unit',
|
||||
'termination_a', 'termination_z', 'owner', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
|
||||
'termination_a', 'termination_z', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
|
||||
'assignments',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'provider', 'cid', 'description')
|
||||
@@ -178,7 +176,7 @@ class CircuitGroupAssignmentSerializer(CircuitGroupAssignmentSerializer_):
|
||||
return serializer(obj.member, nested=True, context=context).data
|
||||
|
||||
|
||||
class VirtualCircuitTypeSerializer(OrganizationalModelSerializer):
|
||||
class VirtualCircuitTypeSerializer(NetBoxModelSerializer):
|
||||
|
||||
# Related object counts
|
||||
virtual_circuit_count = RelatedObjectCountField('virtual_circuits')
|
||||
@@ -186,13 +184,13 @@ class VirtualCircuitTypeSerializer(OrganizationalModelSerializer):
|
||||
class Meta:
|
||||
model = VirtualCircuitType
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'name', 'slug', 'color', 'description', 'owner', 'tags',
|
||||
'custom_fields', 'created', 'last_updated', 'virtual_circuit_count',
|
||||
'id', 'url', 'display_url', 'display', 'name', 'slug', 'color', 'description', 'tags', 'custom_fields',
|
||||
'created', 'last_updated', 'virtual_circuit_count',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'name', 'slug', 'description', 'virtual_circuit_count')
|
||||
|
||||
|
||||
class VirtualCircuitSerializer(PrimaryModelSerializer):
|
||||
class VirtualCircuitSerializer(NetBoxModelSerializer):
|
||||
provider_network = ProviderNetworkSerializer(nested=True)
|
||||
provider_account = ProviderAccountSerializer(nested=True, required=False, allow_null=True, default=None)
|
||||
type = VirtualCircuitTypeSerializer(nested=True)
|
||||
@@ -203,7 +201,7 @@ class VirtualCircuitSerializer(PrimaryModelSerializer):
|
||||
model = VirtualCircuit
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'cid', 'provider_network', 'provider_account', 'type', 'status',
|
||||
'tenant', 'description', 'owner', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
|
||||
'tenant', 'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'provider_network', 'cid', 'description')
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ from circuits.models import Provider, ProviderAccount, ProviderNetwork
|
||||
from ipam.api.serializers_.asns import ASNSerializer
|
||||
from ipam.models import ASN
|
||||
from netbox.api.fields import RelatedObjectCountField, SerializedPKRelatedField
|
||||
from netbox.api.serializers import PrimaryModelSerializer
|
||||
from netbox.api.serializers import NetBoxModelSerializer
|
||||
from .nested import NestedProviderAccountSerializer
|
||||
|
||||
__all__ = (
|
||||
@@ -14,7 +14,7 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class ProviderSerializer(PrimaryModelSerializer):
|
||||
class ProviderSerializer(NetBoxModelSerializer):
|
||||
accounts = SerializedPKRelatedField(
|
||||
queryset=ProviderAccount.objects.all(),
|
||||
serializer=NestedProviderAccountSerializer,
|
||||
@@ -35,32 +35,32 @@ class ProviderSerializer(PrimaryModelSerializer):
|
||||
class Meta:
|
||||
model = Provider
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'name', 'slug', 'accounts', 'description', 'owner', 'comments',
|
||||
'id', 'url', 'display_url', 'display', 'name', 'slug', 'accounts', 'description', 'comments',
|
||||
'asns', 'tags', 'custom_fields', 'created', 'last_updated', 'circuit_count',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'name', 'slug', 'description', 'circuit_count')
|
||||
|
||||
|
||||
class ProviderAccountSerializer(PrimaryModelSerializer):
|
||||
class ProviderAccountSerializer(NetBoxModelSerializer):
|
||||
provider = ProviderSerializer(nested=True)
|
||||
name = serializers.CharField(allow_blank=True, max_length=100, required=False, default='')
|
||||
|
||||
class Meta:
|
||||
model = ProviderAccount
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'provider', 'name', 'account', 'description', 'owner', 'comments',
|
||||
'tags', 'custom_fields', 'created', 'last_updated',
|
||||
'id', 'url', 'display_url', 'display', 'provider', 'name', 'account', 'description', 'comments', 'tags',
|
||||
'custom_fields', 'created', 'last_updated',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'name', 'account', 'description')
|
||||
|
||||
|
||||
class ProviderNetworkSerializer(PrimaryModelSerializer):
|
||||
class ProviderNetworkSerializer(NetBoxModelSerializer):
|
||||
provider = ProviderSerializer(nested=True)
|
||||
|
||||
class Meta:
|
||||
model = ProviderNetwork
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'provider', 'name', 'service_id', 'description', 'owner', 'comments',
|
||||
'tags', 'custom_fields', 'created', 'last_updated',
|
||||
'id', 'url', 'display_url', 'display', 'provider', 'name', 'service_id', 'description', 'comments', 'tags',
|
||||
'custom_fields', 'created', 'last_updated',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'name', 'description')
|
||||
|
||||
@@ -6,7 +6,7 @@ from django.utils.translation import gettext as _
|
||||
from dcim.filtersets import CabledObjectFilterSet
|
||||
from dcim.models import Interface, Location, Region, Site, SiteGroup
|
||||
from ipam.models import ASN
|
||||
from netbox.filtersets import NetBoxModelFilterSet, OrganizationalModelFilterSet, PrimaryModelFilterSet
|
||||
from netbox.filtersets import NetBoxModelFilterSet, OrganizationalModelFilterSet
|
||||
from tenancy.filtersets import ContactModelFilterSet, TenancyFilterSet
|
||||
from utilities.filters import (
|
||||
ContentTypeFilter, MultiValueCharFilter, MultiValueNumberFilter, TreeNodeMultipleChoiceFilter,
|
||||
@@ -29,7 +29,7 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class ProviderFilterSet(PrimaryModelFilterSet, ContactModelFilterSet):
|
||||
class ProviderFilterSet(NetBoxModelFilterSet, ContactModelFilterSet):
|
||||
region_id = TreeNodeMultipleChoiceFilter(
|
||||
queryset=Region.objects.all(),
|
||||
field_name='circuits__terminations___region',
|
||||
@@ -95,7 +95,7 @@ class ProviderFilterSet(PrimaryModelFilterSet, ContactModelFilterSet):
|
||||
)
|
||||
|
||||
|
||||
class ProviderAccountFilterSet(PrimaryModelFilterSet, ContactModelFilterSet):
|
||||
class ProviderAccountFilterSet(NetBoxModelFilterSet, ContactModelFilterSet):
|
||||
provider_id = django_filters.ModelMultipleChoiceFilter(
|
||||
queryset=Provider.objects.all(),
|
||||
label=_('Provider (ID)'),
|
||||
@@ -122,7 +122,7 @@ class ProviderAccountFilterSet(PrimaryModelFilterSet, ContactModelFilterSet):
|
||||
).distinct()
|
||||
|
||||
|
||||
class ProviderNetworkFilterSet(PrimaryModelFilterSet):
|
||||
class ProviderNetworkFilterSet(NetBoxModelFilterSet):
|
||||
provider_id = django_filters.ModelMultipleChoiceFilter(
|
||||
queryset=Provider.objects.all(),
|
||||
label=_('Provider (ID)'),
|
||||
@@ -156,7 +156,7 @@ class CircuitTypeFilterSet(OrganizationalModelFilterSet):
|
||||
fields = ('id', 'name', 'slug', 'color', 'description')
|
||||
|
||||
|
||||
class CircuitFilterSet(PrimaryModelFilterSet, TenancyFilterSet, ContactModelFilterSet):
|
||||
class CircuitFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilterSet):
|
||||
provider_id = django_filters.ModelMultipleChoiceFilter(
|
||||
queryset=Provider.objects.all(),
|
||||
label=_('Provider (ID)'),
|
||||
@@ -475,7 +475,7 @@ class VirtualCircuitTypeFilterSet(OrganizationalModelFilterSet):
|
||||
fields = ('id', 'name', 'slug', 'color', 'description')
|
||||
|
||||
|
||||
class VirtualCircuitFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
|
||||
class VirtualCircuitFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
|
||||
provider_id = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='provider_network__provider',
|
||||
queryset=Provider.objects.all(),
|
||||
|
||||
@@ -11,11 +11,11 @@ from circuits.models import *
|
||||
from dcim.models import Site
|
||||
from ipam.models import ASN
|
||||
from netbox.choices import DistanceUnitChoices
|
||||
from netbox.forms import NetBoxModelBulkEditForm, OrganizationalModelBulkEditForm, PrimaryModelBulkEditForm
|
||||
from netbox.forms import NetBoxModelBulkEditForm
|
||||
from tenancy.models import Tenant
|
||||
from utilities.forms import add_blank_choice, get_field_value
|
||||
from utilities.forms.fields import (
|
||||
ColorField, ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField,
|
||||
ColorField, CommentField, ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField,
|
||||
)
|
||||
from utilities.forms.rendering import FieldSet
|
||||
from utilities.forms.widgets import BulkEditNullBooleanSelect, DatePicker, HTMXSelect, NumberWithOptions
|
||||
@@ -36,12 +36,18 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class ProviderBulkEditForm(PrimaryModelBulkEditForm):
|
||||
class ProviderBulkEditForm(NetBoxModelBulkEditForm):
|
||||
asns = DynamicModelMultipleChoiceField(
|
||||
queryset=ASN.objects.all(),
|
||||
label=_('ASNs'),
|
||||
required=False
|
||||
)
|
||||
description = forms.CharField(
|
||||
label=_('Description'),
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
model = Provider
|
||||
fieldsets = (
|
||||
@@ -52,12 +58,18 @@ class ProviderBulkEditForm(PrimaryModelBulkEditForm):
|
||||
)
|
||||
|
||||
|
||||
class ProviderAccountBulkEditForm(PrimaryModelBulkEditForm):
|
||||
class ProviderAccountBulkEditForm(NetBoxModelBulkEditForm):
|
||||
provider = DynamicModelChoiceField(
|
||||
label=_('Provider'),
|
||||
queryset=Provider.objects.all(),
|
||||
required=False
|
||||
)
|
||||
description = forms.CharField(
|
||||
label=_('Description'),
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
model = ProviderAccount
|
||||
fieldsets = (
|
||||
@@ -68,7 +80,7 @@ class ProviderAccountBulkEditForm(PrimaryModelBulkEditForm):
|
||||
)
|
||||
|
||||
|
||||
class ProviderNetworkBulkEditForm(PrimaryModelBulkEditForm):
|
||||
class ProviderNetworkBulkEditForm(NetBoxModelBulkEditForm):
|
||||
provider = DynamicModelChoiceField(
|
||||
label=_('Provider'),
|
||||
queryset=Provider.objects.all(),
|
||||
@@ -79,6 +91,12 @@ class ProviderNetworkBulkEditForm(PrimaryModelBulkEditForm):
|
||||
required=False,
|
||||
label=_('Service ID')
|
||||
)
|
||||
description = forms.CharField(
|
||||
label=_('Description'),
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
model = ProviderNetwork
|
||||
fieldsets = (
|
||||
@@ -89,11 +107,16 @@ class ProviderNetworkBulkEditForm(PrimaryModelBulkEditForm):
|
||||
)
|
||||
|
||||
|
||||
class CircuitTypeBulkEditForm(OrganizationalModelBulkEditForm):
|
||||
class CircuitTypeBulkEditForm(NetBoxModelBulkEditForm):
|
||||
color = ColorField(
|
||||
label=_('Color'),
|
||||
required=False
|
||||
)
|
||||
description = forms.CharField(
|
||||
label=_('Description'),
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
|
||||
model = CircuitType
|
||||
fieldsets = (
|
||||
@@ -102,7 +125,7 @@ class CircuitTypeBulkEditForm(OrganizationalModelBulkEditForm):
|
||||
nullable_fields = ('color', 'description')
|
||||
|
||||
|
||||
class CircuitBulkEditForm(PrimaryModelBulkEditForm):
|
||||
class CircuitBulkEditForm(NetBoxModelBulkEditForm):
|
||||
type = DynamicModelChoiceField(
|
||||
label=_('Type'),
|
||||
queryset=CircuitType.objects.all(),
|
||||
@@ -160,6 +183,12 @@ class CircuitBulkEditForm(PrimaryModelBulkEditForm):
|
||||
required=False,
|
||||
initial=''
|
||||
)
|
||||
description = forms.CharField(
|
||||
label=_('Description'),
|
||||
max_length=100,
|
||||
required=False
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
model = Circuit
|
||||
fieldsets = (
|
||||
@@ -232,7 +261,12 @@ class CircuitTerminationBulkEditForm(NetBoxModelBulkEditForm):
|
||||
pass
|
||||
|
||||
|
||||
class CircuitGroupBulkEditForm(OrganizationalModelBulkEditForm):
|
||||
class CircuitGroupBulkEditForm(NetBoxModelBulkEditForm):
|
||||
description = forms.CharField(
|
||||
label=_('Description'),
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
tenant = DynamicModelChoiceField(
|
||||
label=_('Tenant'),
|
||||
queryset=Tenant.objects.all(),
|
||||
@@ -264,11 +298,16 @@ class CircuitGroupAssignmentBulkEditForm(NetBoxModelBulkEditForm):
|
||||
nullable_fields = ('priority',)
|
||||
|
||||
|
||||
class VirtualCircuitTypeBulkEditForm(OrganizationalModelBulkEditForm):
|
||||
class VirtualCircuitTypeBulkEditForm(NetBoxModelBulkEditForm):
|
||||
color = ColorField(
|
||||
label=_('Color'),
|
||||
required=False
|
||||
)
|
||||
description = forms.CharField(
|
||||
label=_('Description'),
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
|
||||
model = VirtualCircuitType
|
||||
fieldsets = (
|
||||
@@ -277,7 +316,7 @@ class VirtualCircuitTypeBulkEditForm(OrganizationalModelBulkEditForm):
|
||||
nullable_fields = ('color', 'description')
|
||||
|
||||
|
||||
class VirtualCircuitBulkEditForm(PrimaryModelBulkEditForm):
|
||||
class VirtualCircuitBulkEditForm(NetBoxModelBulkEditForm):
|
||||
provider_network = DynamicModelChoiceField(
|
||||
label=_('Provider network'),
|
||||
queryset=ProviderNetwork.objects.all(),
|
||||
@@ -304,6 +343,12 @@ class VirtualCircuitBulkEditForm(PrimaryModelBulkEditForm):
|
||||
queryset=Tenant.objects.all(),
|
||||
required=False
|
||||
)
|
||||
description = forms.CharField(
|
||||
label=_('Description'),
|
||||
max_length=100,
|
||||
required=False
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
model = VirtualCircuit
|
||||
fieldsets = (
|
||||
|
||||
@@ -7,7 +7,7 @@ from circuits.constants import *
|
||||
from circuits.models import *
|
||||
from dcim.models import Interface
|
||||
from netbox.choices import DistanceUnitChoices
|
||||
from netbox.forms import NetBoxModelImportForm, OrganizationalModelImportForm, PrimaryModelImportForm
|
||||
from netbox.forms import NetBoxModelImportForm
|
||||
from tenancy.models import Tenant
|
||||
from utilities.forms.fields import CSVChoiceField, CSVContentTypeField, CSVModelChoiceField, SlugField
|
||||
|
||||
@@ -28,17 +28,17 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class ProviderImportForm(PrimaryModelImportForm):
|
||||
class ProviderImportForm(NetBoxModelImportForm):
|
||||
slug = SlugField()
|
||||
|
||||
class Meta:
|
||||
model = Provider
|
||||
fields = (
|
||||
'name', 'slug', 'description', 'owner', 'comments', 'tags',
|
||||
'name', 'slug', 'description', 'comments', 'tags',
|
||||
)
|
||||
|
||||
|
||||
class ProviderAccountImportForm(PrimaryModelImportForm):
|
||||
class ProviderAccountImportForm(NetBoxModelImportForm):
|
||||
provider = CSVModelChoiceField(
|
||||
label=_('Provider'),
|
||||
queryset=Provider.objects.all(),
|
||||
@@ -49,11 +49,11 @@ class ProviderAccountImportForm(PrimaryModelImportForm):
|
||||
class Meta:
|
||||
model = ProviderAccount
|
||||
fields = (
|
||||
'provider', 'name', 'account', 'description', 'owner', 'comments', 'tags',
|
||||
'provider', 'name', 'account', 'description', 'comments', 'tags',
|
||||
)
|
||||
|
||||
|
||||
class ProviderNetworkImportForm(PrimaryModelImportForm):
|
||||
class ProviderNetworkImportForm(NetBoxModelImportForm):
|
||||
provider = CSVModelChoiceField(
|
||||
label=_('Provider'),
|
||||
queryset=Provider.objects.all(),
|
||||
@@ -64,19 +64,19 @@ class ProviderNetworkImportForm(PrimaryModelImportForm):
|
||||
class Meta:
|
||||
model = ProviderNetwork
|
||||
fields = [
|
||||
'provider', 'name', 'service_id', 'description', 'owner', 'comments', 'tags'
|
||||
'provider', 'name', 'service_id', 'description', 'comments', 'tags'
|
||||
]
|
||||
|
||||
|
||||
class CircuitTypeImportForm(OrganizationalModelImportForm):
|
||||
class CircuitTypeImportForm(NetBoxModelImportForm):
|
||||
slug = SlugField()
|
||||
|
||||
class Meta:
|
||||
model = CircuitType
|
||||
fields = ('name', 'slug', 'color', 'description', 'owner', 'tags')
|
||||
fields = ('name', 'slug', 'color', 'description', 'tags')
|
||||
|
||||
|
||||
class CircuitImportForm(PrimaryModelImportForm):
|
||||
class CircuitImportForm(NetBoxModelImportForm):
|
||||
provider = CSVModelChoiceField(
|
||||
label=_('Provider'),
|
||||
queryset=Provider.objects.all(),
|
||||
@@ -119,7 +119,7 @@ class CircuitImportForm(PrimaryModelImportForm):
|
||||
model = Circuit
|
||||
fields = [
|
||||
'cid', 'provider', 'provider_account', 'type', 'status', 'tenant', 'install_date', 'termination_date',
|
||||
'commit_rate', 'distance', 'distance_unit', 'description', 'owner', 'comments', 'tags'
|
||||
'commit_rate', 'distance', 'distance_unit', 'description', 'comments', 'tags'
|
||||
]
|
||||
|
||||
|
||||
@@ -165,7 +165,7 @@ class CircuitTerminationImportForm(NetBoxModelImportForm, BaseCircuitTermination
|
||||
}
|
||||
|
||||
|
||||
class CircuitGroupImportForm(OrganizationalModelImportForm):
|
||||
class CircuitGroupImportForm(NetBoxModelImportForm):
|
||||
tenant = CSVModelChoiceField(
|
||||
label=_('Tenant'),
|
||||
queryset=Tenant.objects.all(),
|
||||
@@ -176,7 +176,7 @@ class CircuitGroupImportForm(OrganizationalModelImportForm):
|
||||
|
||||
class Meta:
|
||||
model = CircuitGroup
|
||||
fields = ('name', 'slug', 'description', 'tenant', 'owner', 'tags')
|
||||
fields = ('name', 'slug', 'description', 'tenant', 'tags')
|
||||
|
||||
|
||||
class CircuitGroupAssignmentImportForm(NetBoxModelImportForm):
|
||||
@@ -195,14 +195,15 @@ class CircuitGroupAssignmentImportForm(NetBoxModelImportForm):
|
||||
fields = ('member_type', 'member_id', 'group', 'priority')
|
||||
|
||||
|
||||
class VirtualCircuitTypeImportForm(OrganizationalModelImportForm):
|
||||
class VirtualCircuitTypeImportForm(NetBoxModelImportForm):
|
||||
slug = SlugField()
|
||||
|
||||
class Meta:
|
||||
model = VirtualCircuitType
|
||||
fields = ('name', 'slug', 'color', 'description', 'owner', 'tags')
|
||||
fields = ('name', 'slug', 'color', 'description', 'tags')
|
||||
|
||||
|
||||
class VirtualCircuitImportForm(PrimaryModelImportForm):
|
||||
class VirtualCircuitImportForm(NetBoxModelImportForm):
|
||||
provider_network = CSVModelChoiceField(
|
||||
label=_('Provider network'),
|
||||
queryset=ProviderNetwork.objects.all(),
|
||||
@@ -238,8 +239,8 @@ class VirtualCircuitImportForm(PrimaryModelImportForm):
|
||||
class Meta:
|
||||
model = VirtualCircuit
|
||||
fields = [
|
||||
'cid', 'provider_network', 'provider_account', 'type', 'status', 'tenant', 'description', 'owner',
|
||||
'comments', 'tags',
|
||||
'cid', 'provider_network', 'provider_account', 'type', 'status', 'tenant', 'description', 'comments',
|
||||
'tags',
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ from circuits.models import *
|
||||
from dcim.models import Location, Region, Site, SiteGroup
|
||||
from ipam.models import ASN
|
||||
from netbox.choices import DistanceUnitChoices
|
||||
from netbox.forms import NetBoxModelFilterSetForm, OrganizationalModelFilterSetForm, PrimaryModelFilterSetForm
|
||||
from netbox.forms import NetBoxModelFilterSetForm
|
||||
from tenancy.forms import TenancyFilterForm, ContactModelFilterForm
|
||||
from utilities.forms import add_blank_choice
|
||||
from utilities.forms.fields import ColorField, DynamicModelMultipleChoiceField, TagFilterField
|
||||
@@ -31,10 +31,10 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class ProviderFilterForm(ContactModelFilterForm, PrimaryModelFilterSetForm):
|
||||
class ProviderFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
|
||||
model = Provider
|
||||
fieldsets = (
|
||||
FieldSet('q', 'filter_id', 'tag', 'owner_id'),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('region_id', 'site_group_id', 'site_id', name=_('Location')),
|
||||
FieldSet('asn_id', name=_('ASN')),
|
||||
FieldSet('contact', 'contact_role', 'contact_group', name=_('Contacts')),
|
||||
@@ -66,10 +66,10 @@ class ProviderFilterForm(ContactModelFilterForm, PrimaryModelFilterSetForm):
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class ProviderAccountFilterForm(ContactModelFilterForm, PrimaryModelFilterSetForm):
|
||||
class ProviderAccountFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
|
||||
model = ProviderAccount
|
||||
fieldsets = (
|
||||
FieldSet('q', 'filter_id', 'tag', 'owner_id'),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('provider_id', 'account', name=_('Attributes')),
|
||||
FieldSet('contact', 'contact_role', 'contact_group', name=_('Contacts')),
|
||||
)
|
||||
@@ -85,10 +85,10 @@ class ProviderAccountFilterForm(ContactModelFilterForm, PrimaryModelFilterSetFor
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class ProviderNetworkFilterForm(PrimaryModelFilterSetForm):
|
||||
class ProviderNetworkFilterForm(NetBoxModelFilterSetForm):
|
||||
model = ProviderNetwork
|
||||
fieldsets = (
|
||||
FieldSet('q', 'filter_id', 'tag', 'owner_id'),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('provider_id', 'service_id', name=_('Attributes')),
|
||||
)
|
||||
provider_id = DynamicModelMultipleChoiceField(
|
||||
@@ -104,10 +104,10 @@ class ProviderNetworkFilterForm(PrimaryModelFilterSetForm):
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class CircuitTypeFilterForm(OrganizationalModelFilterSetForm):
|
||||
class CircuitTypeFilterForm(NetBoxModelFilterSetForm):
|
||||
model = CircuitType
|
||||
fieldsets = (
|
||||
FieldSet('q', 'filter_id', 'tag', 'owner_id'),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('color', name=_('Attributes')),
|
||||
)
|
||||
tag = TagFilterField(model)
|
||||
@@ -118,10 +118,10 @@ class CircuitTypeFilterForm(OrganizationalModelFilterSetForm):
|
||||
)
|
||||
|
||||
|
||||
class CircuitFilterForm(TenancyFilterForm, ContactModelFilterForm, PrimaryModelFilterSetForm):
|
||||
class CircuitFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilterSetForm):
|
||||
model = Circuit
|
||||
fieldsets = (
|
||||
FieldSet('q', 'filter_id', 'tag', 'owner_id'),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('provider_id', 'provider_account_id', 'provider_network_id', name=_('Provider')),
|
||||
FieldSet(
|
||||
'type_id', 'status', 'install_date', 'termination_date', 'commit_rate', 'distance', 'distance_unit',
|
||||
@@ -271,10 +271,10 @@ class CircuitTerminationFilterForm(NetBoxModelFilterSetForm):
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class CircuitGroupFilterForm(TenancyFilterForm, OrganizationalModelFilterSetForm):
|
||||
class CircuitGroupFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
model = CircuitGroup
|
||||
fieldsets = (
|
||||
FieldSet('q', 'filter_id', 'tag', 'owner_id'),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||
)
|
||||
tag = TagFilterField(model)
|
||||
@@ -309,10 +309,10 @@ class CircuitGroupAssignmentFilterForm(NetBoxModelFilterSetForm):
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class VirtualCircuitTypeFilterForm(OrganizationalModelFilterSetForm):
|
||||
class VirtualCircuitTypeFilterForm(NetBoxModelFilterSetForm):
|
||||
model = VirtualCircuitType
|
||||
fieldsets = (
|
||||
FieldSet('q', 'filter_id', 'tag', 'owner_id'),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('color', name=_('Attributes')),
|
||||
)
|
||||
tag = TagFilterField(model)
|
||||
@@ -323,10 +323,10 @@ class VirtualCircuitTypeFilterForm(OrganizationalModelFilterSetForm):
|
||||
)
|
||||
|
||||
|
||||
class VirtualCircuitFilterForm(TenancyFilterForm, ContactModelFilterForm, PrimaryModelFilterSetForm):
|
||||
class VirtualCircuitFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilterSetForm):
|
||||
model = VirtualCircuit
|
||||
fieldsets = (
|
||||
FieldSet('q', 'filter_id', 'tag', 'owner_id'),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('provider_id', 'provider_account_id', 'provider_network_id', name=_('Provider')),
|
||||
FieldSet('type_id', 'status', name=_('Attributes')),
|
||||
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||
|
||||
@@ -10,11 +10,11 @@ from circuits.constants import *
|
||||
from circuits.models import *
|
||||
from dcim.models import Interface, Site
|
||||
from ipam.models import ASN
|
||||
from netbox.forms import NetBoxModelForm, OrganizationalModelForm, PrimaryModelForm
|
||||
from netbox.forms import NetBoxModelForm
|
||||
from tenancy.forms import TenancyForm
|
||||
from utilities.forms import get_field_value
|
||||
from utilities.forms.fields import (
|
||||
ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, SlugField,
|
||||
CommentField, ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, SlugField,
|
||||
)
|
||||
from utilities.forms.mixins import DistanceValidationMixin
|
||||
from utilities.forms.rendering import FieldSet, InlineFields
|
||||
@@ -36,13 +36,14 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class ProviderForm(PrimaryModelForm):
|
||||
class ProviderForm(NetBoxModelForm):
|
||||
slug = SlugField()
|
||||
asns = DynamicModelMultipleChoiceField(
|
||||
queryset=ASN.objects.all(),
|
||||
label=_('ASNs'),
|
||||
required=False
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
fieldsets = (
|
||||
FieldSet('name', 'slug', 'asns', 'description', 'tags'),
|
||||
@@ -51,32 +52,34 @@ class ProviderForm(PrimaryModelForm):
|
||||
class Meta:
|
||||
model = Provider
|
||||
fields = [
|
||||
'name', 'slug', 'asns', 'description', 'owner', 'comments', 'tags',
|
||||
'name', 'slug', 'asns', 'description', 'comments', 'tags',
|
||||
]
|
||||
|
||||
|
||||
class ProviderAccountForm(PrimaryModelForm):
|
||||
class ProviderAccountForm(NetBoxModelForm):
|
||||
provider = DynamicModelChoiceField(
|
||||
label=_('Provider'),
|
||||
queryset=Provider.objects.all(),
|
||||
selector=True,
|
||||
quick_add=True
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
class Meta:
|
||||
model = ProviderAccount
|
||||
fields = [
|
||||
'provider', 'name', 'account', 'description', 'owner', 'comments', 'tags',
|
||||
'provider', 'name', 'account', 'description', 'comments', 'tags',
|
||||
]
|
||||
|
||||
|
||||
class ProviderNetworkForm(PrimaryModelForm):
|
||||
class ProviderNetworkForm(NetBoxModelForm):
|
||||
provider = DynamicModelChoiceField(
|
||||
label=_('Provider'),
|
||||
queryset=Provider.objects.all(),
|
||||
selector=True,
|
||||
quick_add=True
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
fieldsets = (
|
||||
FieldSet('provider', 'name', 'service_id', 'description', 'tags'),
|
||||
@@ -85,13 +88,15 @@ class ProviderNetworkForm(PrimaryModelForm):
|
||||
class Meta:
|
||||
model = ProviderNetwork
|
||||
fields = [
|
||||
'provider', 'name', 'service_id', 'description', 'owner', 'comments', 'tags',
|
||||
'provider', 'name', 'service_id', 'description', 'comments', 'tags',
|
||||
]
|
||||
|
||||
|
||||
class CircuitTypeForm(OrganizationalModelForm):
|
||||
class CircuitTypeForm(NetBoxModelForm):
|
||||
slug = SlugField()
|
||||
|
||||
fieldsets = (
|
||||
FieldSet('name', 'slug', 'color', 'description', 'owner', 'tags'),
|
||||
FieldSet('name', 'slug', 'color', 'description', 'tags'),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
@@ -101,7 +106,7 @@ class CircuitTypeForm(OrganizationalModelForm):
|
||||
]
|
||||
|
||||
|
||||
class CircuitForm(DistanceValidationMixin, TenancyForm, PrimaryModelForm):
|
||||
class CircuitForm(DistanceValidationMixin, TenancyForm, NetBoxModelForm):
|
||||
provider = DynamicModelChoiceField(
|
||||
label=_('Provider'),
|
||||
queryset=Provider.objects.all(),
|
||||
@@ -120,6 +125,7 @@ class CircuitForm(DistanceValidationMixin, TenancyForm, PrimaryModelForm):
|
||||
queryset=CircuitType.objects.all(),
|
||||
quick_add=True
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
fieldsets = (
|
||||
FieldSet(
|
||||
@@ -141,7 +147,7 @@ class CircuitForm(DistanceValidationMixin, TenancyForm, PrimaryModelForm):
|
||||
model = Circuit
|
||||
fields = [
|
||||
'cid', 'type', 'provider', 'provider_account', 'status', 'install_date', 'termination_date', 'commit_rate',
|
||||
'distance', 'distance_unit', 'description', 'tenant_group', 'tenant', 'owner', 'comments', 'tags',
|
||||
'distance', 'distance_unit', 'description', 'tenant_group', 'tenant', 'comments', 'tags',
|
||||
]
|
||||
widgets = {
|
||||
'install_date': DatePicker(),
|
||||
@@ -227,7 +233,9 @@ class CircuitTerminationForm(NetBoxModelForm):
|
||||
self.instance.termination = self.cleaned_data.get('termination')
|
||||
|
||||
|
||||
class CircuitGroupForm(TenancyForm, OrganizationalModelForm):
|
||||
class CircuitGroupForm(TenancyForm, NetBoxModelForm):
|
||||
slug = SlugField()
|
||||
|
||||
fieldsets = (
|
||||
FieldSet('name', 'slug', 'description', 'tags', name=_('Circuit Group')),
|
||||
FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
|
||||
@@ -236,7 +244,7 @@ class CircuitGroupForm(TenancyForm, OrganizationalModelForm):
|
||||
class Meta:
|
||||
model = CircuitGroup
|
||||
fields = [
|
||||
'name', 'slug', 'description', 'tenant_group', 'tenant', 'owner', 'tags',
|
||||
'name', 'slug', 'description', 'tenant_group', 'tenant', 'tags',
|
||||
]
|
||||
|
||||
|
||||
@@ -299,7 +307,9 @@ class CircuitGroupAssignmentForm(NetBoxModelForm):
|
||||
self.instance.member = self.cleaned_data.get('member')
|
||||
|
||||
|
||||
class VirtualCircuitTypeForm(OrganizationalModelForm):
|
||||
class VirtualCircuitTypeForm(NetBoxModelForm):
|
||||
slug = SlugField()
|
||||
|
||||
fieldsets = (
|
||||
FieldSet('name', 'slug', 'color', 'description', 'tags'),
|
||||
)
|
||||
@@ -307,11 +317,11 @@ class VirtualCircuitTypeForm(OrganizationalModelForm):
|
||||
class Meta:
|
||||
model = VirtualCircuitType
|
||||
fields = [
|
||||
'name', 'slug', 'color', 'description', 'owner', 'tags',
|
||||
'name', 'slug', 'color', 'description', 'tags',
|
||||
]
|
||||
|
||||
|
||||
class VirtualCircuitForm(TenancyForm, PrimaryModelForm):
|
||||
class VirtualCircuitForm(TenancyForm, NetBoxModelForm):
|
||||
provider_network = DynamicModelChoiceField(
|
||||
label=_('Provider network'),
|
||||
queryset=ProviderNetwork.objects.all(),
|
||||
@@ -326,6 +336,7 @@ class VirtualCircuitForm(TenancyForm, PrimaryModelForm):
|
||||
queryset=VirtualCircuitType.objects.all(),
|
||||
quick_add=True
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
fieldsets = (
|
||||
FieldSet(
|
||||
@@ -339,7 +350,7 @@ class VirtualCircuitForm(TenancyForm, PrimaryModelForm):
|
||||
model = VirtualCircuit
|
||||
fields = [
|
||||
'cid', 'provider_network', 'provider_account', 'type', 'status', 'description', 'tenant_group', 'tenant',
|
||||
'owner', 'comments', 'tags',
|
||||
'comments', 'tags',
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import strawberry_django
|
||||
from circuits import models
|
||||
from dcim.graphql.mixins import CabledObjectMixin
|
||||
from extras.graphql.mixins import ContactsMixin, CustomFieldsMixin, TagsMixin
|
||||
from netbox.graphql.types import BaseObjectType, ObjectType, OrganizationalObjectType, PrimaryObjectType
|
||||
from netbox.graphql.types import BaseObjectType, NetBoxObjectType, ObjectType, OrganizationalObjectType
|
||||
from tenancy.graphql.types import TenantType
|
||||
from .filters import *
|
||||
|
||||
@@ -35,7 +35,8 @@ __all__ = (
|
||||
filters=ProviderFilter,
|
||||
pagination=True
|
||||
)
|
||||
class ProviderType(ContactsMixin, PrimaryObjectType):
|
||||
class ProviderType(NetBoxObjectType, ContactsMixin):
|
||||
|
||||
networks: List[Annotated["ProviderNetworkType", strawberry.lazy('circuits.graphql.types')]]
|
||||
circuits: List[Annotated["CircuitType", strawberry.lazy('circuits.graphql.types')]]
|
||||
asns: List[Annotated["ASNType", strawberry.lazy('ipam.graphql.types')]]
|
||||
@@ -48,8 +49,9 @@ class ProviderType(ContactsMixin, PrimaryObjectType):
|
||||
filters=ProviderAccountFilter,
|
||||
pagination=True
|
||||
)
|
||||
class ProviderAccountType(ContactsMixin, PrimaryObjectType):
|
||||
class ProviderAccountType(ContactsMixin, NetBoxObjectType):
|
||||
provider: Annotated["ProviderType", strawberry.lazy('circuits.graphql.types')]
|
||||
|
||||
circuits: List[Annotated["CircuitType", strawberry.lazy('circuits.graphql.types')]]
|
||||
|
||||
|
||||
@@ -59,8 +61,9 @@ class ProviderAccountType(ContactsMixin, PrimaryObjectType):
|
||||
filters=ProviderNetworkFilter,
|
||||
pagination=True
|
||||
)
|
||||
class ProviderNetworkType(PrimaryObjectType):
|
||||
class ProviderNetworkType(NetBoxObjectType):
|
||||
provider: Annotated["ProviderType", strawberry.lazy('circuits.graphql.types')]
|
||||
|
||||
circuit_terminations: List[Annotated["CircuitTerminationType", strawberry.lazy('circuits.graphql.types')]]
|
||||
|
||||
|
||||
@@ -102,13 +105,14 @@ class CircuitTypeType(OrganizationalObjectType):
|
||||
filters=CircuitFilter,
|
||||
pagination=True
|
||||
)
|
||||
class CircuitType(PrimaryObjectType, ContactsMixin):
|
||||
class CircuitType(NetBoxObjectType, ContactsMixin):
|
||||
provider: ProviderType
|
||||
provider_account: ProviderAccountType | None
|
||||
termination_a: CircuitTerminationType | None
|
||||
termination_z: CircuitTerminationType | None
|
||||
type: CircuitTypeType
|
||||
tenant: TenantType | None
|
||||
|
||||
terminations: List[CircuitTerminationType]
|
||||
|
||||
|
||||
@@ -174,11 +178,12 @@ class VirtualCircuitTerminationType(CustomFieldsMixin, TagsMixin, ObjectType):
|
||||
filters=VirtualCircuitFilter,
|
||||
pagination=True
|
||||
)
|
||||
class VirtualCircuitType(PrimaryObjectType):
|
||||
class VirtualCircuitType(NetBoxObjectType):
|
||||
provider_network: ProviderNetworkType = strawberry_django.field(select_related=["provider_network"])
|
||||
provider_account: ProviderAccountType | None
|
||||
type: Annotated["VirtualCircuitTypeType", strawberry.lazy('circuits.graphql.types')] = strawberry_django.field(
|
||||
select_related=["type"]
|
||||
)
|
||||
tenant: TenantType | None
|
||||
|
||||
terminations: List[VirtualCircuitTerminationType]
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('circuits', '0052_extend_circuit_abs_distance_upper_limit'),
|
||||
('users', '0015_owner'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='circuit',
|
||||
name='owner',
|
||||
field=models.ForeignKey(
|
||||
blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='circuitgroup',
|
||||
name='owner',
|
||||
field=models.ForeignKey(
|
||||
blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='circuittype',
|
||||
name='owner',
|
||||
field=models.ForeignKey(
|
||||
blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='provider',
|
||||
name='owner',
|
||||
field=models.ForeignKey(
|
||||
blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='provideraccount',
|
||||
name='owner',
|
||||
field=models.ForeignKey(
|
||||
blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='providernetwork',
|
||||
name='owner',
|
||||
field=models.ForeignKey(
|
||||
blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='virtualcircuit',
|
||||
name='owner',
|
||||
field=models.ForeignKey(
|
||||
blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='virtualcircuittype',
|
||||
name='owner',
|
||||
field=models.ForeignKey(
|
||||
blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -1,9 +1,11 @@
|
||||
import django_tables2 as tables
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
import django_tables2 as tables
|
||||
|
||||
from circuits.models import *
|
||||
from netbox.tables import NetBoxTable, OrganizationalModelTable, PrimaryModelTable, columns
|
||||
from tenancy.tables import ContactsColumnMixin, TenancyColumnsMixin
|
||||
|
||||
from netbox.tables import NetBoxTable, columns
|
||||
|
||||
from .columns import CommitRateColumn
|
||||
|
||||
__all__ = (
|
||||
@@ -22,7 +24,7 @@ CIRCUITTERMINATION_LINK = """
|
||||
"""
|
||||
|
||||
|
||||
class CircuitTypeTable(OrganizationalModelTable):
|
||||
class CircuitTypeTable(NetBoxTable):
|
||||
name = tables.Column(
|
||||
linkify=True,
|
||||
verbose_name=_('Name'),
|
||||
@@ -37,7 +39,7 @@ class CircuitTypeTable(OrganizationalModelTable):
|
||||
verbose_name=_('Circuits')
|
||||
)
|
||||
|
||||
class Meta(OrganizationalModelTable.Meta):
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = CircuitType
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'circuit_count', 'color', 'description', 'slug', 'tags', 'created', 'last_updated',
|
||||
@@ -46,7 +48,7 @@ class CircuitTypeTable(OrganizationalModelTable):
|
||||
default_columns = ('pk', 'name', 'circuit_count', 'color', 'description')
|
||||
|
||||
|
||||
class CircuitTable(TenancyColumnsMixin, ContactsColumnMixin, PrimaryModelTable):
|
||||
class CircuitTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
|
||||
cid = tables.Column(
|
||||
linkify=True,
|
||||
verbose_name=_('Circuit ID')
|
||||
@@ -77,6 +79,9 @@ class CircuitTable(TenancyColumnsMixin, ContactsColumnMixin, PrimaryModelTable):
|
||||
verbose_name=_('Commit Rate')
|
||||
)
|
||||
distance = columns.DistanceColumn()
|
||||
comments = columns.MarkdownColumn(
|
||||
verbose_name=_('Comments')
|
||||
)
|
||||
tags = columns.TagColumn(
|
||||
url_name='circuits:circuit_list'
|
||||
)
|
||||
@@ -85,7 +90,7 @@ class CircuitTable(TenancyColumnsMixin, ContactsColumnMixin, PrimaryModelTable):
|
||||
linkify_item=True
|
||||
)
|
||||
|
||||
class Meta(PrimaryModelTable.Meta):
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = Circuit
|
||||
fields = (
|
||||
'pk', 'id', 'cid', 'provider', 'provider_account', 'type', 'status', 'tenant', 'tenant_group',
|
||||
@@ -158,7 +163,7 @@ class CircuitTerminationTable(NetBoxTable):
|
||||
)
|
||||
|
||||
|
||||
class CircuitGroupTable(OrganizationalModelTable):
|
||||
class CircuitGroupTable(NetBoxTable):
|
||||
name = tables.Column(
|
||||
verbose_name=_('Name'),
|
||||
linkify=True
|
||||
@@ -172,7 +177,7 @@ class CircuitGroupTable(OrganizationalModelTable):
|
||||
url_name='circuits:circuitgroup_list'
|
||||
)
|
||||
|
||||
class Meta(OrganizationalModelTable.Meta):
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = CircuitGroup
|
||||
fields = (
|
||||
'pk', 'name', 'description', 'circuit_group_assignment_count', 'tags',
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import django_tables2 as tables
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django_tables2.utils import Accessor
|
||||
|
||||
from circuits.models import *
|
||||
from netbox.tables import PrimaryModelTable, columns
|
||||
from django_tables2.utils import Accessor
|
||||
from tenancy.tables import ContactsColumnMixin
|
||||
|
||||
from netbox.tables import NetBoxTable, columns
|
||||
|
||||
__all__ = (
|
||||
'ProviderTable',
|
||||
'ProviderAccountTable',
|
||||
@@ -13,7 +13,7 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class ProviderTable(ContactsColumnMixin, PrimaryModelTable):
|
||||
class ProviderTable(ContactsColumnMixin, NetBoxTable):
|
||||
name = tables.Column(
|
||||
verbose_name=_('Name'),
|
||||
linkify=True
|
||||
@@ -42,11 +42,14 @@ class ProviderTable(ContactsColumnMixin, PrimaryModelTable):
|
||||
url_params={'provider_id': 'pk'},
|
||||
verbose_name=_('Circuits')
|
||||
)
|
||||
comments = columns.MarkdownColumn(
|
||||
verbose_name=_('Comments'),
|
||||
)
|
||||
tags = columns.TagColumn(
|
||||
url_name='circuits:provider_list'
|
||||
)
|
||||
|
||||
class Meta(PrimaryModelTable.Meta):
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = Provider
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'accounts', 'account_count', 'asns', 'asn_count', 'circuit_count', 'description',
|
||||
@@ -55,7 +58,7 @@ class ProviderTable(ContactsColumnMixin, PrimaryModelTable):
|
||||
default_columns = ('pk', 'name', 'account_count', 'circuit_count')
|
||||
|
||||
|
||||
class ProviderAccountTable(ContactsColumnMixin, PrimaryModelTable):
|
||||
class ProviderAccountTable(ContactsColumnMixin, NetBoxTable):
|
||||
account = tables.Column(
|
||||
linkify=True,
|
||||
verbose_name=_('Account'),
|
||||
@@ -73,11 +76,14 @@ class ProviderAccountTable(ContactsColumnMixin, PrimaryModelTable):
|
||||
url_params={'provider_account_id': 'pk'},
|
||||
verbose_name=_('Circuits')
|
||||
)
|
||||
comments = columns.MarkdownColumn(
|
||||
verbose_name=_('Comments'),
|
||||
)
|
||||
tags = columns.TagColumn(
|
||||
url_name='circuits:provideraccount_list'
|
||||
)
|
||||
|
||||
class Meta(PrimaryModelTable.Meta):
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = ProviderAccount
|
||||
fields = (
|
||||
'pk', 'id', 'account', 'name', 'provider', 'circuit_count', 'comments', 'contacts', 'tags', 'created',
|
||||
@@ -86,7 +92,7 @@ class ProviderAccountTable(ContactsColumnMixin, PrimaryModelTable):
|
||||
default_columns = ('pk', 'account', 'name', 'provider', 'circuit_count')
|
||||
|
||||
|
||||
class ProviderNetworkTable(PrimaryModelTable):
|
||||
class ProviderNetworkTable(NetBoxTable):
|
||||
name = tables.Column(
|
||||
verbose_name=_('Name'),
|
||||
linkify=True
|
||||
@@ -95,11 +101,14 @@ class ProviderNetworkTable(PrimaryModelTable):
|
||||
verbose_name=_('Provider'),
|
||||
linkify=True
|
||||
)
|
||||
comments = columns.MarkdownColumn(
|
||||
verbose_name=_('Comments'),
|
||||
)
|
||||
tags = columns.TagColumn(
|
||||
url_name='circuits:providernetwork_list'
|
||||
)
|
||||
|
||||
class Meta(PrimaryModelTable.Meta):
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = ProviderNetwork
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'provider', 'service_id', 'description', 'comments', 'created', 'last_updated', 'tags',
|
||||
|
||||
@@ -2,7 +2,7 @@ import django_tables2 as tables
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from circuits.models import *
|
||||
from netbox.tables import NetBoxTable, OrganizationalModelTable, PrimaryModelTable, columns
|
||||
from netbox.tables import NetBoxTable, columns
|
||||
from tenancy.tables import ContactsColumnMixin, TenancyColumnsMixin
|
||||
|
||||
__all__ = (
|
||||
@@ -12,7 +12,7 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class VirtualCircuitTypeTable(OrganizationalModelTable):
|
||||
class VirtualCircuitTypeTable(NetBoxTable):
|
||||
name = tables.Column(
|
||||
linkify=True,
|
||||
verbose_name=_('Name'),
|
||||
@@ -27,7 +27,7 @@ class VirtualCircuitTypeTable(OrganizationalModelTable):
|
||||
verbose_name=_('Circuits')
|
||||
)
|
||||
|
||||
class Meta(OrganizationalModelTable.Meta):
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = VirtualCircuitType
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'virtual_circuit_count', 'color', 'description', 'slug', 'tags', 'created',
|
||||
@@ -36,7 +36,7 @@ class VirtualCircuitTypeTable(OrganizationalModelTable):
|
||||
default_columns = ('pk', 'name', 'virtual_circuit_count', 'color', 'description')
|
||||
|
||||
|
||||
class VirtualCircuitTable(TenancyColumnsMixin, ContactsColumnMixin, PrimaryModelTable):
|
||||
class VirtualCircuitTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
|
||||
cid = tables.Column(
|
||||
linkify=True,
|
||||
verbose_name=_('Circuit ID')
|
||||
@@ -63,11 +63,14 @@ class VirtualCircuitTable(TenancyColumnsMixin, ContactsColumnMixin, PrimaryModel
|
||||
url_params={'virtual_circuit_id': 'pk'},
|
||||
verbose_name=_('Terminations')
|
||||
)
|
||||
comments = columns.MarkdownColumn(
|
||||
verbose_name=_('Comments')
|
||||
)
|
||||
tags = columns.TagColumn(
|
||||
url_name='circuits:virtualcircuit_list'
|
||||
)
|
||||
|
||||
class Meta(PrimaryModelTable.Meta):
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = VirtualCircuit
|
||||
fields = (
|
||||
'pk', 'id', 'cid', 'provider', 'provider_account', 'provider_network', 'type', 'status', 'tenant',
|
||||
|
||||
@@ -18,6 +18,11 @@ urlpatterns = [
|
||||
path('circuit-types/<int:pk>/', include(get_model_urls('circuits', 'circuittype'))),
|
||||
|
||||
path('circuits/', include(get_model_urls('circuits', 'circuit', detail=False))),
|
||||
path(
|
||||
'circuits/<int:pk>/terminations/swap/',
|
||||
views.CircuitSwapTerminations.as_view(),
|
||||
name='circuit_terminations_swap'
|
||||
),
|
||||
path('circuits/<int:pk>/', include(get_model_urls('circuits', 'circuit'))),
|
||||
|
||||
path('circuit-terminations/', include(get_model_urls('circuits', 'circuittermination', detail=False))),
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
from django.contrib import messages
|
||||
from django.db import router, transaction
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from dcim.views import PathTraceView
|
||||
from ipam.models import ASN
|
||||
from netbox.object_actions import AddObject, BulkDelete, BulkEdit, BulkExport, BulkImport
|
||||
from netbox.views import generic
|
||||
from utilities.forms import ConfirmationForm
|
||||
from utilities.query import count_related
|
||||
from utilities.views import GetRelatedModelsMixin, register_model_view
|
||||
from . import filtersets, forms, tables
|
||||
@@ -368,6 +373,82 @@ class CircuitBulkDeleteView(generic.BulkDeleteView):
|
||||
table = tables.CircuitTable
|
||||
|
||||
|
||||
class CircuitSwapTerminations(generic.ObjectEditView):
|
||||
"""
|
||||
Swap the A and Z terminations of a circuit.
|
||||
"""
|
||||
queryset = Circuit.objects.all()
|
||||
|
||||
def get(self, request, pk):
|
||||
circuit = get_object_or_404(self.queryset, pk=pk)
|
||||
form = ConfirmationForm()
|
||||
|
||||
# Circuit must have at least one termination to swap
|
||||
if not circuit.termination_a and not circuit.termination_z:
|
||||
messages.error(request, _(
|
||||
"No terminations have been defined for circuit {circuit}."
|
||||
).format(circuit=circuit))
|
||||
return redirect('circuits:circuit', pk=circuit.pk)
|
||||
|
||||
return render(request, 'circuits/circuit_terminations_swap.html', {
|
||||
'circuit': circuit,
|
||||
'termination_a': circuit.termination_a,
|
||||
'termination_z': circuit.termination_z,
|
||||
'form': form,
|
||||
'panel_class': 'light',
|
||||
'button_class': 'primary',
|
||||
'return_url': circuit.get_absolute_url(),
|
||||
})
|
||||
|
||||
def post(self, request, pk):
|
||||
circuit = get_object_or_404(self.queryset, pk=pk)
|
||||
form = ConfirmationForm(request.POST)
|
||||
|
||||
if form.is_valid():
|
||||
|
||||
termination_a = CircuitTermination.objects.filter(pk=circuit.termination_a_id).first()
|
||||
termination_z = CircuitTermination.objects.filter(pk=circuit.termination_z_id).first()
|
||||
|
||||
if termination_a and termination_z:
|
||||
# Use a placeholder to avoid an IntegrityError on the (circuit, term_side) unique constraint
|
||||
with transaction.atomic(using=router.db_for_write(CircuitTermination)):
|
||||
termination_a.term_side = '_'
|
||||
termination_a.save()
|
||||
termination_z.term_side = 'A'
|
||||
termination_z.save()
|
||||
termination_a.term_side = 'Z'
|
||||
termination_a.save()
|
||||
circuit.refresh_from_db()
|
||||
circuit.termination_a = termination_z
|
||||
circuit.termination_z = termination_a
|
||||
circuit.save()
|
||||
elif termination_a:
|
||||
termination_a.term_side = 'Z'
|
||||
termination_a.save()
|
||||
circuit.refresh_from_db()
|
||||
circuit.termination_a = None
|
||||
circuit.save()
|
||||
else:
|
||||
termination_z.term_side = 'A'
|
||||
termination_z.save()
|
||||
circuit.refresh_from_db()
|
||||
circuit.termination_z = None
|
||||
circuit.save()
|
||||
|
||||
messages.success(request, _("Swapped terminations for circuit {circuit}.").format(circuit=circuit))
|
||||
return redirect('circuits:circuit', pk=circuit.pk)
|
||||
|
||||
return render(request, 'circuits/circuit_terminations_swap.html', {
|
||||
'circuit': circuit,
|
||||
'termination_a': circuit.termination_a,
|
||||
'termination_z': circuit.termination_z,
|
||||
'form': form,
|
||||
'panel_class': 'default',
|
||||
'button_class': 'primary',
|
||||
'return_url': circuit.get_absolute_url(),
|
||||
})
|
||||
|
||||
|
||||
#
|
||||
# Circuit terminations
|
||||
#
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from core.choices import *
|
||||
from core.models import DataFile, DataSource
|
||||
from netbox.api.fields import ChoiceField, RelatedObjectCountField
|
||||
from netbox.api.serializers import NetBoxModelSerializer, PrimaryModelSerializer
|
||||
from netbox.api.serializers import NetBoxModelSerializer
|
||||
from netbox.utils import get_data_backend_choices
|
||||
|
||||
__all__ = (
|
||||
@@ -10,7 +10,7 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class DataSourceSerializer(PrimaryModelSerializer):
|
||||
class DataSourceSerializer(NetBoxModelSerializer):
|
||||
type = ChoiceField(
|
||||
choices=get_data_backend_choices()
|
||||
)
|
||||
@@ -26,8 +26,8 @@ class DataSourceSerializer(PrimaryModelSerializer):
|
||||
model = DataSource
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'name', 'type', 'source_url', 'enabled', 'status', 'description',
|
||||
'sync_interval', 'parameters', 'ignore_rules', 'owner', 'comments', 'custom_fields', 'created',
|
||||
'last_updated', 'last_synced', 'file_count',
|
||||
'sync_interval', 'parameters', 'ignore_rules', 'comments', 'custom_fields', 'created', 'last_updated',
|
||||
'last_synced', 'file_count',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'name', 'description')
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ from drf_spectacular.utils import OpenApiParameter, extend_schema
|
||||
from rest_framework import viewsets
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
from rest_framework.permissions import IsAdminUser
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.routers import APIRootView
|
||||
from rest_framework.viewsets import ReadOnlyModelViewSet
|
||||
@@ -23,7 +24,7 @@ from netbox.api.authentication import IsAuthenticatedOrLoginNotRequired
|
||||
from netbox.api.metadata import ContentTypeMetadata
|
||||
from netbox.api.pagination import LimitOffsetListPagination
|
||||
from netbox.api.viewsets import NetBoxModelViewSet, NetBoxReadOnlyModelViewSet
|
||||
from utilities.api import IsSuperuser
|
||||
|
||||
from . import serializers
|
||||
|
||||
|
||||
@@ -99,7 +100,7 @@ class BaseRQViewSet(viewsets.ViewSet):
|
||||
"""
|
||||
Base class for RQ view sets. Provides a list() method. Subclasses must implement get_data().
|
||||
"""
|
||||
permission_classes = [IsSuperuser]
|
||||
permission_classes = [IsAdminUser]
|
||||
serializer_class = None
|
||||
|
||||
def get_data(self):
|
||||
|
||||
@@ -3,7 +3,7 @@ from django.contrib.contenttypes.models import ContentType
|
||||
from django.db.models import Q
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from netbox.filtersets import BaseFilterSet, ChangeLoggedModelFilterSet, PrimaryModelFilterSet
|
||||
from netbox.filtersets import BaseFilterSet, ChangeLoggedModelFilterSet, NetBoxModelFilterSet
|
||||
from netbox.utils import get_data_backend_choices
|
||||
from users.models import User
|
||||
from utilities.filters import ContentTypeFilter
|
||||
@@ -20,7 +20,7 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class DataSourceFilterSet(PrimaryModelFilterSet):
|
||||
class DataSourceFilterSet(NetBoxModelFilterSet):
|
||||
type = django_filters.MultipleChoiceFilter(
|
||||
choices=get_data_backend_choices,
|
||||
null_value=None
|
||||
|
||||
@@ -3,8 +3,9 @@ from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from core.choices import JobIntervalChoices
|
||||
from core.models import *
|
||||
from netbox.forms import PrimaryModelBulkEditForm
|
||||
from netbox.forms import NetBoxModelBulkEditForm
|
||||
from netbox.utils import get_data_backend_choices
|
||||
from utilities.forms.fields import CommentField
|
||||
from utilities.forms.rendering import FieldSet
|
||||
from utilities.forms.widgets import BulkEditNullBooleanSelect
|
||||
|
||||
@@ -13,7 +14,7 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class DataSourceBulkEditForm(PrimaryModelBulkEditForm):
|
||||
class DataSourceBulkEditForm(NetBoxModelBulkEditForm):
|
||||
type = forms.ChoiceField(
|
||||
label=_('Type'),
|
||||
choices=get_data_backend_choices,
|
||||
@@ -24,11 +25,17 @@ class DataSourceBulkEditForm(PrimaryModelBulkEditForm):
|
||||
widget=BulkEditNullBooleanSelect(),
|
||||
label=_('Enabled')
|
||||
)
|
||||
description = forms.CharField(
|
||||
label=_('Description'),
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
sync_interval = forms.ChoiceField(
|
||||
choices=JobIntervalChoices,
|
||||
required=False,
|
||||
label=_('Sync interval')
|
||||
)
|
||||
comments = CommentField()
|
||||
parameters = forms.JSONField(
|
||||
label=_('Parameters'),
|
||||
required=False
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
from core.models import *
|
||||
from netbox.forms import PrimaryModelImportForm
|
||||
from netbox.forms import NetBoxModelImportForm
|
||||
|
||||
__all__ = (
|
||||
'DataSourceImportForm',
|
||||
)
|
||||
|
||||
|
||||
class DataSourceImportForm(PrimaryModelImportForm):
|
||||
class DataSourceImportForm(NetBoxModelImportForm):
|
||||
|
||||
class Meta:
|
||||
model = DataSource
|
||||
fields = (
|
||||
'name', 'type', 'source_url', 'enabled', 'description', 'sync_interval', 'parameters', 'ignore_rules',
|
||||
'owner', 'comments',
|
||||
'comments',
|
||||
)
|
||||
|
||||
@@ -3,13 +3,13 @@ from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from core.choices import *
|
||||
from core.models import *
|
||||
from netbox.forms import NetBoxModelFilterSetForm, PrimaryModelFilterSetForm
|
||||
from netbox.forms import NetBoxModelFilterSetForm
|
||||
from netbox.forms.mixins import SavedFiltersMixin
|
||||
from netbox.utils import get_data_backend_choices
|
||||
from users.models import User
|
||||
from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES, FilterForm, add_blank_choice
|
||||
from utilities.forms.fields import (
|
||||
ContentTypeChoiceField, ContentTypeMultipleChoiceField, DynamicModelMultipleChoiceField, TagFilterField,
|
||||
ContentTypeChoiceField, ContentTypeMultipleChoiceField, DynamicModelMultipleChoiceField,
|
||||
)
|
||||
from utilities.forms.rendering import FieldSet
|
||||
from utilities.forms.widgets import DateTimePicker
|
||||
@@ -23,10 +23,10 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class DataSourceFilterForm(PrimaryModelFilterSetForm):
|
||||
class DataSourceFilterForm(NetBoxModelFilterSetForm):
|
||||
model = DataSource
|
||||
fieldsets = (
|
||||
FieldSet('q', 'filter_id', 'tag', 'owner_id'),
|
||||
FieldSet('q', 'filter_id'),
|
||||
FieldSet('type', 'status', 'enabled', 'sync_interval', name=_('Data Source')),
|
||||
)
|
||||
type = forms.MultipleChoiceField(
|
||||
@@ -51,7 +51,6 @@ class DataSourceFilterForm(PrimaryModelFilterSetForm):
|
||||
choices=JobIntervalChoices,
|
||||
required=False
|
||||
)
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class DataFileFilterForm(NetBoxModelFilterSetForm):
|
||||
|
||||
@@ -9,11 +9,11 @@ from django.utils.translation import gettext_lazy as _
|
||||
from core.forms.mixins import SyncedDataMixin
|
||||
from core.models import *
|
||||
from netbox.config import get_config, PARAMS
|
||||
from netbox.forms import NetBoxModelForm, PrimaryModelForm
|
||||
from netbox.forms import NetBoxModelForm
|
||||
from netbox.registry import registry
|
||||
from netbox.utils import get_data_backend_choices
|
||||
from utilities.forms import get_field_value
|
||||
from utilities.forms.fields import JSONField
|
||||
from utilities.forms.fields import CommentField, JSONField
|
||||
from utilities.forms.rendering import FieldSet
|
||||
from utilities.forms.widgets import HTMXSelect
|
||||
|
||||
@@ -26,17 +26,17 @@ __all__ = (
|
||||
EMPTY_VALUES = ('', None, [], ())
|
||||
|
||||
|
||||
class DataSourceForm(PrimaryModelForm):
|
||||
class DataSourceForm(NetBoxModelForm):
|
||||
type = forms.ChoiceField(
|
||||
choices=get_data_backend_choices,
|
||||
widget=HTMXSelect()
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
class Meta:
|
||||
model = DataSource
|
||||
fields = [
|
||||
'name', 'type', 'source_url', 'enabled', 'description', 'sync_interval', 'ignore_rules', 'owner',
|
||||
'comments', 'tags',
|
||||
'name', 'type', 'source_url', 'enabled', 'description', 'sync_interval', 'ignore_rules', 'comments', 'tags',
|
||||
]
|
||||
widgets = {
|
||||
'ignore_rules': forms.Textarea(
|
||||
|
||||
@@ -5,7 +5,7 @@ import strawberry_django
|
||||
from django.contrib.contenttypes.models import ContentType as DjangoContentType
|
||||
|
||||
from core import models
|
||||
from netbox.graphql.types import BaseObjectType, PrimaryObjectType
|
||||
from netbox.graphql.types import BaseObjectType, NetBoxObjectType
|
||||
from .filters import *
|
||||
|
||||
__all__ = (
|
||||
@@ -32,7 +32,8 @@ class DataFileType(BaseObjectType):
|
||||
filters=DataSourceFilter,
|
||||
pagination=True
|
||||
)
|
||||
class DataSourceType(PrimaryObjectType):
|
||||
class DataSourceType(NetBoxObjectType):
|
||||
|
||||
datafiles: List[Annotated["DataFileType", strawberry.lazy('core.graphql.types')]]
|
||||
|
||||
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('core', '0019_configrevision_active'),
|
||||
('users', '0015_owner'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='datasource',
|
||||
name='owner',
|
||||
field=models.ForeignKey(
|
||||
blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
|
||||
),
|
||||
),
|
||||
]
|
||||
3
netbox/core/models/contenttypes.py
Normal file
3
netbox/core/models/contenttypes.py
Normal file
@@ -0,0 +1,3 @@
|
||||
# TODO: Remove this module in NetBox v4.5
|
||||
# Provided for backward compatibility
|
||||
from .object_types import *
|
||||
@@ -2,7 +2,7 @@ from django.utils.translation import gettext_lazy as _
|
||||
import django_tables2 as tables
|
||||
|
||||
from core.models import *
|
||||
from netbox.tables import NetBoxTable, PrimaryModelTable, columns
|
||||
from netbox.tables import NetBoxTable, columns
|
||||
from .columns import BackendTypeColumn
|
||||
from .template_code import DATA_SOURCE_SYNC_BUTTON
|
||||
|
||||
@@ -12,7 +12,7 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class DataSourceTable(PrimaryModelTable):
|
||||
class DataSourceTable(NetBoxTable):
|
||||
name = tables.Column(
|
||||
verbose_name=_('Name'),
|
||||
linkify=True,
|
||||
@@ -42,7 +42,7 @@ class DataSourceTable(PrimaryModelTable):
|
||||
extra_buttons=DATA_SOURCE_SYNC_BUTTON,
|
||||
)
|
||||
|
||||
class Meta(PrimaryModelTable.Meta):
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = DataSource
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'type', 'status', 'enabled', 'source_url', 'description', 'sync_interval', 'comments',
|
||||
|
||||
@@ -8,7 +8,6 @@ from rq.job import Job as RQ_Job, JobStatus
|
||||
from rq.registry import FailedJobRegistry, StartedJobRegistry
|
||||
|
||||
from rest_framework import status
|
||||
from users.constants import TOKEN_PREFIX
|
||||
from users.models import Token, User
|
||||
from utilities.testing import APITestCase, APIViewTestCases, TestCase
|
||||
from utilities.testing.utils import disable_logging
|
||||
@@ -108,14 +107,14 @@ class ObjectTypeTest(APITestCase):
|
||||
def test_list_objects(self):
|
||||
object_type_count = ObjectType.objects.count()
|
||||
|
||||
response = self.client.get(reverse('core-api:objecttype-list'), **self.header)
|
||||
response = self.client.get(reverse('extras-api:objecttype-list'), **self.header)
|
||||
self.assertHttpStatus(response, status.HTTP_200_OK)
|
||||
self.assertEqual(response.data['count'], object_type_count)
|
||||
|
||||
def test_get_object(self):
|
||||
object_type = ObjectType.objects.first()
|
||||
|
||||
url = reverse('core-api:objecttype-detail', kwargs={'pk': object_type.pk})
|
||||
url = reverse('extras-api:objecttype-detail', kwargs={'pk': object_type.pk})
|
||||
self.assertHttpStatus(self.client.get(url, **self.header), status.HTTP_200_OK)
|
||||
|
||||
|
||||
@@ -135,9 +134,12 @@ class BackgroundTaskTestCase(TestCase):
|
||||
Create a user and token for API calls.
|
||||
"""
|
||||
# Create the test user and assign permissions
|
||||
self.user = User.objects.create_user(username='testuser', is_active=True)
|
||||
self.user = User.objects.create_user(username='testuser')
|
||||
self.user.is_staff = True
|
||||
self.user.is_active = True
|
||||
self.user.save()
|
||||
self.token = Token.objects.create(user=self.user)
|
||||
self.header = {'HTTP_AUTHORIZATION': f'Bearer {TOKEN_PREFIX}{self.token.key}.{self.token.token}'}
|
||||
self.header = {'HTTP_AUTHORIZATION': f'Token {self.token.key}'}
|
||||
|
||||
# Clear all queues prior to running each test
|
||||
get_queue('default').connection.flushall()
|
||||
@@ -148,11 +150,13 @@ class BackgroundTaskTestCase(TestCase):
|
||||
url = reverse('core-api:rqqueue-list')
|
||||
|
||||
# Attempt to load view without permission
|
||||
self.user.is_staff = False
|
||||
self.user.save()
|
||||
response = self.client.get(url, **self.header)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
# Load view with permission
|
||||
self.user.is_superuser = True
|
||||
self.user.is_staff = True
|
||||
self.user.save()
|
||||
response = self.client.get(url, **self.header)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
@@ -161,16 +165,7 @@ class BackgroundTaskTestCase(TestCase):
|
||||
self.assertIn('low', str(response.content))
|
||||
|
||||
def test_background_queue(self):
|
||||
url = reverse('core-api:rqqueue-detail', args=['default'])
|
||||
|
||||
# Attempt to load view without permission
|
||||
response = self.client.get(url, **self.header)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
# Load view with permission
|
||||
self.user.is_superuser = True
|
||||
self.user.save()
|
||||
response = self.client.get(url, **self.header)
|
||||
response = self.client.get(reverse('core-api:rqqueue-detail', args=['default']), **self.header)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn('default', str(response.content))
|
||||
self.assertIn('oldest_job_timestamp', str(response.content))
|
||||
@@ -179,16 +174,8 @@ class BackgroundTaskTestCase(TestCase):
|
||||
def test_background_task_list(self):
|
||||
queue = get_queue('default')
|
||||
queue.enqueue(self.dummy_job_default)
|
||||
url = reverse('core-api:rqtask-list')
|
||||
|
||||
# Attempt to load view without permission
|
||||
response = self.client.get(url, **self.header)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
# Load view with permission
|
||||
self.user.is_superuser = True
|
||||
self.user.save()
|
||||
response = self.client.get(url, **self.header)
|
||||
response = self.client.get(reverse('core-api:rqtask-list'), **self.header)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn('origin', str(response.content))
|
||||
self.assertIn('core.tests.test_api.BackgroundTaskTestCase.dummy_job_default()', str(response.content))
|
||||
@@ -196,16 +183,8 @@ class BackgroundTaskTestCase(TestCase):
|
||||
def test_background_task(self):
|
||||
queue = get_queue('default')
|
||||
job = queue.enqueue(self.dummy_job_default)
|
||||
url = reverse('core-api:rqtask-detail', args=[job.id])
|
||||
|
||||
# Attempt to load view without permission
|
||||
response = self.client.get(url, **self.header)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
# Load view with permission
|
||||
self.user.is_superuser = True
|
||||
self.user.save()
|
||||
response = self.client.get(url, **self.header)
|
||||
response = self.client.get(reverse('core-api:rqtask-detail', args=[job.id]), **self.header)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn(str(job.id), str(response.content))
|
||||
self.assertIn('origin', str(response.content))
|
||||
@@ -215,65 +194,45 @@ class BackgroundTaskTestCase(TestCase):
|
||||
def test_background_task_delete(self):
|
||||
queue = get_queue('default')
|
||||
job = queue.enqueue(self.dummy_job_default)
|
||||
url = reverse('core-api:rqtask-delete', args=[job.id])
|
||||
|
||||
# Attempt to load view without permission
|
||||
response = self.client.get(url, **self.header)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
# Load view with permission
|
||||
self.user.is_superuser = True
|
||||
self.user.save()
|
||||
response = self.client.post(url, **self.header)
|
||||
response = self.client.post(reverse('core-api:rqtask-delete', args=[job.id]), **self.header)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertFalse(RQ_Job.exists(job.id, connection=queue.connection))
|
||||
queue = get_queue('default')
|
||||
self.assertNotIn(job.id, queue.job_ids)
|
||||
|
||||
def test_background_task_requeue(self):
|
||||
# Enqueue & run a job that will fail
|
||||
queue = get_queue('default')
|
||||
|
||||
# Enqueue & run a job that will fail
|
||||
job = queue.enqueue(self.dummy_job_failing)
|
||||
worker = get_worker('default')
|
||||
with disable_logging():
|
||||
worker.work(burst=True)
|
||||
self.assertTrue(job.is_failed)
|
||||
url = reverse('core-api:rqtask-requeue', args=[job.id])
|
||||
|
||||
# Attempt to requeue the job without permission
|
||||
response = self.client.post(url, **self.header)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
# Re-enqueue the failed job and check that its status has been reset
|
||||
self.user.is_superuser = True
|
||||
self.user.save()
|
||||
response = self.client.post(url, **self.header)
|
||||
response = self.client.post(reverse('core-api:rqtask-requeue', args=[job.id]), **self.header)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
job = RQ_Job.fetch(job.id, queue.connection)
|
||||
self.assertFalse(job.is_failed)
|
||||
|
||||
def test_background_task_enqueue(self):
|
||||
# Enqueue some jobs that each depends on its predecessor
|
||||
queue = get_queue('default')
|
||||
|
||||
# Enqueue some jobs that each depends on its predecessor
|
||||
job = previous_job = None
|
||||
for _ in range(0, 3):
|
||||
job = queue.enqueue(self.dummy_job_default, depends_on=previous_job)
|
||||
previous_job = job
|
||||
url = reverse('core-api:rqtask-enqueue', args=[job.id])
|
||||
|
||||
# Check that the last job to be enqueued has a status of deferred
|
||||
self.assertIsNotNone(job)
|
||||
self.assertEqual(job.get_status(), JobStatus.DEFERRED)
|
||||
self.assertIsNone(job.enqueued_at)
|
||||
|
||||
# Attempt to force-enqueue the job without permission
|
||||
response = self.client.post(url, **self.header)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
# Force-enqueue the deferred job
|
||||
self.user.is_superuser = True
|
||||
self.user.save()
|
||||
response = self.client.post(url, **self.header)
|
||||
response = self.client.post(reverse('core-api:rqtask-enqueue', args=[job.id]), **self.header)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
# Check that job's status is updated correctly
|
||||
@@ -283,27 +242,19 @@ class BackgroundTaskTestCase(TestCase):
|
||||
|
||||
def test_background_task_stop(self):
|
||||
queue = get_queue('default')
|
||||
|
||||
worker = get_worker('default')
|
||||
job = queue.enqueue(self.dummy_job_default)
|
||||
worker.prepare_job_execution(job)
|
||||
url = reverse('core-api:rqtask-stop', args=[job.id])
|
||||
|
||||
self.assertEqual(job.get_status(), JobStatus.STARTED)
|
||||
|
||||
# Attempt to stop the task without permission
|
||||
response = self.client.post(url, **self.header)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
# Stop the task
|
||||
self.user.is_superuser = True
|
||||
self.user.save()
|
||||
response = self.client.post(url, **self.header)
|
||||
response = self.client.post(reverse('core-api:rqtask-stop', args=[job.id]), **self.header)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
with disable_logging():
|
||||
worker.monitor_work_horse(job, queue) # Sets the job as Failed and removes from Started
|
||||
started_job_registry = StartedJobRegistry(queue.name, connection=queue.connection)
|
||||
self.assertEqual(len(started_job_registry), 0)
|
||||
|
||||
# Verify that the task was cancelled
|
||||
canceled_job_registry = FailedJobRegistry(queue.name, connection=queue.connection)
|
||||
self.assertEqual(len(canceled_job_registry), 1)
|
||||
self.assertIn(job.id, canceled_job_registry)
|
||||
@@ -311,34 +262,19 @@ class BackgroundTaskTestCase(TestCase):
|
||||
def test_worker_list(self):
|
||||
worker1 = get_worker('default', name=uuid.uuid4().hex)
|
||||
worker1.register_birth()
|
||||
|
||||
worker2 = get_worker('high')
|
||||
worker2.register_birth()
|
||||
url = reverse('core-api:rqworker-list')
|
||||
|
||||
# Attempt to fetch the worker list without permission
|
||||
response = self.client.get(url, **self.header)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
# Fetch the worker list
|
||||
self.user.is_superuser = True
|
||||
self.user.save()
|
||||
response = self.client.get(url, **self.header)
|
||||
response = self.client.get(reverse('core-api:rqworker-list'), **self.header)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn(str(worker1.name), str(response.content))
|
||||
|
||||
def test_worker(self):
|
||||
worker1 = get_worker('default', name=uuid.uuid4().hex)
|
||||
worker1.register_birth()
|
||||
url = reverse('core-api:rqworker-detail', args=[worker1.name])
|
||||
|
||||
# Attempt to fetch a worker without permission
|
||||
response = self.client.get(url, **self.header)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
# Fetch the worker
|
||||
self.user.is_superuser = True
|
||||
self.user.save()
|
||||
response = self.client.get(url, **self.header)
|
||||
response = self.client.get(reverse('core-api:rqworker-detail', args=[worker1.name]), **self.header)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn(str(worker1.name), str(response.content))
|
||||
self.assertIn('birth_date', str(response.content))
|
||||
|
||||
@@ -158,7 +158,7 @@ class BackgroundTaskTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.user.is_superuser = True
|
||||
self.user.is_staff = True
|
||||
self.user.is_active = True
|
||||
self.user.save()
|
||||
|
||||
@@ -171,13 +171,13 @@ class BackgroundTaskTestCase(TestCase):
|
||||
url = reverse('core:background_queue_list')
|
||||
|
||||
# Attempt to load view without permission
|
||||
self.user.is_superuser = False
|
||||
self.user.is_staff = False
|
||||
self.user.save()
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
# Load view with permission
|
||||
self.user.is_superuser = True
|
||||
self.user.is_staff = True
|
||||
self.user.save()
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
@@ -356,7 +356,7 @@ class SystemTestCase(TestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.user.is_superuser = True
|
||||
self.user.is_staff = True
|
||||
self.user.save()
|
||||
|
||||
def test_system_view_default(self):
|
||||
|
||||
@@ -372,7 +372,7 @@ class ConfigRevisionRestoreView(ContentTypePermissionRequiredMixin, View):
|
||||
class BaseRQView(UserPassesTestMixin, View):
|
||||
|
||||
def test_func(self):
|
||||
return self.request.user.is_superuser
|
||||
return self.request.user.is_staff
|
||||
|
||||
|
||||
class BackgroundQueueListView(TableMixin, BaseRQView):
|
||||
@@ -555,7 +555,7 @@ class WorkerView(BaseRQView):
|
||||
class SystemView(UserPassesTestMixin, View):
|
||||
|
||||
def test_func(self):
|
||||
return self.request.user.is_superuser
|
||||
return self.request.user.is_staff
|
||||
|
||||
def get(self, request):
|
||||
|
||||
@@ -638,7 +638,7 @@ class BasePluginView(UserPassesTestMixin, View):
|
||||
CACHE_KEY_CATALOG_ERROR = 'plugins-catalog-error'
|
||||
|
||||
def test_func(self):
|
||||
return self.request.user.is_superuser
|
||||
return self.request.user.is_staff
|
||||
|
||||
def get_cached_plugins(self, request):
|
||||
catalog_plugins = {}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from drf_spectacular.types import OpenApiTypes
|
||||
from drf_spectacular.utils import extend_schema_field
|
||||
from rest_framework import serializers
|
||||
|
||||
from dcim.choices import *
|
||||
from dcim.constants import *
|
||||
from dcim.models import Cable, CablePath, CableTermination
|
||||
from netbox.api.fields import ChoiceField, ContentTypeField
|
||||
from netbox.api.serializers import (
|
||||
BaseModelSerializer, GenericObjectSerializer, NetBoxModelSerializer, PrimaryModelSerializer,
|
||||
)
|
||||
from netbox.api.serializers import BaseModelSerializer, GenericObjectSerializer, NetBoxModelSerializer
|
||||
from tenancy.api.serializers_.tenants import TenantSerializer
|
||||
from utilities.api import get_serializer_for_model
|
||||
|
||||
@@ -20,7 +20,7 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class CableSerializer(PrimaryModelSerializer):
|
||||
class CableSerializer(NetBoxModelSerializer):
|
||||
a_terminations = GenericObjectSerializer(many=True, required=False)
|
||||
b_terminations = GenericObjectSerializer(many=True, required=False)
|
||||
status = ChoiceField(choices=LinkStatusChoices, required=False)
|
||||
@@ -31,8 +31,8 @@ class CableSerializer(PrimaryModelSerializer):
|
||||
model = Cable
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'type', 'a_terminations', 'b_terminations', 'status', 'tenant',
|
||||
'label', 'color', 'length', 'length_unit', 'description', 'owner', 'comments', 'tags', 'custom_fields',
|
||||
'created', 'last_updated',
|
||||
'label', 'color', 'length', 'length_unit', 'description', 'comments', 'tags', 'custom_fields', 'created',
|
||||
'last_updated',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'label', 'description')
|
||||
|
||||
@@ -51,11 +51,9 @@ class TracedCableSerializer(BaseModelSerializer):
|
||||
|
||||
class CableTerminationSerializer(NetBoxModelSerializer):
|
||||
termination_type = ContentTypeField(
|
||||
read_only=True,
|
||||
)
|
||||
termination = serializers.SerializerMethodField(
|
||||
read_only=True,
|
||||
queryset=ContentType.objects.filter(CABLE_TERMINATION_MODELS)
|
||||
)
|
||||
termination = serializers.SerializerMethodField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = CableTermination
|
||||
@@ -63,8 +61,6 @@ class CableTerminationSerializer(NetBoxModelSerializer):
|
||||
'id', 'url', 'display', 'cable', 'cable_end', 'termination_type', 'termination_id',
|
||||
'termination', 'created', 'last_updated',
|
||||
]
|
||||
read_only_fields = fields
|
||||
brief_fields = ('id', 'url', 'display', 'cable', 'cable_end', 'termination_type', 'termination_id')
|
||||
|
||||
@extend_schema_field(serializers.JSONField(allow_null=True))
|
||||
def get_termination(self, obj):
|
||||
|
||||
@@ -11,15 +11,15 @@ from dcim.models import Device, DeviceBay, MACAddress, Module, VirtualDeviceCont
|
||||
from extras.api.serializers_.configtemplates import ConfigTemplateSerializer
|
||||
from ipam.api.serializers_.ip import IPAddressSerializer
|
||||
from netbox.api.fields import ChoiceField, ContentTypeField, RelatedObjectCountField
|
||||
from netbox.api.serializers import PrimaryModelSerializer
|
||||
from netbox.api.serializers import NetBoxModelSerializer
|
||||
from tenancy.api.serializers_.tenants import TenantSerializer
|
||||
from utilities.api import get_serializer_for_model
|
||||
from virtualization.api.serializers_.clusters import ClusterSerializer
|
||||
from .devicetypes import *
|
||||
from .nested import NestedDeviceBaySerializer, NestedDeviceSerializer, NestedModuleBaySerializer
|
||||
from .platforms import PlatformSerializer
|
||||
from .racks import RackSerializer
|
||||
from .roles import DeviceRoleSerializer
|
||||
from .nested import NestedDeviceBaySerializer, NestedDeviceSerializer, NestedModuleBaySerializer
|
||||
from .sites import LocationSerializer, SiteSerializer
|
||||
from .virtualchassis import VirtualChassisSerializer
|
||||
|
||||
@@ -32,7 +32,7 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class DeviceSerializer(PrimaryModelSerializer):
|
||||
class DeviceSerializer(NetBoxModelSerializer):
|
||||
device_type = DeviceTypeSerializer(nested=True)
|
||||
role = DeviceRoleSerializer(nested=True)
|
||||
tenant = TenantSerializer(
|
||||
@@ -84,8 +84,8 @@ class DeviceSerializer(PrimaryModelSerializer):
|
||||
'id', 'url', 'display_url', 'display', 'name', 'device_type', 'role', 'tenant', 'platform', 'serial',
|
||||
'asset_tag', 'site', 'location', 'rack', 'position', 'face', 'latitude', 'longitude', 'parent_device',
|
||||
'status', 'airflow', 'primary_ip', 'primary_ip4', 'primary_ip6', 'oob_ip', 'cluster', 'virtual_chassis',
|
||||
'vc_position', 'vc_priority', 'description', 'owner', 'comments', 'config_template', 'local_context_data',
|
||||
'tags', 'custom_fields', 'created', 'last_updated', 'console_port_count', 'console_server_port_count',
|
||||
'vc_position', 'vc_priority', 'description', 'comments', 'config_template', 'local_context_data', 'tags',
|
||||
'custom_fields', 'created', 'last_updated', 'console_port_count', 'console_server_port_count',
|
||||
'power_port_count', 'power_outlet_count', 'interface_count', 'front_port_count', 'rear_port_count',
|
||||
'device_bay_count', 'module_bay_count', 'inventory_item_count',
|
||||
]
|
||||
@@ -122,7 +122,7 @@ class DeviceWithConfigContextSerializer(DeviceSerializer):
|
||||
return obj.get_config_context()
|
||||
|
||||
|
||||
class VirtualDeviceContextSerializer(PrimaryModelSerializer):
|
||||
class VirtualDeviceContextSerializer(NetBoxModelSerializer):
|
||||
device = DeviceSerializer(nested=True)
|
||||
identifier = serializers.IntegerField(allow_null=True, max_value=32767, min_value=0, required=False, default=None)
|
||||
tenant = TenantSerializer(nested=True, required=False, allow_null=True, default=None)
|
||||
@@ -138,13 +138,13 @@ class VirtualDeviceContextSerializer(PrimaryModelSerializer):
|
||||
model = VirtualDeviceContext
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'name', 'device', 'identifier', 'tenant', 'primary_ip',
|
||||
'primary_ip4', 'primary_ip6', 'status', 'description', 'owner', 'comments', 'tags', 'custom_fields',
|
||||
'primary_ip4', 'primary_ip6', 'status', 'description', 'comments', 'tags', 'custom_fields',
|
||||
'created', 'last_updated', 'interface_count',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'name', 'identifier', 'device', 'description')
|
||||
|
||||
|
||||
class ModuleSerializer(PrimaryModelSerializer):
|
||||
class ModuleSerializer(NetBoxModelSerializer):
|
||||
device = DeviceSerializer(nested=True)
|
||||
module_bay = NestedModuleBaySerializer()
|
||||
module_type = ModuleTypeSerializer(nested=True)
|
||||
@@ -154,12 +154,12 @@ class ModuleSerializer(PrimaryModelSerializer):
|
||||
model = Module
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'device', 'module_bay', 'module_type', 'status', 'serial',
|
||||
'asset_tag', 'description', 'owner', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
|
||||
'asset_tag', 'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'device', 'module_bay', 'module_type', 'description')
|
||||
|
||||
|
||||
class MACAddressSerializer(PrimaryModelSerializer):
|
||||
class MACAddressSerializer(NetBoxModelSerializer):
|
||||
assigned_object_type = ContentTypeField(
|
||||
queryset=ContentType.objects.filter(MACADDRESS_ASSIGNMENT_MODELS),
|
||||
required=False,
|
||||
@@ -171,7 +171,7 @@ class MACAddressSerializer(PrimaryModelSerializer):
|
||||
model = MACAddress
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'mac_address', 'assigned_object_type', 'assigned_object_id',
|
||||
'assigned_object', 'description', 'owner', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
|
||||
'assigned_object', 'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'mac_address', 'description')
|
||||
|
||||
|
||||
@@ -155,7 +155,7 @@ class PowerOutletTemplateSerializer(ComponentTemplateSerializer):
|
||||
model = PowerOutletTemplate
|
||||
fields = [
|
||||
'id', 'url', 'display', 'device_type', 'module_type', 'name', 'label', 'type',
|
||||
'color', 'power_port', 'feed_leg', 'description', 'created', 'last_updated',
|
||||
'power_port', 'feed_leg', 'description', 'created', 'last_updated',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'name', 'description')
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ from rest_framework import serializers
|
||||
from dcim.choices import *
|
||||
from dcim.models import DeviceType, ModuleType, ModuleTypeProfile
|
||||
from netbox.api.fields import AttributesField, ChoiceField, RelatedObjectCountField
|
||||
from netbox.api.serializers import PrimaryModelSerializer
|
||||
from netbox.api.serializers import NetBoxModelSerializer
|
||||
from netbox.choices import *
|
||||
from .manufacturers import ManufacturerSerializer
|
||||
from .platforms import PlatformSerializer
|
||||
@@ -18,7 +18,7 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class DeviceTypeSerializer(PrimaryModelSerializer):
|
||||
class DeviceTypeSerializer(NetBoxModelSerializer):
|
||||
manufacturer = ManufacturerSerializer(nested=True)
|
||||
default_platform = PlatformSerializer(nested=True, required=False, allow_null=True)
|
||||
u_height = serializers.DecimalField(
|
||||
@@ -54,7 +54,7 @@ class DeviceTypeSerializer(PrimaryModelSerializer):
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'manufacturer', 'default_platform', 'model', 'slug', 'part_number',
|
||||
'u_height', 'exclude_from_utilization', 'is_full_depth', 'subdevice_role', 'airflow', 'weight',
|
||||
'weight_unit', 'front_image', 'rear_image', 'description', 'owner', 'comments', 'tags', 'custom_fields',
|
||||
'weight_unit', 'front_image', 'rear_image', 'description', 'comments', 'tags', 'custom_fields',
|
||||
'created', 'last_updated', 'device_count', 'console_port_template_count',
|
||||
'console_server_port_template_count', 'power_port_template_count', 'power_outlet_template_count',
|
||||
'interface_template_count', 'front_port_template_count', 'rear_port_template_count',
|
||||
@@ -63,18 +63,18 @@ class DeviceTypeSerializer(PrimaryModelSerializer):
|
||||
brief_fields = ('id', 'url', 'display', 'manufacturer', 'model', 'slug', 'description', 'device_count')
|
||||
|
||||
|
||||
class ModuleTypeProfileSerializer(PrimaryModelSerializer):
|
||||
class ModuleTypeProfileSerializer(NetBoxModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = ModuleTypeProfile
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'name', 'description', 'schema', 'owner', 'comments', 'tags',
|
||||
'custom_fields', 'created', 'last_updated',
|
||||
'id', 'url', 'display_url', 'display', 'name', 'description', 'schema', 'comments', 'tags', 'custom_fields',
|
||||
'created', 'last_updated',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'name', 'description')
|
||||
|
||||
|
||||
class ModuleTypeSerializer(PrimaryModelSerializer):
|
||||
class ModuleTypeSerializer(NetBoxModelSerializer):
|
||||
profile = ModuleTypeProfileSerializer(
|
||||
nested=True,
|
||||
required=False,
|
||||
@@ -105,7 +105,7 @@ class ModuleTypeSerializer(PrimaryModelSerializer):
|
||||
model = ModuleType
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'profile', 'manufacturer', 'model', 'part_number', 'airflow',
|
||||
'weight', 'weight_unit', 'description', 'attributes', 'owner', 'comments', 'tags', 'custom_fields',
|
||||
'created', 'last_updated',
|
||||
'weight', 'weight_unit', 'description', 'attributes', 'comments', 'tags', 'custom_fields', 'created',
|
||||
'last_updated',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'profile', 'manufacturer', 'model', 'description')
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
from dcim.models import Manufacturer
|
||||
from netbox.api.fields import RelatedObjectCountField
|
||||
from netbox.api.serializers import OrganizationalModelSerializer
|
||||
from netbox.api.serializers import NetBoxModelSerializer
|
||||
|
||||
__all__ = (
|
||||
'ManufacturerSerializer',
|
||||
)
|
||||
|
||||
|
||||
class ManufacturerSerializer(OrganizationalModelSerializer):
|
||||
class ManufacturerSerializer(NetBoxModelSerializer):
|
||||
|
||||
# Related object counts
|
||||
devicetype_count = RelatedObjectCountField('device_types')
|
||||
@@ -17,7 +17,7 @@ class ManufacturerSerializer(OrganizationalModelSerializer):
|
||||
class Meta:
|
||||
model = Manufacturer
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'name', 'slug', 'description', 'owner', 'tags', 'custom_fields',
|
||||
'id', 'url', 'display_url', 'display', 'name', 'slug', 'description', 'tags', 'custom_fields',
|
||||
'created', 'last_updated', 'devicetype_count', 'inventoryitem_count', 'platform_count',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'name', 'slug', 'description', 'devicetype_count')
|
||||
|
||||
@@ -24,7 +24,7 @@ class PlatformSerializer(NestedGroupModelSerializer):
|
||||
model = Platform
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'parent', 'name', 'slug', 'manufacturer', 'config_template',
|
||||
'description', 'owner', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'device_count',
|
||||
'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'device_count',
|
||||
'virtualmachine_count', '_depth',
|
||||
]
|
||||
brief_fields = (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from dcim.choices import *
|
||||
from dcim.models import PowerFeed, PowerPanel
|
||||
from netbox.api.fields import ChoiceField, RelatedObjectCountField
|
||||
from netbox.api.serializers import PrimaryModelSerializer
|
||||
from netbox.api.serializers import NetBoxModelSerializer
|
||||
from tenancy.api.serializers_.tenants import TenantSerializer
|
||||
from .base import ConnectedEndpointsSerializer
|
||||
from .cables import CabledObjectSerializer
|
||||
@@ -14,7 +14,7 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class PowerPanelSerializer(PrimaryModelSerializer):
|
||||
class PowerPanelSerializer(NetBoxModelSerializer):
|
||||
site = SiteSerializer(nested=True)
|
||||
location = LocationSerializer(
|
||||
nested=True,
|
||||
@@ -29,13 +29,13 @@ class PowerPanelSerializer(PrimaryModelSerializer):
|
||||
class Meta:
|
||||
model = PowerPanel
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'site', 'location', 'name', 'description', 'owner', 'comments',
|
||||
'tags', 'custom_fields', 'powerfeed_count', 'created', 'last_updated',
|
||||
'id', 'url', 'display_url', 'display', 'site', 'location', 'name', 'description', 'comments', 'tags',
|
||||
'custom_fields', 'powerfeed_count', 'created', 'last_updated',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'name', 'description', 'powerfeed_count')
|
||||
|
||||
|
||||
class PowerFeedSerializer(PrimaryModelSerializer, CabledObjectSerializer, ConnectedEndpointsSerializer):
|
||||
class PowerFeedSerializer(NetBoxModelSerializer, CabledObjectSerializer, ConnectedEndpointsSerializer):
|
||||
power_panel = PowerPanelSerializer(nested=True)
|
||||
rack = RackSerializer(
|
||||
nested=True,
|
||||
@@ -71,7 +71,6 @@ class PowerFeedSerializer(PrimaryModelSerializer, CabledObjectSerializer, Connec
|
||||
'id', 'url', 'display_url', 'display', 'power_panel', 'rack', 'name', 'status', 'type', 'supply',
|
||||
'phase', 'voltage', 'amperage', 'max_utilization', 'mark_connected', 'cable', 'cable_end', 'link_peers',
|
||||
'link_peers_type', 'connected_endpoints', 'connected_endpoints_type', 'connected_endpoints_reachable',
|
||||
'description', 'tenant', 'owner', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
|
||||
'_occupied',
|
||||
'description', 'tenant', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'name', 'description', 'cable', '_occupied')
|
||||
|
||||
@@ -5,7 +5,7 @@ from dcim.choices import *
|
||||
from dcim.constants import *
|
||||
from dcim.models import Rack, RackReservation, RackRole, RackType
|
||||
from netbox.api.fields import ChoiceField, RelatedObjectCountField
|
||||
from netbox.api.serializers import OrganizationalModelSerializer, PrimaryModelSerializer
|
||||
from netbox.api.serializers import NetBoxModelSerializer
|
||||
from netbox.choices import *
|
||||
from netbox.config import ConfigItem
|
||||
from tenancy.api.serializers_.tenants import TenantSerializer
|
||||
@@ -22,7 +22,7 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class RackRoleSerializer(OrganizationalModelSerializer):
|
||||
class RackRoleSerializer(NetBoxModelSerializer):
|
||||
|
||||
# Related object counts
|
||||
rack_count = RelatedObjectCountField('racks')
|
||||
@@ -30,13 +30,13 @@ class RackRoleSerializer(OrganizationalModelSerializer):
|
||||
class Meta:
|
||||
model = RackRole
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'name', 'slug', 'color', 'description', 'owner', 'tags',
|
||||
'custom_fields', 'created', 'last_updated', 'rack_count',
|
||||
'id', 'url', 'display_url', 'display', 'name', 'slug', 'color', 'description', 'tags', 'custom_fields',
|
||||
'created', 'last_updated', 'rack_count',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'name', 'slug', 'description', 'rack_count')
|
||||
|
||||
|
||||
class RackBaseSerializer(PrimaryModelSerializer):
|
||||
class RackBaseSerializer(NetBoxModelSerializer):
|
||||
form_factor = ChoiceField(
|
||||
choices=RackFormFactorChoices,
|
||||
allow_blank=True,
|
||||
@@ -71,8 +71,8 @@ class RackTypeSerializer(RackBaseSerializer):
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'manufacturer', 'model', 'slug', 'description', 'form_factor',
|
||||
'width', 'u_height', 'starting_unit', 'desc_units', 'outer_width', 'outer_height', 'outer_depth',
|
||||
'outer_unit', 'weight', 'max_weight', 'weight_unit', 'mounting_depth', 'description', 'owner', 'comments',
|
||||
'tags', 'custom_fields', 'created', 'last_updated',
|
||||
'outer_unit', 'weight', 'max_weight', 'weight_unit', 'mounting_depth', 'description', 'comments', 'tags',
|
||||
'custom_fields', 'created', 'last_updated',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'manufacturer', 'model', 'slug', 'description')
|
||||
|
||||
@@ -130,13 +130,13 @@ class RackSerializer(RackBaseSerializer):
|
||||
'id', 'url', 'display_url', 'display', 'name', 'facility_id', 'site', 'location', 'tenant', 'status',
|
||||
'role', 'serial', 'asset_tag', 'rack_type', 'form_factor', 'width', 'u_height', 'starting_unit', 'weight',
|
||||
'max_weight', 'weight_unit', 'desc_units', 'outer_width', 'outer_height', 'outer_depth', 'outer_unit',
|
||||
'mounting_depth', 'airflow', 'description', 'owner', 'comments', 'tags', 'custom_fields', 'created',
|
||||
'last_updated', 'device_count', 'powerfeed_count',
|
||||
'mounting_depth', 'airflow', 'description', 'comments', 'tags', 'custom_fields',
|
||||
'created', 'last_updated', 'device_count', 'powerfeed_count',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'name', 'description', 'device_count')
|
||||
|
||||
|
||||
class RackReservationSerializer(PrimaryModelSerializer):
|
||||
class RackReservationSerializer(NetBoxModelSerializer):
|
||||
rack = RackSerializer(
|
||||
nested=True,
|
||||
)
|
||||
@@ -157,7 +157,7 @@ class RackReservationSerializer(PrimaryModelSerializer):
|
||||
model = RackReservation
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'rack', 'units', 'status', 'created', 'last_updated', 'user',
|
||||
'tenant', 'description', 'owner', 'comments', 'tags', 'custom_fields',
|
||||
'tenant', 'description', 'comments', 'tags', 'custom_fields',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'status', 'user', 'description', 'units')
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ from rest_framework import serializers
|
||||
from dcim.models import DeviceRole, InventoryItemRole
|
||||
from extras.api.serializers_.configtemplates import ConfigTemplateSerializer
|
||||
from netbox.api.fields import RelatedObjectCountField
|
||||
from netbox.api.serializers import NestedGroupModelSerializer, OrganizationalModelSerializer
|
||||
from netbox.api.serializers import NestedGroupModelSerializer, NetBoxModelSerializer
|
||||
from .nested import NestedDeviceRoleSerializer
|
||||
|
||||
__all__ = (
|
||||
@@ -23,14 +23,14 @@ class DeviceRoleSerializer(NestedGroupModelSerializer):
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'name', 'slug', 'color', 'vm_role', 'config_template', 'parent',
|
||||
'description', 'tags', 'custom_fields', 'created', 'last_updated', 'device_count', 'virtualmachine_count',
|
||||
'owner', 'comments', '_depth',
|
||||
'comments', '_depth',
|
||||
]
|
||||
brief_fields = (
|
||||
'id', 'url', 'display', 'name', 'slug', 'description', 'device_count', 'virtualmachine_count', '_depth'
|
||||
)
|
||||
|
||||
|
||||
class InventoryItemRoleSerializer(OrganizationalModelSerializer):
|
||||
class InventoryItemRoleSerializer(NetBoxModelSerializer):
|
||||
|
||||
# Related object counts
|
||||
inventoryitem_count = RelatedObjectCountField('inventory_items')
|
||||
@@ -38,7 +38,7 @@ class InventoryItemRoleSerializer(OrganizationalModelSerializer):
|
||||
class Meta:
|
||||
model = InventoryItemRole
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'name', 'slug', 'color', 'description', 'owner', 'tags',
|
||||
'custom_fields', 'created', 'last_updated', 'inventoryitem_count',
|
||||
'id', 'url', 'display_url', 'display', 'name', 'slug', 'color', 'description', 'tags', 'custom_fields',
|
||||
'created', 'last_updated', 'inventoryitem_count',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'name', 'slug', 'description', 'inventoryitem_count')
|
||||
|
||||
@@ -6,7 +6,7 @@ from dcim.models import Location, Region, Site, SiteGroup
|
||||
from ipam.api.serializers_.asns import ASNSerializer
|
||||
from ipam.models import ASN
|
||||
from netbox.api.fields import ChoiceField, RelatedObjectCountField, SerializedPKRelatedField
|
||||
from netbox.api.serializers import NestedGroupModelSerializer, PrimaryModelSerializer
|
||||
from netbox.api.serializers import NestedGroupModelSerializer, NetBoxModelSerializer
|
||||
from tenancy.api.serializers_.tenants import TenantSerializer
|
||||
from .nested import NestedLocationSerializer, NestedRegionSerializer, NestedSiteGroupSerializer
|
||||
|
||||
@@ -27,7 +27,7 @@ class RegionSerializer(NestedGroupModelSerializer):
|
||||
model = Region
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'name', 'slug', 'parent', 'description', 'tags', 'custom_fields',
|
||||
'created', 'last_updated', 'site_count', 'prefix_count', 'owner', 'comments', '_depth',
|
||||
'created', 'last_updated', 'site_count', 'prefix_count', 'comments', '_depth',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'name', 'slug', 'description', 'site_count', '_depth')
|
||||
|
||||
@@ -41,12 +41,12 @@ class SiteGroupSerializer(NestedGroupModelSerializer):
|
||||
model = SiteGroup
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'name', 'slug', 'parent', 'description', 'tags', 'custom_fields',
|
||||
'created', 'last_updated', 'site_count', 'prefix_count', 'owner', 'comments', '_depth',
|
||||
'created', 'last_updated', 'site_count', 'prefix_count', 'comments', '_depth',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'name', 'slug', 'description', 'site_count', '_depth')
|
||||
|
||||
|
||||
class SiteSerializer(PrimaryModelSerializer):
|
||||
class SiteSerializer(NetBoxModelSerializer):
|
||||
status = ChoiceField(choices=SiteStatusChoices, required=False)
|
||||
region = RegionSerializer(nested=True, required=False, allow_null=True)
|
||||
group = SiteGroupSerializer(nested=True, required=False, allow_null=True)
|
||||
@@ -72,7 +72,7 @@ class SiteSerializer(PrimaryModelSerializer):
|
||||
model = Site
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'name', 'slug', 'status', 'region', 'group', 'tenant', 'facility',
|
||||
'time_zone', 'description', 'physical_address', 'shipping_address', 'latitude', 'longitude', 'owner',
|
||||
'time_zone', 'description', 'physical_address', 'shipping_address', 'latitude', 'longitude',
|
||||
'comments', 'asns', 'tags', 'custom_fields', 'created', 'last_updated', 'circuit_count', 'device_count',
|
||||
'prefix_count', 'rack_count', 'virtualmachine_count', 'vlan_count',
|
||||
]
|
||||
@@ -93,6 +93,6 @@ class LocationSerializer(NestedGroupModelSerializer):
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'name', 'slug', 'site', 'parent', 'status', 'tenant', 'facility',
|
||||
'description', 'tags', 'custom_fields', 'created', 'last_updated', 'rack_count', 'device_count',
|
||||
'prefix_count', 'owner', 'comments', '_depth',
|
||||
'prefix_count', 'comments', '_depth',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'name', 'slug', 'description', 'rack_count', '_depth')
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from dcim.models import VirtualChassis
|
||||
from netbox.api.serializers import PrimaryModelSerializer
|
||||
from netbox.api.serializers import NetBoxModelSerializer
|
||||
from .nested import NestedDeviceSerializer
|
||||
|
||||
__all__ = (
|
||||
@@ -9,7 +9,7 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class VirtualChassisSerializer(PrimaryModelSerializer):
|
||||
class VirtualChassisSerializer(NetBoxModelSerializer):
|
||||
master = NestedDeviceSerializer(required=False, allow_null=True, default=None)
|
||||
members = NestedDeviceSerializer(many=True, read_only=True)
|
||||
|
||||
@@ -19,7 +19,7 @@ class VirtualChassisSerializer(PrimaryModelSerializer):
|
||||
class Meta:
|
||||
model = VirtualChassis
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'name', 'domain', 'master', 'description', 'owner', 'comments',
|
||||
'tags', 'custom_fields', 'created', 'last_updated', 'member_count', 'members',
|
||||
'id', 'url', 'display_url', 'display', 'name', 'domain', 'master', 'description', 'comments', 'tags',
|
||||
'custom_fields', 'created', 'last_updated', 'member_count', 'members',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'name', 'master', 'description', 'member_count')
|
||||
|
||||
@@ -16,7 +16,7 @@ from extras.api.mixins import ConfigContextQuerySetMixin, RenderConfigMixin
|
||||
from netbox.api.authentication import IsAuthenticatedOrLoginNotRequired
|
||||
from netbox.api.metadata import ContentTypeMetadata
|
||||
from netbox.api.pagination import StripCountAnnotationsPaginator
|
||||
from netbox.api.viewsets import NetBoxModelViewSet, MPTTLockedMixin, NetBoxReadOnlyModelViewSet
|
||||
from netbox.api.viewsets import NetBoxModelViewSet, MPTTLockedMixin
|
||||
from netbox.api.viewsets.mixins import SequentialBulkCreatesMixin
|
||||
from utilities.api import get_serializer_for_model
|
||||
from utilities.query_functions import CollateAsChar
|
||||
@@ -563,7 +563,7 @@ class CableViewSet(NetBoxModelViewSet):
|
||||
filterset_class = filtersets.CableFilterSet
|
||||
|
||||
|
||||
class CableTerminationViewSet(NetBoxReadOnlyModelViewSet):
|
||||
class CableTerminationViewSet(NetBoxModelViewSet):
|
||||
metadata_class = ContentTypeMetadata
|
||||
queryset = CableTermination.objects.all()
|
||||
serializer_class = serializers.CableTerminationSerializer
|
||||
|
||||
@@ -11,20 +11,19 @@ from ipam.filtersets import PrimaryIPFilterSet
|
||||
from ipam.models import ASN, IPAddress, VLANTranslationPolicy, VRF
|
||||
from netbox.choices import ColorChoices
|
||||
from netbox.filtersets import (
|
||||
AttributeFiltersMixin, BaseFilterSet, ChangeLoggedModelFilterSet, NestedGroupModelFilterSet,
|
||||
OrganizationalModelFilterSet, PrimaryModelFilterSet, NetBoxModelFilterSet,
|
||||
AttributeFiltersMixin, BaseFilterSet, ChangeLoggedModelFilterSet, NestedGroupModelFilterSet, NetBoxModelFilterSet,
|
||||
OrganizationalModelFilterSet,
|
||||
)
|
||||
from tenancy.filtersets import TenancyFilterSet, ContactModelFilterSet
|
||||
from tenancy.filtersets import ContactModelFilterSet, TenancyFilterSet
|
||||
from tenancy.models import *
|
||||
from users.filterset_mixins import OwnerFilterMixin
|
||||
from users.models import User
|
||||
from utilities.filters import (
|
||||
ContentTypeFilter, MultiValueCharFilter, MultiValueMACAddressFilter, MultiValueNumberFilter, MultiValueWWNFilter,
|
||||
NumericArrayFilter, TreeNodeMultipleChoiceFilter,
|
||||
)
|
||||
from virtualization.models import Cluster, ClusterGroup, VMInterface, VirtualMachine
|
||||
from virtualization.models import Cluster, ClusterGroup, VirtualMachine, VMInterface
|
||||
from vpn.models import L2VPN
|
||||
from wireless.choices import WirelessRoleChoices, WirelessChannelChoices
|
||||
from wireless.choices import WirelessChannelChoices, WirelessRoleChoices
|
||||
from wireless.models import WirelessLAN, WirelessLink
|
||||
from .choices import *
|
||||
from .constants import *
|
||||
@@ -144,7 +143,7 @@ class SiteGroupFilterSet(NestedGroupModelFilterSet, ContactModelFilterSet):
|
||||
fields = ('id', 'name', 'slug', 'description')
|
||||
|
||||
|
||||
class SiteFilterSet(PrimaryModelFilterSet, TenancyFilterSet, ContactModelFilterSet):
|
||||
class SiteFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilterSet):
|
||||
status = django_filters.MultipleChoiceFilter(
|
||||
choices=SiteStatusChoices,
|
||||
null_value=None
|
||||
@@ -294,7 +293,7 @@ class RackRoleFilterSet(OrganizationalModelFilterSet):
|
||||
fields = ('id', 'name', 'slug', 'color', 'description')
|
||||
|
||||
|
||||
class RackTypeFilterSet(PrimaryModelFilterSet):
|
||||
class RackTypeFilterSet(NetBoxModelFilterSet):
|
||||
manufacturer_id = django_filters.ModelMultipleChoiceFilter(
|
||||
queryset=Manufacturer.objects.all(),
|
||||
label=_('Manufacturer (ID)'),
|
||||
@@ -329,7 +328,7 @@ class RackTypeFilterSet(PrimaryModelFilterSet):
|
||||
)
|
||||
|
||||
|
||||
class RackFilterSet(PrimaryModelFilterSet, TenancyFilterSet, ContactModelFilterSet):
|
||||
class RackFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilterSet):
|
||||
region_id = TreeNodeMultipleChoiceFilter(
|
||||
queryset=Region.objects.all(),
|
||||
field_name='site__region',
|
||||
@@ -445,7 +444,7 @@ class RackFilterSet(PrimaryModelFilterSet, TenancyFilterSet, ContactModelFilterS
|
||||
)
|
||||
|
||||
|
||||
class RackReservationFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
|
||||
class RackReservationFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
|
||||
rack_id = django_filters.ModelMultipleChoiceFilter(
|
||||
queryset=Rack.objects.all(),
|
||||
label=_('Rack (ID)'),
|
||||
@@ -541,7 +540,7 @@ class ManufacturerFilterSet(OrganizationalModelFilterSet, ContactModelFilterSet)
|
||||
fields = ('id', 'name', 'slug', 'description')
|
||||
|
||||
|
||||
class DeviceTypeFilterSet(PrimaryModelFilterSet):
|
||||
class DeviceTypeFilterSet(NetBoxModelFilterSet):
|
||||
manufacturer_id = django_filters.ModelMultipleChoiceFilter(
|
||||
queryset=Manufacturer.objects.all(),
|
||||
label=_('Manufacturer (ID)'),
|
||||
@@ -683,7 +682,7 @@ class DeviceTypeFilterSet(PrimaryModelFilterSet):
|
||||
return queryset.exclude(inventoryitemtemplates__isnull=value)
|
||||
|
||||
|
||||
class ModuleTypeProfileFilterSet(PrimaryModelFilterSet):
|
||||
class ModuleTypeProfileFilterSet(NetBoxModelFilterSet):
|
||||
|
||||
class Meta:
|
||||
model = ModuleTypeProfile
|
||||
@@ -699,7 +698,7 @@ class ModuleTypeProfileFilterSet(PrimaryModelFilterSet):
|
||||
)
|
||||
|
||||
|
||||
class ModuleTypeFilterSet(AttributeFiltersMixin, PrimaryModelFilterSet):
|
||||
class ModuleTypeFilterSet(AttributeFiltersMixin, NetBoxModelFilterSet):
|
||||
profile_id = django_filters.ModelMultipleChoiceFilter(
|
||||
queryset=ModuleTypeProfile.objects.all(),
|
||||
label=_('Profile (ID)'),
|
||||
@@ -843,7 +842,7 @@ class PowerOutletTemplateFilterSet(ChangeLoggedModelFilterSet, ModularDeviceType
|
||||
|
||||
class Meta:
|
||||
model = PowerOutletTemplate
|
||||
fields = ('id', 'name', 'label', 'type', 'color', 'feed_leg', 'description')
|
||||
fields = ('id', 'name', 'label', 'type', 'feed_leg', 'description')
|
||||
|
||||
|
||||
class InterfaceTemplateFilterSet(ChangeLoggedModelFilterSet, ModularDeviceTypeComponentFilterSet):
|
||||
@@ -952,7 +951,7 @@ class InventoryItemTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeCompo
|
||||
return queryset.filter(qs_filter)
|
||||
|
||||
|
||||
class DeviceRoleFilterSet(NestedGroupModelFilterSet):
|
||||
class DeviceRoleFilterSet(OrganizationalModelFilterSet):
|
||||
config_template_id = django_filters.ModelMultipleChoiceFilter(
|
||||
queryset=ConfigTemplate.objects.all(),
|
||||
label=_('Config template (ID)'),
|
||||
@@ -986,7 +985,7 @@ class DeviceRoleFilterSet(NestedGroupModelFilterSet):
|
||||
fields = ('id', 'name', 'slug', 'color', 'vm_role', 'description')
|
||||
|
||||
|
||||
class PlatformFilterSet(NestedGroupModelFilterSet):
|
||||
class PlatformFilterSet(OrganizationalModelFilterSet):
|
||||
parent_id = django_filters.ModelMultipleChoiceFilter(
|
||||
queryset=Platform.objects.all(),
|
||||
label=_('Immediate parent platform (ID)'),
|
||||
@@ -1044,7 +1043,7 @@ class PlatformFilterSet(NestedGroupModelFilterSet):
|
||||
|
||||
|
||||
class DeviceFilterSet(
|
||||
PrimaryModelFilterSet,
|
||||
NetBoxModelFilterSet,
|
||||
TenancyFilterSet,
|
||||
ContactModelFilterSet,
|
||||
LocalConfigContextFilterSet,
|
||||
@@ -1346,7 +1345,7 @@ class DeviceFilterSet(
|
||||
return queryset.exclude(params)
|
||||
|
||||
|
||||
class VirtualDeviceContextFilterSet(PrimaryModelFilterSet, TenancyFilterSet, PrimaryIPFilterSet):
|
||||
class VirtualDeviceContextFilterSet(NetBoxModelFilterSet, TenancyFilterSet, PrimaryIPFilterSet):
|
||||
device_id = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='device',
|
||||
queryset=Device.objects.all(),
|
||||
@@ -1395,7 +1394,7 @@ class VirtualDeviceContextFilterSet(PrimaryModelFilterSet, TenancyFilterSet, Pri
|
||||
return queryset.exclude(params)
|
||||
|
||||
|
||||
class ModuleFilterSet(PrimaryModelFilterSet):
|
||||
class ModuleFilterSet(NetBoxModelFilterSet):
|
||||
manufacturer_id = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='module_type__manufacturer',
|
||||
queryset=Manufacturer.objects.all(),
|
||||
@@ -1517,7 +1516,7 @@ class ModuleFilterSet(PrimaryModelFilterSet):
|
||||
).distinct()
|
||||
|
||||
|
||||
class DeviceComponentFilterSet(OwnerFilterMixin, NetBoxModelFilterSet):
|
||||
class DeviceComponentFilterSet(django_filters.FilterSet):
|
||||
q = django_filters.CharFilter(
|
||||
method='search',
|
||||
label=_('Search'),
|
||||
@@ -1683,7 +1682,12 @@ class PathEndpointFilterSet(django_filters.FilterSet):
|
||||
return queryset.filter(Q(_path__isnull=True) | Q(_path__is_active=False))
|
||||
|
||||
|
||||
class ConsolePortFilterSet(ModularDeviceComponentFilterSet, CabledObjectFilterSet, PathEndpointFilterSet):
|
||||
class ConsolePortFilterSet(
|
||||
ModularDeviceComponentFilterSet,
|
||||
NetBoxModelFilterSet,
|
||||
CabledObjectFilterSet,
|
||||
PathEndpointFilterSet
|
||||
):
|
||||
type = django_filters.MultipleChoiceFilter(
|
||||
choices=ConsolePortTypeChoices,
|
||||
null_value=None
|
||||
@@ -1694,7 +1698,12 @@ class ConsolePortFilterSet(ModularDeviceComponentFilterSet, CabledObjectFilterSe
|
||||
fields = ('id', 'name', 'label', 'speed', 'description', 'mark_connected', 'cable_end')
|
||||
|
||||
|
||||
class ConsoleServerPortFilterSet(ModularDeviceComponentFilterSet, CabledObjectFilterSet, PathEndpointFilterSet):
|
||||
class ConsoleServerPortFilterSet(
|
||||
ModularDeviceComponentFilterSet,
|
||||
NetBoxModelFilterSet,
|
||||
CabledObjectFilterSet,
|
||||
PathEndpointFilterSet
|
||||
):
|
||||
type = django_filters.MultipleChoiceFilter(
|
||||
choices=ConsolePortTypeChoices,
|
||||
null_value=None
|
||||
@@ -1705,7 +1714,12 @@ class ConsoleServerPortFilterSet(ModularDeviceComponentFilterSet, CabledObjectFi
|
||||
fields = ('id', 'name', 'label', 'speed', 'description', 'mark_connected', 'cable_end')
|
||||
|
||||
|
||||
class PowerPortFilterSet(ModularDeviceComponentFilterSet, CabledObjectFilterSet, PathEndpointFilterSet):
|
||||
class PowerPortFilterSet(
|
||||
ModularDeviceComponentFilterSet,
|
||||
NetBoxModelFilterSet,
|
||||
CabledObjectFilterSet,
|
||||
PathEndpointFilterSet
|
||||
):
|
||||
type = django_filters.MultipleChoiceFilter(
|
||||
choices=PowerPortTypeChoices,
|
||||
null_value=None
|
||||
@@ -1718,7 +1732,12 @@ class PowerPortFilterSet(ModularDeviceComponentFilterSet, CabledObjectFilterSet,
|
||||
)
|
||||
|
||||
|
||||
class PowerOutletFilterSet(ModularDeviceComponentFilterSet, CabledObjectFilterSet, PathEndpointFilterSet):
|
||||
class PowerOutletFilterSet(
|
||||
ModularDeviceComponentFilterSet,
|
||||
NetBoxModelFilterSet,
|
||||
CabledObjectFilterSet,
|
||||
PathEndpointFilterSet
|
||||
):
|
||||
type = django_filters.MultipleChoiceFilter(
|
||||
choices=PowerOutletTypeChoices,
|
||||
null_value=None
|
||||
@@ -1743,7 +1762,7 @@ class PowerOutletFilterSet(ModularDeviceComponentFilterSet, CabledObjectFilterSe
|
||||
)
|
||||
|
||||
|
||||
class MACAddressFilterSet(PrimaryModelFilterSet):
|
||||
class MACAddressFilterSet(NetBoxModelFilterSet):
|
||||
mac_address = MultiValueMACAddressFilter()
|
||||
assigned_object_type = ContentTypeFilter()
|
||||
device = MultiValueCharFilter(
|
||||
@@ -1788,6 +1807,14 @@ class MACAddressFilterSet(PrimaryModelFilterSet):
|
||||
queryset=VMInterface.objects.all(),
|
||||
label=_('VM interface (ID)'),
|
||||
)
|
||||
assigned = django_filters.BooleanFilter(
|
||||
method='filter_assigned',
|
||||
label=_('Is assigned'),
|
||||
)
|
||||
primary = django_filters.BooleanFilter(
|
||||
method='filter_primary',
|
||||
label=_('Is primary'),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = MACAddress
|
||||
@@ -1824,6 +1851,29 @@ class MACAddressFilterSet(PrimaryModelFilterSet):
|
||||
vminterface__in=interface_ids
|
||||
)
|
||||
|
||||
def filter_assigned(self, queryset, name, value):
|
||||
params = {
|
||||
'assigned_object_type__isnull': True,
|
||||
'assigned_object_id__isnull': True,
|
||||
}
|
||||
if value:
|
||||
return queryset.exclude(**params)
|
||||
else:
|
||||
return queryset.filter(**params)
|
||||
|
||||
def filter_primary(self, queryset, name, value):
|
||||
interface_mac_ids = Interface.objects.filter(primary_mac_address_id__isnull=False).values_list(
|
||||
'primary_mac_address_id', flat=True
|
||||
)
|
||||
vminterface_mac_ids = VMInterface.objects.filter(primary_mac_address_id__isnull=False).values_list(
|
||||
'primary_mac_address_id', flat=True
|
||||
)
|
||||
query = Q(pk__in=interface_mac_ids) | Q(pk__in=vminterface_mac_ids)
|
||||
if value:
|
||||
return queryset.filter(query)
|
||||
else:
|
||||
return queryset.exclude(query)
|
||||
|
||||
|
||||
class CommonInterfaceFilterSet(django_filters.FilterSet):
|
||||
mode = django_filters.MultipleChoiceFilter(
|
||||
@@ -1895,6 +1945,7 @@ class CommonInterfaceFilterSet(django_filters.FilterSet):
|
||||
|
||||
class InterfaceFilterSet(
|
||||
ModularDeviceComponentFilterSet,
|
||||
NetBoxModelFilterSet,
|
||||
CabledObjectFilterSet,
|
||||
PathEndpointFilterSet,
|
||||
CommonInterfaceFilterSet
|
||||
@@ -2055,7 +2106,11 @@ class InterfaceFilterSet(
|
||||
)
|
||||
|
||||
|
||||
class FrontPortFilterSet(ModularDeviceComponentFilterSet, CabledObjectFilterSet):
|
||||
class FrontPortFilterSet(
|
||||
ModularDeviceComponentFilterSet,
|
||||
NetBoxModelFilterSet,
|
||||
CabledObjectFilterSet
|
||||
):
|
||||
type = django_filters.MultipleChoiceFilter(
|
||||
choices=PortTypeChoices,
|
||||
null_value=None
|
||||
@@ -2071,7 +2126,11 @@ class FrontPortFilterSet(ModularDeviceComponentFilterSet, CabledObjectFilterSet)
|
||||
)
|
||||
|
||||
|
||||
class RearPortFilterSet(ModularDeviceComponentFilterSet, CabledObjectFilterSet):
|
||||
class RearPortFilterSet(
|
||||
ModularDeviceComponentFilterSet,
|
||||
NetBoxModelFilterSet,
|
||||
CabledObjectFilterSet
|
||||
):
|
||||
type = django_filters.MultipleChoiceFilter(
|
||||
choices=PortTypeChoices,
|
||||
null_value=None
|
||||
@@ -2084,7 +2143,7 @@ class RearPortFilterSet(ModularDeviceComponentFilterSet, CabledObjectFilterSet):
|
||||
)
|
||||
|
||||
|
||||
class ModuleBayFilterSet(ModularDeviceComponentFilterSet):
|
||||
class ModuleBayFilterSet(ModularDeviceComponentFilterSet, NetBoxModelFilterSet):
|
||||
parent_id = django_filters.ModelMultipleChoiceFilter(
|
||||
queryset=ModuleBay.objects.all(),
|
||||
label=_('Parent module bay (ID)'),
|
||||
@@ -2100,7 +2159,7 @@ class ModuleBayFilterSet(ModularDeviceComponentFilterSet):
|
||||
fields = ('id', 'name', 'label', 'position', 'description')
|
||||
|
||||
|
||||
class DeviceBayFilterSet(DeviceComponentFilterSet):
|
||||
class DeviceBayFilterSet(DeviceComponentFilterSet, NetBoxModelFilterSet):
|
||||
installed_device_id = django_filters.ModelMultipleChoiceFilter(
|
||||
queryset=Device.objects.all(),
|
||||
label=_('Installed device (ID)'),
|
||||
@@ -2117,7 +2176,7 @@ class DeviceBayFilterSet(DeviceComponentFilterSet):
|
||||
fields = ('id', 'name', 'label', 'description')
|
||||
|
||||
|
||||
class InventoryItemFilterSet(DeviceComponentFilterSet):
|
||||
class InventoryItemFilterSet(DeviceComponentFilterSet, NetBoxModelFilterSet):
|
||||
parent_id = django_filters.ModelMultipleChoiceFilter(
|
||||
queryset=InventoryItem.objects.all(),
|
||||
label=_('Parent inventory item (ID)'),
|
||||
@@ -2176,7 +2235,7 @@ class InventoryItemRoleFilterSet(OrganizationalModelFilterSet):
|
||||
fields = ('id', 'name', 'slug', 'color', 'description')
|
||||
|
||||
|
||||
class VirtualChassisFilterSet(PrimaryModelFilterSet):
|
||||
class VirtualChassisFilterSet(NetBoxModelFilterSet):
|
||||
master_id = django_filters.ModelMultipleChoiceFilter(
|
||||
queryset=Device.objects.all(),
|
||||
label=_('Master (ID)'),
|
||||
@@ -2252,7 +2311,7 @@ class VirtualChassisFilterSet(PrimaryModelFilterSet):
|
||||
return queryset.filter(qs_filter).distinct()
|
||||
|
||||
|
||||
class CableFilterSet(TenancyFilterSet, PrimaryModelFilterSet):
|
||||
class CableFilterSet(TenancyFilterSet, NetBoxModelFilterSet):
|
||||
termination_a_type = ContentTypeFilter(
|
||||
field_name='terminations__termination_type'
|
||||
)
|
||||
@@ -2429,7 +2488,7 @@ class CableTerminationFilterSet(ChangeLoggedModelFilterSet):
|
||||
fields = ('id', 'cable', 'cable_end', 'termination_type', 'termination_id')
|
||||
|
||||
|
||||
class PowerPanelFilterSet(PrimaryModelFilterSet, ContactModelFilterSet):
|
||||
class PowerPanelFilterSet(NetBoxModelFilterSet, ContactModelFilterSet):
|
||||
region_id = TreeNodeMultipleChoiceFilter(
|
||||
queryset=Region.objects.all(),
|
||||
field_name='site__region',
|
||||
@@ -2487,7 +2546,7 @@ class PowerPanelFilterSet(PrimaryModelFilterSet, ContactModelFilterSet):
|
||||
return queryset.filter(qs_filter)
|
||||
|
||||
|
||||
class PowerFeedFilterSet(PrimaryModelFilterSet, CabledObjectFilterSet, PathEndpointFilterSet, TenancyFilterSet):
|
||||
class PowerFeedFilterSet(NetBoxModelFilterSet, CabledObjectFilterSet, PathEndpointFilterSet, TenancyFilterSet):
|
||||
region_id = TreeNodeMultipleChoiceFilter(
|
||||
queryset=Region.objects.all(),
|
||||
field_name='power_panel__site__region',
|
||||
|
||||
@@ -10,14 +10,14 @@ from extras.models import ConfigTemplate
|
||||
from ipam.choices import VLANQinQRoleChoices
|
||||
from ipam.models import ASN, VLAN, VLANGroup, VRF
|
||||
from netbox.choices import *
|
||||
from netbox.forms import (
|
||||
NestedGroupModelBulkEditForm, NetBoxModelBulkEditForm, OrganizationalModelBulkEditForm, PrimaryModelBulkEditForm,
|
||||
)
|
||||
from netbox.forms.mixins import ChangelogMessageMixin, OwnerMixin
|
||||
from netbox.forms import NetBoxModelBulkEditForm
|
||||
from netbox.forms.mixins import ChangelogMessageMixin
|
||||
from tenancy.models import Tenant
|
||||
from users.models import User
|
||||
from utilities.forms import BulkEditForm, add_blank_choice, form_from_model
|
||||
from utilities.forms.fields import ColorField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, JSONField
|
||||
from utilities.forms.fields import (
|
||||
ColorField, CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, JSONField,
|
||||
)
|
||||
from utilities.forms.rendering import FieldSet, InlineFields, TabbedGroups
|
||||
from utilities.forms.widgets import BulkEditNullBooleanSelect, NumberWithOptions
|
||||
from virtualization.models import Cluster
|
||||
@@ -71,12 +71,18 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class RegionBulkEditForm(NestedGroupModelBulkEditForm):
|
||||
class RegionBulkEditForm(NetBoxModelBulkEditForm):
|
||||
parent = DynamicModelChoiceField(
|
||||
label=_('Parent'),
|
||||
queryset=Region.objects.all(),
|
||||
required=False
|
||||
)
|
||||
description = forms.CharField(
|
||||
label=_('Description'),
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
model = Region
|
||||
fieldsets = (
|
||||
@@ -85,12 +91,18 @@ class RegionBulkEditForm(NestedGroupModelBulkEditForm):
|
||||
nullable_fields = ('parent', 'description', 'comments')
|
||||
|
||||
|
||||
class SiteGroupBulkEditForm(NestedGroupModelBulkEditForm):
|
||||
class SiteGroupBulkEditForm(NetBoxModelBulkEditForm):
|
||||
parent = DynamicModelChoiceField(
|
||||
label=_('Parent'),
|
||||
queryset=SiteGroup.objects.all(),
|
||||
required=False
|
||||
)
|
||||
description = forms.CharField(
|
||||
label=_('Description'),
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
model = SiteGroup
|
||||
fieldsets = (
|
||||
@@ -99,7 +111,7 @@ class SiteGroupBulkEditForm(NestedGroupModelBulkEditForm):
|
||||
nullable_fields = ('parent', 'description', 'comments')
|
||||
|
||||
|
||||
class SiteBulkEditForm(PrimaryModelBulkEditForm):
|
||||
class SiteBulkEditForm(NetBoxModelBulkEditForm):
|
||||
status = forms.ChoiceField(
|
||||
label=_('Status'),
|
||||
choices=add_blank_choice(SiteStatusChoices),
|
||||
@@ -150,6 +162,12 @@ class SiteBulkEditForm(PrimaryModelBulkEditForm):
|
||||
choices=add_blank_choice(TimeZoneFormField().choices),
|
||||
required=False
|
||||
)
|
||||
description = forms.CharField(
|
||||
label=_('Description'),
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
model = Site
|
||||
fieldsets = (
|
||||
@@ -160,7 +178,7 @@ class SiteBulkEditForm(PrimaryModelBulkEditForm):
|
||||
)
|
||||
|
||||
|
||||
class LocationBulkEditForm(NestedGroupModelBulkEditForm):
|
||||
class LocationBulkEditForm(NetBoxModelBulkEditForm):
|
||||
site = DynamicModelChoiceField(
|
||||
label=_('Site'),
|
||||
queryset=Site.objects.all(),
|
||||
@@ -190,6 +208,12 @@ class LocationBulkEditForm(NestedGroupModelBulkEditForm):
|
||||
max_length=50,
|
||||
required=False
|
||||
)
|
||||
description = forms.CharField(
|
||||
label=_('Description'),
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
model = Location
|
||||
fieldsets = (
|
||||
@@ -198,11 +222,16 @@ class LocationBulkEditForm(NestedGroupModelBulkEditForm):
|
||||
nullable_fields = ('parent', 'tenant', 'facility', 'description', 'comments')
|
||||
|
||||
|
||||
class RackRoleBulkEditForm(OrganizationalModelBulkEditForm):
|
||||
class RackRoleBulkEditForm(NetBoxModelBulkEditForm):
|
||||
color = ColorField(
|
||||
label=_('Color'),
|
||||
required=False
|
||||
)
|
||||
description = forms.CharField(
|
||||
label=_('Description'),
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
|
||||
model = RackRole
|
||||
fieldsets = (
|
||||
@@ -211,7 +240,7 @@ class RackRoleBulkEditForm(OrganizationalModelBulkEditForm):
|
||||
nullable_fields = ('color', 'description')
|
||||
|
||||
|
||||
class RackTypeBulkEditForm(PrimaryModelBulkEditForm):
|
||||
class RackTypeBulkEditForm(NetBoxModelBulkEditForm):
|
||||
manufacturer = DynamicModelChoiceField(
|
||||
label=_('Manufacturer'),
|
||||
queryset=Manufacturer.objects.all(),
|
||||
@@ -281,6 +310,12 @@ class RackTypeBulkEditForm(PrimaryModelBulkEditForm):
|
||||
required=False,
|
||||
initial=''
|
||||
)
|
||||
description = forms.CharField(
|
||||
label=_('Description'),
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
model = RackType
|
||||
fieldsets = (
|
||||
@@ -299,7 +334,7 @@ class RackTypeBulkEditForm(PrimaryModelBulkEditForm):
|
||||
)
|
||||
|
||||
|
||||
class RackBulkEditForm(PrimaryModelBulkEditForm):
|
||||
class RackBulkEditForm(NetBoxModelBulkEditForm):
|
||||
region = DynamicModelChoiceField(
|
||||
label=_('Region'),
|
||||
queryset=Region.objects.all(),
|
||||
@@ -429,6 +464,12 @@ class RackBulkEditForm(PrimaryModelBulkEditForm):
|
||||
required=False,
|
||||
initial=''
|
||||
)
|
||||
description = forms.CharField(
|
||||
label=_('Description'),
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
model = Rack
|
||||
fieldsets = (
|
||||
@@ -444,7 +485,7 @@ class RackBulkEditForm(PrimaryModelBulkEditForm):
|
||||
)
|
||||
|
||||
|
||||
class RackReservationBulkEditForm(PrimaryModelBulkEditForm):
|
||||
class RackReservationBulkEditForm(NetBoxModelBulkEditForm):
|
||||
status = forms.ChoiceField(
|
||||
label=_('Status'),
|
||||
choices=add_blank_choice(RackReservationStatusChoices),
|
||||
@@ -461,6 +502,12 @@ class RackReservationBulkEditForm(PrimaryModelBulkEditForm):
|
||||
queryset=Tenant.objects.all(),
|
||||
required=False
|
||||
)
|
||||
description = forms.CharField(
|
||||
label=_('Description'),
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
model = RackReservation
|
||||
fieldsets = (
|
||||
@@ -469,7 +516,13 @@ class RackReservationBulkEditForm(PrimaryModelBulkEditForm):
|
||||
nullable_fields = ('comments',)
|
||||
|
||||
|
||||
class ManufacturerBulkEditForm(OrganizationalModelBulkEditForm):
|
||||
class ManufacturerBulkEditForm(NetBoxModelBulkEditForm):
|
||||
description = forms.CharField(
|
||||
label=_('Description'),
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
|
||||
model = Manufacturer
|
||||
fieldsets = (
|
||||
FieldSet('description'),
|
||||
@@ -477,7 +530,7 @@ class ManufacturerBulkEditForm(OrganizationalModelBulkEditForm):
|
||||
nullable_fields = ('description',)
|
||||
|
||||
|
||||
class DeviceTypeBulkEditForm(PrimaryModelBulkEditForm):
|
||||
class DeviceTypeBulkEditForm(NetBoxModelBulkEditForm):
|
||||
manufacturer = DynamicModelChoiceField(
|
||||
label=_('Manufacturer'),
|
||||
queryset=Manufacturer.objects.all(),
|
||||
@@ -523,6 +576,12 @@ class DeviceTypeBulkEditForm(PrimaryModelBulkEditForm):
|
||||
required=False,
|
||||
initial=''
|
||||
)
|
||||
description = forms.CharField(
|
||||
label=_('Description'),
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
model = DeviceType
|
||||
fieldsets = (
|
||||
@@ -535,11 +594,17 @@ class DeviceTypeBulkEditForm(PrimaryModelBulkEditForm):
|
||||
nullable_fields = ('part_number', 'airflow', 'weight', 'weight_unit', 'description', 'comments')
|
||||
|
||||
|
||||
class ModuleTypeProfileBulkEditForm(PrimaryModelBulkEditForm):
|
||||
class ModuleTypeProfileBulkEditForm(NetBoxModelBulkEditForm):
|
||||
schema = JSONField(
|
||||
label=_('Schema'),
|
||||
required=False
|
||||
)
|
||||
description = forms.CharField(
|
||||
label=_('Description'),
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
model = ModuleTypeProfile
|
||||
fieldsets = (
|
||||
@@ -548,7 +613,7 @@ class ModuleTypeProfileBulkEditForm(PrimaryModelBulkEditForm):
|
||||
nullable_fields = ('description', 'comments')
|
||||
|
||||
|
||||
class ModuleTypeBulkEditForm(PrimaryModelBulkEditForm):
|
||||
class ModuleTypeBulkEditForm(NetBoxModelBulkEditForm):
|
||||
profile = DynamicModelChoiceField(
|
||||
label=_('Profile'),
|
||||
queryset=ModuleTypeProfile.objects.all(),
|
||||
@@ -579,6 +644,12 @@ class ModuleTypeBulkEditForm(PrimaryModelBulkEditForm):
|
||||
required=False,
|
||||
initial=''
|
||||
)
|
||||
description = forms.CharField(
|
||||
label=_('Description'),
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
model = ModuleType
|
||||
fieldsets = (
|
||||
@@ -592,7 +663,7 @@ class ModuleTypeBulkEditForm(PrimaryModelBulkEditForm):
|
||||
nullable_fields = ('part_number', 'weight', 'weight_unit', 'profile', 'description', 'comments')
|
||||
|
||||
|
||||
class DeviceRoleBulkEditForm(NestedGroupModelBulkEditForm):
|
||||
class DeviceRoleBulkEditForm(NetBoxModelBulkEditForm):
|
||||
parent = DynamicModelChoiceField(
|
||||
label=_('Parent'),
|
||||
queryset=DeviceRole.objects.all(),
|
||||
@@ -612,6 +683,12 @@ class DeviceRoleBulkEditForm(NestedGroupModelBulkEditForm):
|
||||
queryset=ConfigTemplate.objects.all(),
|
||||
required=False
|
||||
)
|
||||
description = forms.CharField(
|
||||
label=_('Description'),
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
model = DeviceRole
|
||||
fieldsets = (
|
||||
@@ -620,7 +697,7 @@ class DeviceRoleBulkEditForm(NestedGroupModelBulkEditForm):
|
||||
nullable_fields = ('parent', 'color', 'config_template', 'description', 'comments')
|
||||
|
||||
|
||||
class PlatformBulkEditForm(NestedGroupModelBulkEditForm):
|
||||
class PlatformBulkEditForm(NetBoxModelBulkEditForm):
|
||||
parent = DynamicModelChoiceField(
|
||||
label=_('Parent'),
|
||||
queryset=Platform.objects.all(),
|
||||
@@ -636,6 +713,12 @@ class PlatformBulkEditForm(NestedGroupModelBulkEditForm):
|
||||
queryset=ConfigTemplate.objects.all(),
|
||||
required=False
|
||||
)
|
||||
description = forms.CharField(
|
||||
label=_('Description'),
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
model = Platform
|
||||
fieldsets = (
|
||||
@@ -644,7 +727,7 @@ class PlatformBulkEditForm(NestedGroupModelBulkEditForm):
|
||||
nullable_fields = ('parent', 'manufacturer', 'config_template', 'description', 'comments')
|
||||
|
||||
|
||||
class DeviceBulkEditForm(PrimaryModelBulkEditForm):
|
||||
class DeviceBulkEditForm(NetBoxModelBulkEditForm):
|
||||
manufacturer = DynamicModelChoiceField(
|
||||
label=_('Manufacturer'),
|
||||
queryset=Manufacturer.objects.all(),
|
||||
@@ -704,6 +787,11 @@ class DeviceBulkEditForm(PrimaryModelBulkEditForm):
|
||||
required=False,
|
||||
label=_('Serial Number')
|
||||
)
|
||||
description = forms.CharField(
|
||||
label=_('Description'),
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
config_template = DynamicModelChoiceField(
|
||||
label=_('Config template'),
|
||||
queryset=ConfigTemplate.objects.all(),
|
||||
@@ -717,6 +805,7 @@ class DeviceBulkEditForm(PrimaryModelBulkEditForm):
|
||||
'site_id': ['$site', 'null']
|
||||
},
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
model = Device
|
||||
fieldsets = (
|
||||
@@ -731,7 +820,7 @@ class DeviceBulkEditForm(PrimaryModelBulkEditForm):
|
||||
)
|
||||
|
||||
|
||||
class ModuleBulkEditForm(PrimaryModelBulkEditForm):
|
||||
class ModuleBulkEditForm(NetBoxModelBulkEditForm):
|
||||
manufacturer = DynamicModelChoiceField(
|
||||
label=_('Manufacturer'),
|
||||
queryset=Manufacturer.objects.all(),
|
||||
@@ -759,6 +848,12 @@ class ModuleBulkEditForm(PrimaryModelBulkEditForm):
|
||||
required=False,
|
||||
label=_('Serial Number')
|
||||
)
|
||||
description = forms.CharField(
|
||||
label=_('Description'),
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
model = Module
|
||||
fieldsets = (
|
||||
@@ -767,7 +862,7 @@ class ModuleBulkEditForm(PrimaryModelBulkEditForm):
|
||||
nullable_fields = ('serial', 'description', 'comments')
|
||||
|
||||
|
||||
class CableBulkEditForm(PrimaryModelBulkEditForm):
|
||||
class CableBulkEditForm(NetBoxModelBulkEditForm):
|
||||
type = forms.ChoiceField(
|
||||
label=_('Type'),
|
||||
choices=add_blank_choice(CableTypeChoices),
|
||||
@@ -805,6 +900,12 @@ class CableBulkEditForm(PrimaryModelBulkEditForm):
|
||||
required=False,
|
||||
initial=''
|
||||
)
|
||||
description = forms.CharField(
|
||||
label=_('Description'),
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
model = Cable
|
||||
fieldsets = (
|
||||
@@ -816,12 +917,18 @@ class CableBulkEditForm(PrimaryModelBulkEditForm):
|
||||
)
|
||||
|
||||
|
||||
class VirtualChassisBulkEditForm(PrimaryModelBulkEditForm):
|
||||
class VirtualChassisBulkEditForm(NetBoxModelBulkEditForm):
|
||||
domain = forms.CharField(
|
||||
label=_('Domain'),
|
||||
max_length=30,
|
||||
required=False
|
||||
)
|
||||
description = forms.CharField(
|
||||
label=_('Description'),
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
model = VirtualChassis
|
||||
fieldsets = (
|
||||
@@ -830,7 +937,7 @@ class VirtualChassisBulkEditForm(PrimaryModelBulkEditForm):
|
||||
nullable_fields = ('domain', 'description', 'comments')
|
||||
|
||||
|
||||
class PowerPanelBulkEditForm(PrimaryModelBulkEditForm):
|
||||
class PowerPanelBulkEditForm(NetBoxModelBulkEditForm):
|
||||
region = DynamicModelChoiceField(
|
||||
label=_('Region'),
|
||||
queryset=Region.objects.all(),
|
||||
@@ -864,6 +971,12 @@ class PowerPanelBulkEditForm(PrimaryModelBulkEditForm):
|
||||
'site_id': '$site'
|
||||
}
|
||||
)
|
||||
description = forms.CharField(
|
||||
label=_('Description'),
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
model = PowerPanel
|
||||
fieldsets = (
|
||||
@@ -872,7 +985,7 @@ class PowerPanelBulkEditForm(PrimaryModelBulkEditForm):
|
||||
nullable_fields = ('location', 'description', 'comments')
|
||||
|
||||
|
||||
class PowerFeedBulkEditForm(PrimaryModelBulkEditForm):
|
||||
class PowerFeedBulkEditForm(NetBoxModelBulkEditForm):
|
||||
power_panel = DynamicModelChoiceField(
|
||||
label=_('Power panel'),
|
||||
queryset=PowerPanel.objects.all(),
|
||||
@@ -928,6 +1041,12 @@ class PowerFeedBulkEditForm(PrimaryModelBulkEditForm):
|
||||
queryset=Tenant.objects.all(),
|
||||
required=False
|
||||
)
|
||||
description = forms.CharField(
|
||||
label=_('Description'),
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
model = PowerFeed
|
||||
fieldsets = (
|
||||
@@ -1044,10 +1163,6 @@ class PowerOutletTemplateBulkEditForm(ComponentTemplateBulkEditForm):
|
||||
choices=add_blank_choice(PowerOutletTypeChoices),
|
||||
required=False
|
||||
)
|
||||
color = ColorField(
|
||||
label=_('Color'),
|
||||
required=False
|
||||
)
|
||||
power_port = forms.ModelChoiceField(
|
||||
label=_('Power port'),
|
||||
queryset=PowerPortTemplate.objects.all(),
|
||||
@@ -1250,7 +1365,7 @@ class InventoryItemTemplateBulkEditForm(ComponentTemplateBulkEditForm):
|
||||
# Device components
|
||||
#
|
||||
|
||||
class ComponentBulkEditForm(OwnerMixin, NetBoxModelBulkEditForm):
|
||||
class ComponentBulkEditForm(NetBoxModelBulkEditForm):
|
||||
device = forms.ModelChoiceField(
|
||||
label=_('Device'),
|
||||
queryset=Device.objects.all(),
|
||||
@@ -1703,11 +1818,16 @@ class InventoryItemBulkEditForm(
|
||||
# Device component roles
|
||||
#
|
||||
|
||||
class InventoryItemRoleBulkEditForm(OrganizationalModelBulkEditForm):
|
||||
class InventoryItemRoleBulkEditForm(NetBoxModelBulkEditForm):
|
||||
color = ColorField(
|
||||
label=_('Color'),
|
||||
required=False
|
||||
)
|
||||
description = forms.CharField(
|
||||
label=_('Description'),
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
|
||||
model = InventoryItemRole
|
||||
fieldsets = (
|
||||
@@ -1716,7 +1836,7 @@ class InventoryItemRoleBulkEditForm(OrganizationalModelBulkEditForm):
|
||||
nullable_fields = ('color', 'description')
|
||||
|
||||
|
||||
class VirtualDeviceContextBulkEditForm(PrimaryModelBulkEditForm):
|
||||
class VirtualDeviceContextBulkEditForm(NetBoxModelBulkEditForm):
|
||||
device = DynamicModelChoiceField(
|
||||
label=_('Device'),
|
||||
queryset=Device.objects.all(),
|
||||
@@ -1732,7 +1852,6 @@ class VirtualDeviceContextBulkEditForm(PrimaryModelBulkEditForm):
|
||||
queryset=Tenant.objects.all(),
|
||||
required=False
|
||||
)
|
||||
|
||||
model = VirtualDeviceContext
|
||||
fieldsets = (
|
||||
FieldSet('device', 'status', 'tenant'),
|
||||
@@ -1744,7 +1863,14 @@ class VirtualDeviceContextBulkEditForm(PrimaryModelBulkEditForm):
|
||||
# Addressing
|
||||
#
|
||||
|
||||
class MACAddressBulkEditForm(PrimaryModelBulkEditForm):
|
||||
class MACAddressBulkEditForm(NetBoxModelBulkEditForm):
|
||||
description = forms.CharField(
|
||||
label=_('Description'),
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
model = MACAddress
|
||||
fieldsets = (
|
||||
FieldSet('description'),
|
||||
|
||||
@@ -11,10 +11,7 @@ from dcim.models import *
|
||||
from extras.models import ConfigTemplate
|
||||
from ipam.models import VRF, IPAddress
|
||||
from netbox.choices import *
|
||||
from netbox.forms import (
|
||||
NestedGroupModelImportForm, NetBoxModelImportForm, OrganizationalModelImportForm, OwnerCSVMixin,
|
||||
PrimaryModelImportForm,
|
||||
)
|
||||
from netbox.forms import NetBoxModelImportForm
|
||||
from tenancy.models import Tenant
|
||||
from utilities.forms.fields import (
|
||||
CSVChoiceField, CSVContentTypeField, CSVModelChoiceField, CSVModelMultipleChoiceField, CSVTypedChoiceField,
|
||||
@@ -61,7 +58,7 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class RegionImportForm(NestedGroupModelImportForm):
|
||||
class RegionImportForm(NetBoxModelImportForm):
|
||||
parent = CSVModelChoiceField(
|
||||
label=_('Parent'),
|
||||
queryset=Region.objects.all(),
|
||||
@@ -72,10 +69,10 @@ class RegionImportForm(NestedGroupModelImportForm):
|
||||
|
||||
class Meta:
|
||||
model = Region
|
||||
fields = ('name', 'slug', 'parent', 'description', 'owner', 'comments', 'tags')
|
||||
fields = ('name', 'slug', 'parent', 'description', 'tags', 'comments')
|
||||
|
||||
|
||||
class SiteGroupImportForm(NestedGroupModelImportForm):
|
||||
class SiteGroupImportForm(NetBoxModelImportForm):
|
||||
parent = CSVModelChoiceField(
|
||||
label=_('Parent'),
|
||||
queryset=SiteGroup.objects.all(),
|
||||
@@ -86,10 +83,10 @@ class SiteGroupImportForm(NestedGroupModelImportForm):
|
||||
|
||||
class Meta:
|
||||
model = SiteGroup
|
||||
fields = ('name', 'slug', 'parent', 'description', 'owner', 'comments', 'tags')
|
||||
fields = ('name', 'slug', 'parent', 'description', 'comments', 'tags')
|
||||
|
||||
|
||||
class SiteImportForm(PrimaryModelImportForm):
|
||||
class SiteImportForm(NetBoxModelImportForm):
|
||||
status = CSVChoiceField(
|
||||
label=_('Status'),
|
||||
choices=SiteStatusChoices,
|
||||
@@ -121,7 +118,7 @@ class SiteImportForm(PrimaryModelImportForm):
|
||||
model = Site
|
||||
fields = (
|
||||
'name', 'slug', 'status', 'region', 'group', 'tenant', 'facility', 'time_zone', 'description',
|
||||
'physical_address', 'shipping_address', 'latitude', 'longitude', 'owner', 'comments', 'tags'
|
||||
'physical_address', 'shipping_address', 'latitude', 'longitude', 'comments', 'tags'
|
||||
)
|
||||
help_texts = {
|
||||
'time_zone': mark_safe(
|
||||
@@ -132,7 +129,7 @@ class SiteImportForm(PrimaryModelImportForm):
|
||||
}
|
||||
|
||||
|
||||
class LocationImportForm(NestedGroupModelImportForm):
|
||||
class LocationImportForm(NetBoxModelImportForm):
|
||||
site = CSVModelChoiceField(
|
||||
label=_('Site'),
|
||||
queryset=Site.objects.all(),
|
||||
@@ -165,8 +162,8 @@ class LocationImportForm(NestedGroupModelImportForm):
|
||||
class Meta:
|
||||
model = Location
|
||||
fields = (
|
||||
'site', 'parent', 'name', 'slug', 'status', 'tenant', 'facility', 'description', 'owner', 'comments',
|
||||
'tags',
|
||||
'site', 'parent', 'name', 'slug', 'status', 'tenant', 'facility', 'description',
|
||||
'tags', 'comments',
|
||||
)
|
||||
|
||||
def __init__(self, data=None, *args, **kwargs):
|
||||
@@ -178,14 +175,15 @@ class LocationImportForm(NestedGroupModelImportForm):
|
||||
self.fields['parent'].queryset = self.fields['parent'].queryset.filter(**params)
|
||||
|
||||
|
||||
class RackRoleImportForm(OrganizationalModelImportForm):
|
||||
class RackRoleImportForm(NetBoxModelImportForm):
|
||||
slug = SlugField()
|
||||
|
||||
class Meta:
|
||||
model = RackRole
|
||||
fields = ('name', 'slug', 'color', 'description', 'owner', 'tags')
|
||||
fields = ('name', 'slug', 'color', 'description', 'tags')
|
||||
|
||||
|
||||
class RackTypeImportForm(PrimaryModelImportForm):
|
||||
class RackTypeImportForm(NetBoxModelImportForm):
|
||||
manufacturer = forms.ModelChoiceField(
|
||||
label=_('Manufacturer'),
|
||||
queryset=Manufacturer.objects.all(),
|
||||
@@ -226,14 +224,14 @@ class RackTypeImportForm(PrimaryModelImportForm):
|
||||
fields = (
|
||||
'manufacturer', 'model', 'slug', 'form_factor', 'width', 'u_height', 'starting_unit', 'desc_units',
|
||||
'outer_width', 'outer_height', 'outer_depth', 'outer_unit', 'mounting_depth', 'weight', 'max_weight',
|
||||
'weight_unit', 'description', 'owner', 'comments', 'tags',
|
||||
'weight_unit', 'description', 'comments', 'tags',
|
||||
)
|
||||
|
||||
def __init__(self, data=None, *args, **kwargs):
|
||||
super().__init__(data, *args, **kwargs)
|
||||
|
||||
|
||||
class RackImportForm(PrimaryModelImportForm):
|
||||
class RackImportForm(NetBoxModelImportForm):
|
||||
site = CSVModelChoiceField(
|
||||
label=_('Site'),
|
||||
queryset=Site.objects.all(),
|
||||
@@ -311,8 +309,7 @@ class RackImportForm(PrimaryModelImportForm):
|
||||
fields = (
|
||||
'site', 'location', 'name', 'facility_id', 'tenant', 'status', 'role', 'rack_type', 'form_factor', 'serial',
|
||||
'asset_tag', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_height', 'outer_depth', 'outer_unit',
|
||||
'mounting_depth', 'airflow', 'weight', 'max_weight', 'weight_unit', 'description', 'owner', 'comments',
|
||||
'tags',
|
||||
'mounting_depth', 'airflow', 'weight', 'max_weight', 'weight_unit', 'description', 'comments', 'tags',
|
||||
)
|
||||
|
||||
def __init__(self, data=None, *args, **kwargs):
|
||||
@@ -335,7 +332,7 @@ class RackImportForm(PrimaryModelImportForm):
|
||||
raise forms.ValidationError(_("U height must be set if not specifying a rack type."))
|
||||
|
||||
|
||||
class RackReservationImportForm(PrimaryModelImportForm):
|
||||
class RackReservationImportForm(NetBoxModelImportForm):
|
||||
site = CSVModelChoiceField(
|
||||
label=_('Site'),
|
||||
queryset=Site.objects.all(),
|
||||
@@ -376,7 +373,7 @@ class RackReservationImportForm(PrimaryModelImportForm):
|
||||
|
||||
class Meta:
|
||||
model = RackReservation
|
||||
fields = ('site', 'location', 'rack', 'units', 'status', 'tenant', 'description', 'owner', 'comments', 'tags')
|
||||
fields = ('site', 'location', 'rack', 'units', 'status', 'tenant', 'description', 'comments', 'tags')
|
||||
|
||||
def __init__(self, data=None, *args, **kwargs):
|
||||
super().__init__(data, *args, **kwargs)
|
||||
@@ -395,14 +392,14 @@ class RackReservationImportForm(PrimaryModelImportForm):
|
||||
self.fields['rack'].queryset = self.fields['rack'].queryset.filter(**params)
|
||||
|
||||
|
||||
class ManufacturerImportForm(OrganizationalModelImportForm):
|
||||
class ManufacturerImportForm(NetBoxModelImportForm):
|
||||
|
||||
class Meta:
|
||||
model = Manufacturer
|
||||
fields = ('name', 'slug', 'description', 'owner', 'tags')
|
||||
fields = ('name', 'slug', 'description', 'tags')
|
||||
|
||||
|
||||
class DeviceTypeImportForm(PrimaryModelImportForm):
|
||||
class DeviceTypeImportForm(NetBoxModelImportForm):
|
||||
manufacturer = CSVModelChoiceField(
|
||||
label=_('Manufacturer'),
|
||||
queryset=Manufacturer.objects.all(),
|
||||
@@ -432,21 +429,20 @@ class DeviceTypeImportForm(PrimaryModelImportForm):
|
||||
model = DeviceType
|
||||
fields = [
|
||||
'manufacturer', 'default_platform', 'model', 'slug', 'part_number', 'u_height', 'exclude_from_utilization',
|
||||
'is_full_depth', 'subdevice_role', 'airflow', 'description', 'weight', 'weight_unit', 'owner', 'comments',
|
||||
'tags',
|
||||
'is_full_depth', 'subdevice_role', 'airflow', 'description', 'weight', 'weight_unit', 'comments', 'tags',
|
||||
]
|
||||
|
||||
|
||||
class ModuleTypeProfileImportForm(PrimaryModelImportForm):
|
||||
class ModuleTypeProfileImportForm(NetBoxModelImportForm):
|
||||
|
||||
class Meta:
|
||||
model = ModuleTypeProfile
|
||||
fields = [
|
||||
'name', 'description', 'schema', 'owner', 'comments', 'tags',
|
||||
'name', 'description', 'schema', 'comments', 'tags',
|
||||
]
|
||||
|
||||
|
||||
class ModuleTypeImportForm(PrimaryModelImportForm):
|
||||
class ModuleTypeImportForm(NetBoxModelImportForm):
|
||||
profile = forms.ModelChoiceField(
|
||||
label=_('Profile'),
|
||||
queryset=ModuleTypeProfile.objects.all(),
|
||||
@@ -480,11 +476,11 @@ class ModuleTypeImportForm(PrimaryModelImportForm):
|
||||
model = ModuleType
|
||||
fields = [
|
||||
'manufacturer', 'model', 'part_number', 'description', 'airflow', 'weight', 'weight_unit', 'profile',
|
||||
'owner', 'comments', 'tags'
|
||||
'comments', 'tags'
|
||||
]
|
||||
|
||||
|
||||
class DeviceRoleImportForm(NestedGroupModelImportForm):
|
||||
class DeviceRoleImportForm(NetBoxModelImportForm):
|
||||
parent = CSVModelChoiceField(
|
||||
label=_('Parent'),
|
||||
queryset=DeviceRole.objects.all(),
|
||||
@@ -502,15 +498,17 @@ class DeviceRoleImportForm(NestedGroupModelImportForm):
|
||||
required=False,
|
||||
help_text=_('Config template')
|
||||
)
|
||||
slug = SlugField()
|
||||
|
||||
class Meta:
|
||||
model = DeviceRole
|
||||
fields = (
|
||||
'name', 'slug', 'parent', 'color', 'vm_role', 'config_template', 'description', 'owner', 'comments', 'tags'
|
||||
'name', 'slug', 'parent', 'color', 'vm_role', 'config_template', 'description', 'comments', 'tags'
|
||||
)
|
||||
|
||||
|
||||
class PlatformImportForm(NestedGroupModelImportForm):
|
||||
class PlatformImportForm(NetBoxModelImportForm):
|
||||
slug = SlugField()
|
||||
parent = CSVModelChoiceField(
|
||||
label=_('Parent'),
|
||||
queryset=Platform.objects.all(),
|
||||
@@ -539,11 +537,11 @@ class PlatformImportForm(NestedGroupModelImportForm):
|
||||
class Meta:
|
||||
model = Platform
|
||||
fields = (
|
||||
'name', 'slug', 'parent', 'manufacturer', 'config_template', 'description', 'owner', 'comments', 'tags',
|
||||
'name', 'slug', 'parent', 'manufacturer', 'config_template', 'description', 'tags',
|
||||
)
|
||||
|
||||
|
||||
class BaseDeviceImportForm(PrimaryModelImportForm):
|
||||
class BaseDeviceImportForm(NetBoxModelImportForm):
|
||||
role = CSVModelChoiceField(
|
||||
label=_('Device role'),
|
||||
queryset=DeviceRole.objects.all(),
|
||||
@@ -669,8 +667,8 @@ class DeviceImportForm(BaseDeviceImportForm):
|
||||
fields = [
|
||||
'name', 'role', 'tenant', 'manufacturer', 'device_type', 'platform', 'serial', 'asset_tag', 'status',
|
||||
'site', 'location', 'rack', 'position', 'face', 'latitude', 'longitude', 'parent', 'device_bay', 'airflow',
|
||||
'virtual_chassis', 'vc_position', 'vc_priority', 'cluster', 'description', 'config_template', 'owner',
|
||||
'comments', 'tags',
|
||||
'virtual_chassis', 'vc_position', 'vc_priority', 'cluster', 'description', 'config_template', 'comments',
|
||||
'tags',
|
||||
]
|
||||
|
||||
def __init__(self, data=None, *args, **kwargs):
|
||||
@@ -717,7 +715,7 @@ class DeviceImportForm(BaseDeviceImportForm):
|
||||
self.instance.parent_bay = device_bay
|
||||
|
||||
|
||||
class ModuleImportForm(ModuleCommonForm, PrimaryModelImportForm):
|
||||
class ModuleImportForm(ModuleCommonForm, NetBoxModelImportForm):
|
||||
device = CSVModelChoiceField(
|
||||
label=_('Device'),
|
||||
queryset=Device.objects.all(),
|
||||
@@ -755,7 +753,7 @@ class ModuleImportForm(ModuleCommonForm, PrimaryModelImportForm):
|
||||
class Meta:
|
||||
model = Module
|
||||
fields = (
|
||||
'device', 'module_bay', 'module_type', 'serial', 'asset_tag', 'status', 'description', 'owner', 'comments',
|
||||
'device', 'module_bay', 'module_type', 'serial', 'asset_tag', 'status', 'description', 'comments',
|
||||
'replicate_components', 'adopt_components', 'tags',
|
||||
)
|
||||
|
||||
@@ -779,7 +777,7 @@ class ModuleImportForm(ModuleCommonForm, PrimaryModelImportForm):
|
||||
# Device components
|
||||
#
|
||||
|
||||
class ConsolePortImportForm(OwnerCSVMixin, NetBoxModelImportForm):
|
||||
class ConsolePortImportForm(NetBoxModelImportForm):
|
||||
device = CSVModelChoiceField(
|
||||
label=_('Device'),
|
||||
queryset=Device.objects.all(),
|
||||
@@ -802,10 +800,10 @@ class ConsolePortImportForm(OwnerCSVMixin, NetBoxModelImportForm):
|
||||
|
||||
class Meta:
|
||||
model = ConsolePort
|
||||
fields = ('device', 'name', 'label', 'type', 'speed', 'mark_connected', 'description', 'owner', 'tags')
|
||||
fields = ('device', 'name', 'label', 'type', 'speed', 'mark_connected', 'description', 'tags')
|
||||
|
||||
|
||||
class ConsoleServerPortImportForm(OwnerCSVMixin, NetBoxModelImportForm):
|
||||
class ConsoleServerPortImportForm(NetBoxModelImportForm):
|
||||
device = CSVModelChoiceField(
|
||||
label=_('Device'),
|
||||
queryset=Device.objects.all(),
|
||||
@@ -828,10 +826,10 @@ class ConsoleServerPortImportForm(OwnerCSVMixin, NetBoxModelImportForm):
|
||||
|
||||
class Meta:
|
||||
model = ConsoleServerPort
|
||||
fields = ('device', 'name', 'label', 'type', 'speed', 'mark_connected', 'description', 'owner', 'tags')
|
||||
fields = ('device', 'name', 'label', 'type', 'speed', 'mark_connected', 'description', 'tags')
|
||||
|
||||
|
||||
class PowerPortImportForm(OwnerCSVMixin, NetBoxModelImportForm):
|
||||
class PowerPortImportForm(NetBoxModelImportForm):
|
||||
device = CSVModelChoiceField(
|
||||
label=_('Device'),
|
||||
queryset=Device.objects.all(),
|
||||
@@ -847,12 +845,11 @@ class PowerPortImportForm(OwnerCSVMixin, NetBoxModelImportForm):
|
||||
class Meta:
|
||||
model = PowerPort
|
||||
fields = (
|
||||
'device', 'name', 'label', 'type', 'mark_connected', 'maximum_draw', 'allocated_draw', 'description',
|
||||
'owner', 'tags',
|
||||
'device', 'name', 'label', 'type', 'mark_connected', 'maximum_draw', 'allocated_draw', 'description', 'tags'
|
||||
)
|
||||
|
||||
|
||||
class PowerOutletImportForm(OwnerCSVMixin, NetBoxModelImportForm):
|
||||
class PowerOutletImportForm(NetBoxModelImportForm):
|
||||
device = CSVModelChoiceField(
|
||||
label=_('Device'),
|
||||
queryset=Device.objects.all(),
|
||||
@@ -882,7 +879,7 @@ class PowerOutletImportForm(OwnerCSVMixin, NetBoxModelImportForm):
|
||||
model = PowerOutlet
|
||||
fields = (
|
||||
'device', 'name', 'label', 'type', 'color', 'mark_connected', 'power_port', 'feed_leg', 'description',
|
||||
'owner', 'tags',
|
||||
'tags',
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -908,7 +905,7 @@ class PowerOutletImportForm(OwnerCSVMixin, NetBoxModelImportForm):
|
||||
self.fields['power_port'].queryset = PowerPort.objects.none()
|
||||
|
||||
|
||||
class InterfaceImportForm(OwnerCSVMixin, NetBoxModelImportForm):
|
||||
class InterfaceImportForm(NetBoxModelImportForm):
|
||||
device = CSVModelChoiceField(
|
||||
label=_('Device'),
|
||||
queryset=Device.objects.all(),
|
||||
@@ -991,7 +988,7 @@ class InterfaceImportForm(OwnerCSVMixin, NetBoxModelImportForm):
|
||||
fields = (
|
||||
'device', 'name', 'label', 'parent', 'bridge', 'lag', 'type', 'speed', 'duplex', 'enabled',
|
||||
'mark_connected', 'wwn', 'vdcs', 'mtu', 'mgmt_only', 'description', 'poe_mode', 'poe_type', 'mode',
|
||||
'vrf', 'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'owner', 'tags'
|
||||
'vrf', 'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'tags'
|
||||
)
|
||||
|
||||
def __init__(self, data=None, *args, **kwargs):
|
||||
@@ -1026,7 +1023,7 @@ class InterfaceImportForm(OwnerCSVMixin, NetBoxModelImportForm):
|
||||
return self.cleaned_data['vdcs']
|
||||
|
||||
|
||||
class FrontPortImportForm(OwnerCSVMixin, NetBoxModelImportForm):
|
||||
class FrontPortImportForm(NetBoxModelImportForm):
|
||||
device = CSVModelChoiceField(
|
||||
label=_('Device'),
|
||||
queryset=Device.objects.all(),
|
||||
@@ -1048,7 +1045,7 @@ class FrontPortImportForm(OwnerCSVMixin, NetBoxModelImportForm):
|
||||
model = FrontPort
|
||||
fields = (
|
||||
'device', 'name', 'label', 'type', 'color', 'mark_connected', 'rear_port', 'rear_port_position',
|
||||
'description', 'owner', 'tags'
|
||||
'description', 'tags'
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -1074,7 +1071,7 @@ class FrontPortImportForm(OwnerCSVMixin, NetBoxModelImportForm):
|
||||
self.fields['rear_port'].queryset = RearPort.objects.none()
|
||||
|
||||
|
||||
class RearPortImportForm(OwnerCSVMixin, NetBoxModelImportForm):
|
||||
class RearPortImportForm(NetBoxModelImportForm):
|
||||
device = CSVModelChoiceField(
|
||||
label=_('Device'),
|
||||
queryset=Device.objects.all(),
|
||||
@@ -1088,12 +1085,10 @@ class RearPortImportForm(OwnerCSVMixin, NetBoxModelImportForm):
|
||||
|
||||
class Meta:
|
||||
model = RearPort
|
||||
fields = (
|
||||
'device', 'name', 'label', 'type', 'color', 'mark_connected', 'positions', 'description', 'owner', 'tags',
|
||||
)
|
||||
fields = ('device', 'name', 'label', 'type', 'color', 'mark_connected', 'positions', 'description', 'tags')
|
||||
|
||||
|
||||
class ModuleBayImportForm(OwnerCSVMixin, NetBoxModelImportForm):
|
||||
class ModuleBayImportForm(NetBoxModelImportForm):
|
||||
device = CSVModelChoiceField(
|
||||
label=_('Device'),
|
||||
queryset=Device.objects.all(),
|
||||
@@ -1102,10 +1097,10 @@ class ModuleBayImportForm(OwnerCSVMixin, NetBoxModelImportForm):
|
||||
|
||||
class Meta:
|
||||
model = ModuleBay
|
||||
fields = ('device', 'name', 'label', 'position', 'description', 'owner', 'tags')
|
||||
fields = ('device', 'name', 'label', 'position', 'description', 'tags')
|
||||
|
||||
|
||||
class DeviceBayImportForm(OwnerCSVMixin, NetBoxModelImportForm):
|
||||
class DeviceBayImportForm(NetBoxModelImportForm):
|
||||
device = CSVModelChoiceField(
|
||||
label=_('Device'),
|
||||
queryset=Device.objects.all(),
|
||||
@@ -1124,7 +1119,7 @@ class DeviceBayImportForm(OwnerCSVMixin, NetBoxModelImportForm):
|
||||
|
||||
class Meta:
|
||||
model = DeviceBay
|
||||
fields = ('device', 'name', 'label', 'installed_device', 'description', 'owner', 'tags')
|
||||
fields = ('device', 'name', 'label', 'installed_device', 'description', 'tags')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
@@ -1153,7 +1148,7 @@ class DeviceBayImportForm(OwnerCSVMixin, NetBoxModelImportForm):
|
||||
self.fields['installed_device'].queryset = Device.objects.none()
|
||||
|
||||
|
||||
class InventoryItemImportForm(OwnerCSVMixin, NetBoxModelImportForm):
|
||||
class InventoryItemImportForm(NetBoxModelImportForm):
|
||||
device = CSVModelChoiceField(
|
||||
label=_('Device'),
|
||||
queryset=Device.objects.all(),
|
||||
@@ -1200,7 +1195,7 @@ class InventoryItemImportForm(OwnerCSVMixin, NetBoxModelImportForm):
|
||||
model = InventoryItem
|
||||
fields = (
|
||||
'device', 'name', 'label', 'status', 'role', 'manufacturer', 'parent', 'part_id', 'serial', 'asset_tag',
|
||||
'discovered', 'description', 'owner', 'tags', 'component_type', 'component_name',
|
||||
'discovered', 'description', 'tags', 'component_type', 'component_name',
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -1263,7 +1258,7 @@ class InventoryItemImportForm(OwnerCSVMixin, NetBoxModelImportForm):
|
||||
# Device component roles
|
||||
#
|
||||
|
||||
class InventoryItemRoleImportForm(OrganizationalModelImportForm):
|
||||
class InventoryItemRoleImportForm(NetBoxModelImportForm):
|
||||
slug = SlugField()
|
||||
|
||||
class Meta:
|
||||
@@ -1275,7 +1270,7 @@ class InventoryItemRoleImportForm(OrganizationalModelImportForm):
|
||||
# Addressing
|
||||
#
|
||||
|
||||
class MACAddressImportForm(PrimaryModelImportForm):
|
||||
class MACAddressImportForm(NetBoxModelImportForm):
|
||||
device = CSVModelChoiceField(
|
||||
label=_('Device'),
|
||||
queryset=Device.objects.all(),
|
||||
@@ -1306,8 +1301,7 @@ class MACAddressImportForm(PrimaryModelImportForm):
|
||||
class Meta:
|
||||
model = MACAddress
|
||||
fields = [
|
||||
'mac_address', 'device', 'virtual_machine', 'interface', 'is_primary', 'description', 'owner', 'comments',
|
||||
'tags',
|
||||
'mac_address', 'device', 'virtual_machine', 'interface', 'is_primary', 'description', 'comments', 'tags',
|
||||
]
|
||||
|
||||
def __init__(self, data=None, *args, **kwargs):
|
||||
@@ -1360,7 +1354,7 @@ class MACAddressImportForm(PrimaryModelImportForm):
|
||||
# Cables
|
||||
#
|
||||
|
||||
class CableImportForm(PrimaryModelImportForm):
|
||||
class CableImportForm(NetBoxModelImportForm):
|
||||
# Termination A
|
||||
side_a_site = CSVModelChoiceField(
|
||||
label=_('Side A site'),
|
||||
@@ -1449,7 +1443,7 @@ class CableImportForm(PrimaryModelImportForm):
|
||||
fields = [
|
||||
'side_a_site', 'side_a_device', 'side_a_type', 'side_a_name', 'side_b_site', 'side_b_device', 'side_b_type',
|
||||
'side_b_name', 'type', 'status', 'tenant', 'label', 'color', 'length', 'length_unit', 'description',
|
||||
'owner', 'comments', 'tags',
|
||||
'comments', 'tags',
|
||||
]
|
||||
|
||||
def __init__(self, data=None, *args, **kwargs):
|
||||
@@ -1543,7 +1537,7 @@ class CableImportForm(PrimaryModelImportForm):
|
||||
#
|
||||
|
||||
|
||||
class VirtualChassisImportForm(PrimaryModelImportForm):
|
||||
class VirtualChassisImportForm(NetBoxModelImportForm):
|
||||
master = CSVModelChoiceField(
|
||||
label=_('Master'),
|
||||
queryset=Device.objects.all(),
|
||||
@@ -1554,14 +1548,14 @@ class VirtualChassisImportForm(PrimaryModelImportForm):
|
||||
|
||||
class Meta:
|
||||
model = VirtualChassis
|
||||
fields = ('name', 'domain', 'master', 'description', 'owner', 'comments', 'tags')
|
||||
fields = ('name', 'domain', 'master', 'description', 'comments', 'tags')
|
||||
|
||||
|
||||
#
|
||||
# Power
|
||||
#
|
||||
|
||||
class PowerPanelImportForm(PrimaryModelImportForm):
|
||||
class PowerPanelImportForm(NetBoxModelImportForm):
|
||||
site = CSVModelChoiceField(
|
||||
label=_('Site'),
|
||||
queryset=Site.objects.all(),
|
||||
@@ -1577,7 +1571,7 @@ class PowerPanelImportForm(PrimaryModelImportForm):
|
||||
|
||||
class Meta:
|
||||
model = PowerPanel
|
||||
fields = ('site', 'location', 'name', 'description', 'owner', 'comments', 'tags')
|
||||
fields = ('site', 'location', 'name', 'description', 'comments', 'tags')
|
||||
|
||||
def __init__(self, data=None, *args, **kwargs):
|
||||
super().__init__(data, *args, **kwargs)
|
||||
@@ -1589,7 +1583,7 @@ class PowerPanelImportForm(PrimaryModelImportForm):
|
||||
self.fields['location'].queryset = self.fields['location'].queryset.filter(**params)
|
||||
|
||||
|
||||
class PowerFeedImportForm(PrimaryModelImportForm):
|
||||
class PowerFeedImportForm(NetBoxModelImportForm):
|
||||
site = CSVModelChoiceField(
|
||||
label=_('Site'),
|
||||
queryset=Site.objects.all(),
|
||||
@@ -1647,7 +1641,7 @@ class PowerFeedImportForm(PrimaryModelImportForm):
|
||||
model = PowerFeed
|
||||
fields = (
|
||||
'site', 'power_panel', 'location', 'rack', 'name', 'status', 'type', 'mark_connected', 'supply', 'phase',
|
||||
'voltage', 'amperage', 'max_utilization', 'tenant', 'description', 'owner', 'comments', 'tags',
|
||||
'voltage', 'amperage', 'max_utilization', 'tenant', 'description', 'comments', 'tags',
|
||||
)
|
||||
|
||||
def __init__(self, data=None, *args, **kwargs):
|
||||
@@ -1671,7 +1665,8 @@ class PowerFeedImportForm(PrimaryModelImportForm):
|
||||
self.fields['rack'].queryset = self.fields['rack'].queryset.filter(**params)
|
||||
|
||||
|
||||
class VirtualDeviceContextImportForm(PrimaryModelImportForm):
|
||||
class VirtualDeviceContextImportForm(NetBoxModelImportForm):
|
||||
|
||||
device = CSVModelChoiceField(
|
||||
label=_('Device'),
|
||||
queryset=Device.objects.all(),
|
||||
@@ -1706,7 +1701,7 @@ class VirtualDeviceContextImportForm(PrimaryModelImportForm):
|
||||
|
||||
class Meta:
|
||||
fields = [
|
||||
'name', 'device', 'status', 'tenant', 'identifier', 'owner', 'comments', 'primary_ip4', 'primary_ip6',
|
||||
'name', 'device', 'status', 'tenant', 'identifier', 'comments', 'primary_ip4', 'primary_ip6',
|
||||
]
|
||||
model = VirtualDeviceContext
|
||||
|
||||
|
||||
@@ -8,14 +8,11 @@ from extras.forms import LocalConfigContextFilterForm
|
||||
from extras.models import ConfigTemplate
|
||||
from ipam.models import ASN, VRF, VLANTranslationPolicy
|
||||
from netbox.choices import *
|
||||
from netbox.forms import (
|
||||
NestedGroupModelFilterSetForm, NetBoxModelFilterSetForm, OrganizationalModelFilterSetForm,
|
||||
PrimaryModelFilterSetForm,
|
||||
)
|
||||
from netbox.forms import NetBoxModelFilterSetForm
|
||||
from tenancy.forms import ContactModelFilterForm, TenancyFilterForm
|
||||
from users.models import Owner, User
|
||||
from users.models import User
|
||||
from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES, FilterForm, add_blank_choice
|
||||
from utilities.forms.fields import ColorField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, TagFilterField
|
||||
from utilities.forms.fields import ColorField, DynamicModelMultipleChoiceField, TagFilterField
|
||||
from utilities.forms.rendering import FieldSet
|
||||
from utilities.forms.widgets import NumberWithOptions
|
||||
from virtualization.models import Cluster, ClusterGroup, VirtualMachine
|
||||
@@ -140,18 +137,12 @@ class DeviceComponentFilterForm(NetBoxModelFilterSetForm):
|
||||
required=False,
|
||||
label=_('Device Status'),
|
||||
)
|
||||
owner_id = DynamicModelChoiceField(
|
||||
queryset=Owner.objects.all(),
|
||||
required=False,
|
||||
label=_('Owner'),
|
||||
)
|
||||
|
||||
|
||||
class RegionFilterForm(ContactModelFilterForm, NestedGroupModelFilterSetForm):
|
||||
class RegionFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
|
||||
model = Region
|
||||
fieldsets = (
|
||||
FieldSet('q', 'filter_id', 'tag', 'owner_id'),
|
||||
FieldSet('parent_id', name=_('Region')),
|
||||
FieldSet('q', 'filter_id', 'tag', 'parent_id'),
|
||||
FieldSet('contact', 'contact_role', 'contact_group', name=_('Contacts'))
|
||||
)
|
||||
parent_id = DynamicModelMultipleChoiceField(
|
||||
@@ -162,11 +153,10 @@ class RegionFilterForm(ContactModelFilterForm, NestedGroupModelFilterSetForm):
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class SiteGroupFilterForm(ContactModelFilterForm, NestedGroupModelFilterSetForm):
|
||||
class SiteGroupFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
|
||||
model = SiteGroup
|
||||
fieldsets = (
|
||||
FieldSet('q', 'filter_id', 'tag', 'owner_id'),
|
||||
FieldSet('parent_id', name=_('Site Group')),
|
||||
FieldSet('q', 'filter_id', 'tag', 'parent_id'),
|
||||
FieldSet('contact', 'contact_role', 'contact_group', name=_('Contacts'))
|
||||
)
|
||||
parent_id = DynamicModelMultipleChoiceField(
|
||||
@@ -177,10 +167,10 @@ class SiteGroupFilterForm(ContactModelFilterForm, NestedGroupModelFilterSetForm)
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class SiteFilterForm(TenancyFilterForm, ContactModelFilterForm, PrimaryModelFilterSetForm):
|
||||
class SiteFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilterSetForm):
|
||||
model = Site
|
||||
fieldsets = (
|
||||
FieldSet('q', 'filter_id', 'tag', 'owner_id'),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('status', 'region_id', 'group_id', 'asn_id', name=_('Attributes')),
|
||||
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||
FieldSet('contact', 'contact_role', 'contact_group', name=_('Contacts')),
|
||||
@@ -209,10 +199,10 @@ class SiteFilterForm(TenancyFilterForm, ContactModelFilterForm, PrimaryModelFilt
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class LocationFilterForm(TenancyFilterForm, ContactModelFilterForm, NestedGroupModelFilterSetForm):
|
||||
class LocationFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilterSetForm):
|
||||
model = Location
|
||||
fieldsets = (
|
||||
FieldSet('q', 'filter_id', 'tag', 'owner_id'),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('region_id', 'site_group_id', 'site_id', 'parent_id', 'status', 'facility', name=_('Attributes')),
|
||||
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||
FieldSet('contact', 'contact_role', 'contact_group', name=_('Contacts')),
|
||||
@@ -257,15 +247,12 @@ class LocationFilterForm(TenancyFilterForm, ContactModelFilterForm, NestedGroupM
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class RackRoleFilterForm(OrganizationalModelFilterSetForm):
|
||||
class RackRoleFilterForm(NetBoxModelFilterSetForm):
|
||||
model = RackRole
|
||||
fieldsets = (
|
||||
FieldSet('q', 'filter_id', 'tag', 'owner_id'),
|
||||
)
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class RackBaseFilterForm(PrimaryModelFilterSetForm):
|
||||
class RackBaseFilterForm(NetBoxModelFilterSetForm):
|
||||
form_factor = forms.MultipleChoiceField(
|
||||
label=_('Form factor'),
|
||||
choices=RackFormFactorChoices,
|
||||
@@ -316,7 +303,7 @@ class RackBaseFilterForm(PrimaryModelFilterSetForm):
|
||||
class RackTypeFilterForm(RackBaseFilterForm):
|
||||
model = RackType
|
||||
fieldsets = (
|
||||
FieldSet('q', 'filter_id', 'tag', 'owner_id'),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('manufacturer_id', 'form_factor', 'width', 'u_height', name=_('Rack Type')),
|
||||
FieldSet('starting_unit', 'desc_units', name=_('Numbering')),
|
||||
FieldSet('weight', 'max_weight', 'weight_unit', name=_('Weight')),
|
||||
@@ -333,7 +320,7 @@ class RackTypeFilterForm(RackBaseFilterForm):
|
||||
class RackFilterForm(TenancyFilterForm, ContactModelFilterForm, RackBaseFilterForm):
|
||||
model = Rack
|
||||
fieldsets = (
|
||||
FieldSet('q', 'filter_id', 'tag', 'owner_id'),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', name=_('Location')),
|
||||
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||
FieldSet('status', 'role_id', 'manufacturer_id', 'rack_type_id', 'serial', 'asset_tag', name=_('Rack')),
|
||||
@@ -426,10 +413,10 @@ class RackElevationFilterForm(RackFilterForm):
|
||||
)
|
||||
|
||||
|
||||
class RackReservationFilterForm(TenancyFilterForm, PrimaryModelFilterSetForm):
|
||||
class RackReservationFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
model = RackReservation
|
||||
fieldsets = (
|
||||
FieldSet('q', 'filter_id', 'tag', 'owner_id'),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('status', 'user_id', name=_('Reservation')),
|
||||
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Rack')),
|
||||
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||
@@ -484,19 +471,19 @@ class RackReservationFilterForm(TenancyFilterForm, PrimaryModelFilterSetForm):
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class ManufacturerFilterForm(ContactModelFilterForm, OrganizationalModelFilterSetForm):
|
||||
class ManufacturerFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
|
||||
model = Manufacturer
|
||||
fieldsets = (
|
||||
FieldSet('q', 'filter_id', 'tag', 'owner_id'),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('contact', 'contact_role', 'contact_group', name=_('Contacts'))
|
||||
)
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class DeviceTypeFilterForm(PrimaryModelFilterSetForm):
|
||||
class DeviceTypeFilterForm(NetBoxModelFilterSetForm):
|
||||
model = DeviceType
|
||||
fieldsets = (
|
||||
FieldSet('q', 'filter_id', 'tag', 'owner_id'),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet(
|
||||
'manufacturer_id', 'default_platform_id', 'part_number', 'subdevice_role', 'airflow', name=_('Hardware')
|
||||
),
|
||||
@@ -621,18 +608,18 @@ class DeviceTypeFilterForm(PrimaryModelFilterSetForm):
|
||||
)
|
||||
|
||||
|
||||
class ModuleTypeProfileFilterForm(PrimaryModelFilterSetForm):
|
||||
class ModuleTypeProfileFilterForm(NetBoxModelFilterSetForm):
|
||||
model = ModuleTypeProfile
|
||||
fieldsets = (
|
||||
FieldSet('q', 'filter_id', 'tag', 'owner_id'),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
)
|
||||
selector_fields = ('filter_id', 'q')
|
||||
|
||||
|
||||
class ModuleTypeFilterForm(PrimaryModelFilterSetForm):
|
||||
class ModuleTypeFilterForm(NetBoxModelFilterSetForm):
|
||||
model = ModuleType
|
||||
fieldsets = (
|
||||
FieldSet('q', 'filter_id', 'tag', 'owner_id'),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('profile_id', 'manufacturer_id', 'part_number', 'airflow', name=_('Hardware')),
|
||||
FieldSet(
|
||||
'console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces',
|
||||
@@ -714,12 +701,8 @@ class ModuleTypeFilterForm(PrimaryModelFilterSetForm):
|
||||
)
|
||||
|
||||
|
||||
class DeviceRoleFilterForm(NestedGroupModelFilterSetForm):
|
||||
class DeviceRoleFilterForm(NetBoxModelFilterSetForm):
|
||||
model = DeviceRole
|
||||
fieldsets = (
|
||||
FieldSet('q', 'filter_id', 'tag', 'owner_id'),
|
||||
FieldSet('parent_id', 'config_template_id', name=_('Device Role'))
|
||||
)
|
||||
config_template_id = DynamicModelMultipleChoiceField(
|
||||
queryset=ConfigTemplate.objects.all(),
|
||||
required=False,
|
||||
@@ -733,12 +716,8 @@ class DeviceRoleFilterForm(NestedGroupModelFilterSetForm):
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class PlatformFilterForm(NestedGroupModelFilterSetForm):
|
||||
class PlatformFilterForm(NetBoxModelFilterSetForm):
|
||||
model = Platform
|
||||
fieldsets = (
|
||||
FieldSet('q', 'filter_id', 'tag', 'owner_id'),
|
||||
FieldSet('manufacturer_id', 'parent_id', 'config_template_id', name=_('Platform'))
|
||||
)
|
||||
selector_fields = ('filter_id', 'q', 'manufacturer_id')
|
||||
parent_id = DynamicModelMultipleChoiceField(
|
||||
queryset=Platform.objects.all(),
|
||||
@@ -762,11 +741,11 @@ class DeviceFilterForm(
|
||||
LocalConfigContextFilterForm,
|
||||
TenancyFilterForm,
|
||||
ContactModelFilterForm,
|
||||
PrimaryModelFilterSetForm
|
||||
NetBoxModelFilterSetForm
|
||||
):
|
||||
model = Device
|
||||
fieldsets = (
|
||||
FieldSet('q', 'filter_id', 'tag', 'owner_id'),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
|
||||
FieldSet('status', 'role_id', 'airflow', 'serial', 'asset_tag', 'mac_address', name=_('Operation')),
|
||||
FieldSet('manufacturer_id', 'device_type_id', 'platform_id', name=_('Hardware')),
|
||||
@@ -956,10 +935,13 @@ class DeviceFilterForm(
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class VirtualDeviceContextFilterForm(TenancyFilterForm, PrimaryModelFilterSetForm):
|
||||
class VirtualDeviceContextFilterForm(
|
||||
TenancyFilterForm,
|
||||
NetBoxModelFilterSetForm
|
||||
):
|
||||
model = VirtualDeviceContext
|
||||
fieldsets = (
|
||||
FieldSet('q', 'filter_id', 'tag', 'owner_id'),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('device', 'status', 'has_primary_ip', name=_('Attributes')),
|
||||
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||
)
|
||||
@@ -983,10 +965,10 @@ class VirtualDeviceContextFilterForm(TenancyFilterForm, PrimaryModelFilterSetFor
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class ModuleFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, PrimaryModelFilterSetForm):
|
||||
class ModuleFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
model = Module
|
||||
fieldsets = (
|
||||
FieldSet('q', 'filter_id', 'tag', 'owner_id'),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'device_id', name=_('Location')),
|
||||
FieldSet('manufacturer_id', 'module_type_id', 'status', 'serial', 'asset_tag', name=_('Hardware')),
|
||||
)
|
||||
@@ -1066,10 +1048,10 @@ class ModuleFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, PrimaryM
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class VirtualChassisFilterForm(TenancyFilterForm, PrimaryModelFilterSetForm):
|
||||
class VirtualChassisFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
model = VirtualChassis
|
||||
fieldsets = (
|
||||
FieldSet('q', 'filter_id', 'tag', 'owner_id'),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('region_id', 'site_group_id', 'site_id', name=_('Location')),
|
||||
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||
)
|
||||
@@ -1095,10 +1077,10 @@ class VirtualChassisFilterForm(TenancyFilterForm, PrimaryModelFilterSetForm):
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class CableFilterForm(TenancyFilterForm, PrimaryModelFilterSetForm):
|
||||
class CableFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
model = Cable
|
||||
fieldsets = (
|
||||
FieldSet('q', 'filter_id', 'tag', 'owner_id'),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('site_id', 'location_id', 'rack_id', 'device_id', name=_('Location')),
|
||||
FieldSet('type', 'status', 'color', 'length', 'length_unit', 'unterminated', name=_('Attributes')),
|
||||
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||
@@ -1179,10 +1161,10 @@ class CableFilterForm(TenancyFilterForm, PrimaryModelFilterSetForm):
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class PowerPanelFilterForm(ContactModelFilterForm, PrimaryModelFilterSetForm):
|
||||
class PowerPanelFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
|
||||
model = PowerPanel
|
||||
fieldsets = (
|
||||
FieldSet('q', 'filter_id', 'tag', 'owner_id'),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', name=_('Location')),
|
||||
FieldSet('contact', 'contact_role', 'contact_group', name=_('Contacts')),
|
||||
)
|
||||
@@ -1218,10 +1200,10 @@ class PowerPanelFilterForm(ContactModelFilterForm, PrimaryModelFilterSetForm):
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class PowerFeedFilterForm(TenancyFilterForm, PrimaryModelFilterSetForm):
|
||||
class PowerFeedFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
model = PowerFeed
|
||||
fieldsets = (
|
||||
FieldSet('q', 'filter_id', 'tag', 'owner_id'),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('region_id', 'site_group_id', 'site_id', 'power_panel_id', 'rack_id', name=_('Location')),
|
||||
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||
FieldSet('status', 'type', 'supply', 'phase', 'voltage', 'amperage', 'max_utilization', name=_('Attributes')),
|
||||
@@ -1331,7 +1313,7 @@ class PathEndpointFilterForm(CabledFilterForm):
|
||||
class ConsolePortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
|
||||
model = ConsolePort
|
||||
fieldsets = (
|
||||
FieldSet('q', 'filter_id', 'tag', 'owner_id'),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('name', 'label', 'type', 'speed', name=_('Attributes')),
|
||||
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
|
||||
FieldSet(
|
||||
@@ -1355,7 +1337,7 @@ class ConsolePortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
|
||||
class ConsoleServerPortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
|
||||
model = ConsoleServerPort
|
||||
fieldsets = (
|
||||
FieldSet('q', 'filter_id', 'tag', 'owner_id'),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('name', 'label', 'type', 'speed', name=_('Attributes')),
|
||||
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
|
||||
FieldSet(
|
||||
@@ -1380,7 +1362,7 @@ class ConsoleServerPortFilterForm(PathEndpointFilterForm, DeviceComponentFilterF
|
||||
class PowerPortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
|
||||
model = PowerPort
|
||||
fieldsets = (
|
||||
FieldSet('q', 'filter_id', 'tag', 'owner_id'),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('name', 'label', 'type', name=_('Attributes')),
|
||||
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
|
||||
FieldSet(
|
||||
@@ -1399,7 +1381,7 @@ class PowerPortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
|
||||
class PowerOutletFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
|
||||
model = PowerOutlet
|
||||
fieldsets = (
|
||||
FieldSet('q', 'filter_id', 'tag', 'owner_id'),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('name', 'label', 'type', 'color', 'status', name=_('Attributes')),
|
||||
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
|
||||
FieldSet(
|
||||
@@ -1428,7 +1410,7 @@ class PowerOutletFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
|
||||
class InterfaceFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
|
||||
model = Interface
|
||||
fieldsets = (
|
||||
FieldSet('q', 'filter_id', 'tag', 'owner_id'),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('name', 'label', 'kind', 'type', 'speed', 'duplex', 'enabled', 'mgmt_only', name=_('Attributes')),
|
||||
FieldSet('vrf_id', 'l2vpn_id', 'mac_address', 'wwn', name=_('Addressing')),
|
||||
FieldSet('poe_mode', 'poe_type', name=_('PoE')),
|
||||
@@ -1553,7 +1535,7 @@ class InterfaceFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
|
||||
|
||||
class FrontPortFilterForm(CabledFilterForm, DeviceComponentFilterForm):
|
||||
fieldsets = (
|
||||
FieldSet('q', 'filter_id', 'tag', 'owner_id'),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('name', 'label', 'type', 'color', name=_('Attributes')),
|
||||
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
|
||||
FieldSet(
|
||||
@@ -1577,7 +1559,7 @@ class FrontPortFilterForm(CabledFilterForm, DeviceComponentFilterForm):
|
||||
class RearPortFilterForm(CabledFilterForm, DeviceComponentFilterForm):
|
||||
model = RearPort
|
||||
fieldsets = (
|
||||
FieldSet('q', 'filter_id', 'tag', 'owner_id'),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('name', 'label', 'type', 'color', name=_('Attributes')),
|
||||
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
|
||||
FieldSet(
|
||||
@@ -1601,7 +1583,7 @@ class RearPortFilterForm(CabledFilterForm, DeviceComponentFilterForm):
|
||||
class ModuleBayFilterForm(DeviceComponentFilterForm):
|
||||
model = ModuleBay
|
||||
fieldsets = (
|
||||
FieldSet('q', 'filter_id', 'tag', 'owner_id'),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('name', 'label', 'position', name=_('Attributes')),
|
||||
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
|
||||
FieldSet(
|
||||
@@ -1619,7 +1601,7 @@ class ModuleBayFilterForm(DeviceComponentFilterForm):
|
||||
class DeviceBayFilterForm(DeviceComponentFilterForm):
|
||||
model = DeviceBay
|
||||
fieldsets = (
|
||||
FieldSet('q', 'filter_id', 'tag', 'owner_id'),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('name', 'label', name=_('Attributes')),
|
||||
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
|
||||
FieldSet(
|
||||
@@ -1633,7 +1615,7 @@ class DeviceBayFilterForm(DeviceComponentFilterForm):
|
||||
class InventoryItemFilterForm(DeviceComponentFilterForm):
|
||||
model = InventoryItem
|
||||
fieldsets = (
|
||||
FieldSet('q', 'filter_id', 'tag', 'owner_id'),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet(
|
||||
'name', 'label', 'status', 'role_id', 'manufacturer_id', 'serial', 'asset_tag', 'discovered',
|
||||
name=_('Attributes')
|
||||
@@ -1681,11 +1663,8 @@ class InventoryItemFilterForm(DeviceComponentFilterForm):
|
||||
# Device component roles
|
||||
#
|
||||
|
||||
class InventoryItemRoleFilterForm(OrganizationalModelFilterSetForm):
|
||||
class InventoryItemRoleFilterForm(NetBoxModelFilterSetForm):
|
||||
model = InventoryItemRole
|
||||
fieldsets = (
|
||||
FieldSet('q', 'filter_id', 'tag', 'owner_id'),
|
||||
)
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
@@ -1693,16 +1672,20 @@ class InventoryItemRoleFilterForm(OrganizationalModelFilterSetForm):
|
||||
# Addressing
|
||||
#
|
||||
|
||||
class MACAddressFilterForm(PrimaryModelFilterSetForm):
|
||||
class MACAddressFilterForm(NetBoxModelFilterSetForm):
|
||||
model = MACAddress
|
||||
fieldsets = (
|
||||
FieldSet('q', 'filter_id', 'tag', 'owner_id'),
|
||||
FieldSet('mac_address', 'device_id', 'virtual_machine_id', name=_('MAC address')),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('mac_address', name=_('Attributes')),
|
||||
FieldSet(
|
||||
'device_id', 'virtual_machine_id', 'assigned', 'primary',
|
||||
name=_('Assignments'),
|
||||
),
|
||||
)
|
||||
selector_fields = ('filter_id', 'q', 'device_id', 'virtual_machine_id')
|
||||
mac_address = forms.CharField(
|
||||
required=False,
|
||||
label=_('MAC address')
|
||||
label=_('MAC address'),
|
||||
)
|
||||
device_id = DynamicModelMultipleChoiceField(
|
||||
queryset=Device.objects.all(),
|
||||
@@ -1714,6 +1697,20 @@ class MACAddressFilterForm(PrimaryModelFilterSetForm):
|
||||
required=False,
|
||||
label=_('Assigned VM'),
|
||||
)
|
||||
assigned = forms.NullBooleanField(
|
||||
required=False,
|
||||
label=_('Assigned to an interface'),
|
||||
widget=forms.Select(
|
||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||
),
|
||||
)
|
||||
primary = forms.NullBooleanField(
|
||||
required=False,
|
||||
label=_('Primary MAC of an interface'),
|
||||
widget=forms.Select(
|
||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||
),
|
||||
)
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
|
||||
@@ -10,13 +10,13 @@ from dcim.models import *
|
||||
from extras.models import ConfigTemplate
|
||||
from ipam.choices import VLANQinQRoleChoices
|
||||
from ipam.models import ASN, IPAddress, VLAN, VLANGroup, VLANTranslationPolicy, VRF
|
||||
from netbox.forms import NestedGroupModelForm, NetBoxModelForm, OrganizationalModelForm, PrimaryModelForm
|
||||
from netbox.forms.mixins import ChangelogMessageMixin, OwnerMixin
|
||||
from netbox.forms import NetBoxModelForm
|
||||
from netbox.forms.mixins import ChangelogMessageMixin
|
||||
from tenancy.forms import TenancyForm
|
||||
from users.models import User
|
||||
from utilities.forms import add_blank_choice, get_field_value
|
||||
from utilities.forms.fields import (
|
||||
DynamicModelChoiceField, DynamicModelMultipleChoiceField, JSONField, NumericArrayField, SlugField,
|
||||
CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, JSONField, NumericArrayField, SlugField,
|
||||
)
|
||||
from utilities.forms.rendering import FieldSet, InlineFields, TabbedGroups
|
||||
from utilities.forms.widgets import APISelect, ClearableFileInput, HTMXSelect, NumberWithOptions, SelectWithPK
|
||||
@@ -75,12 +75,14 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class RegionForm(NestedGroupModelForm):
|
||||
class RegionForm(NetBoxModelForm):
|
||||
parent = DynamicModelChoiceField(
|
||||
label=_('Parent'),
|
||||
queryset=Region.objects.all(),
|
||||
required=False
|
||||
)
|
||||
slug = SlugField()
|
||||
comments = CommentField()
|
||||
|
||||
fieldsets = (
|
||||
FieldSet('parent', 'name', 'slug', 'description', 'tags'),
|
||||
@@ -89,16 +91,18 @@ class RegionForm(NestedGroupModelForm):
|
||||
class Meta:
|
||||
model = Region
|
||||
fields = (
|
||||
'parent', 'name', 'slug', 'description', 'owner', 'tags', 'comments',
|
||||
'parent', 'name', 'slug', 'description', 'tags', 'comments',
|
||||
)
|
||||
|
||||
|
||||
class SiteGroupForm(NestedGroupModelForm):
|
||||
class SiteGroupForm(NetBoxModelForm):
|
||||
parent = DynamicModelChoiceField(
|
||||
label=_('Parent'),
|
||||
queryset=SiteGroup.objects.all(),
|
||||
required=False
|
||||
)
|
||||
slug = SlugField()
|
||||
comments = CommentField()
|
||||
|
||||
fieldsets = (
|
||||
FieldSet('parent', 'name', 'slug', 'description', 'tags'),
|
||||
@@ -107,11 +111,11 @@ class SiteGroupForm(NestedGroupModelForm):
|
||||
class Meta:
|
||||
model = SiteGroup
|
||||
fields = (
|
||||
'parent', 'name', 'slug', 'description', 'owner', 'comments', 'tags',
|
||||
'parent', 'name', 'slug', 'description', 'comments', 'tags',
|
||||
)
|
||||
|
||||
|
||||
class SiteForm(TenancyForm, PrimaryModelForm):
|
||||
class SiteForm(TenancyForm, NetBoxModelForm):
|
||||
region = DynamicModelChoiceField(
|
||||
label=_('Region'),
|
||||
queryset=Region.objects.all(),
|
||||
@@ -135,6 +139,7 @@ class SiteForm(TenancyForm, PrimaryModelForm):
|
||||
choices=add_blank_choice(TimeZoneFormField().choices),
|
||||
required=False
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
fieldsets = (
|
||||
FieldSet(
|
||||
@@ -149,7 +154,7 @@ class SiteForm(TenancyForm, PrimaryModelForm):
|
||||
model = Site
|
||||
fields = (
|
||||
'name', 'slug', 'status', 'region', 'group', 'tenant_group', 'tenant', 'facility', 'asns', 'time_zone',
|
||||
'description', 'physical_address', 'shipping_address', 'latitude', 'longitude', 'owner', 'comments', 'tags',
|
||||
'description', 'physical_address', 'shipping_address', 'latitude', 'longitude', 'comments', 'tags',
|
||||
)
|
||||
widgets = {
|
||||
'physical_address': forms.Textarea(
|
||||
@@ -165,7 +170,7 @@ class SiteForm(TenancyForm, PrimaryModelForm):
|
||||
}
|
||||
|
||||
|
||||
class LocationForm(TenancyForm, NestedGroupModelForm):
|
||||
class LocationForm(TenancyForm, NetBoxModelForm):
|
||||
site = DynamicModelChoiceField(
|
||||
label=_('Site'),
|
||||
queryset=Site.objects.all(),
|
||||
@@ -179,6 +184,8 @@ class LocationForm(TenancyForm, NestedGroupModelForm):
|
||||
'site_id': '$site'
|
||||
}
|
||||
)
|
||||
slug = SlugField()
|
||||
comments = CommentField()
|
||||
|
||||
fieldsets = (
|
||||
FieldSet('site', 'parent', 'name', 'slug', 'status', 'facility', 'description', 'tags', name=_('Location')),
|
||||
@@ -188,12 +195,14 @@ class LocationForm(TenancyForm, NestedGroupModelForm):
|
||||
class Meta:
|
||||
model = Location
|
||||
fields = (
|
||||
'site', 'parent', 'name', 'slug', 'status', 'description', 'tenant_group', 'tenant', 'facility', 'owner',
|
||||
'comments', 'tags',
|
||||
'site', 'parent', 'name', 'slug', 'status', 'description', 'tenant_group', 'tenant',
|
||||
'facility', 'tags', 'comments',
|
||||
)
|
||||
|
||||
|
||||
class RackRoleForm(OrganizationalModelForm):
|
||||
class RackRoleForm(NetBoxModelForm):
|
||||
slug = SlugField()
|
||||
|
||||
fieldsets = (
|
||||
FieldSet('name', 'slug', 'color', 'description', 'tags', name=_('Rack Role')),
|
||||
)
|
||||
@@ -201,16 +210,17 @@ class RackRoleForm(OrganizationalModelForm):
|
||||
class Meta:
|
||||
model = RackRole
|
||||
fields = [
|
||||
'name', 'slug', 'color', 'description', 'owner', 'tags',
|
||||
'name', 'slug', 'color', 'description', 'tags',
|
||||
]
|
||||
|
||||
|
||||
class RackTypeForm(PrimaryModelForm):
|
||||
class RackTypeForm(NetBoxModelForm):
|
||||
manufacturer = DynamicModelChoiceField(
|
||||
label=_('Manufacturer'),
|
||||
queryset=Manufacturer.objects.all(),
|
||||
quick_add=True
|
||||
)
|
||||
comments = CommentField()
|
||||
slug = SlugField(
|
||||
label=_('Slug'),
|
||||
slug_source='model'
|
||||
@@ -232,11 +242,11 @@ class RackTypeForm(PrimaryModelForm):
|
||||
fields = [
|
||||
'manufacturer', 'model', 'slug', 'form_factor', 'width', 'u_height', 'starting_unit', 'desc_units',
|
||||
'outer_width', 'outer_height', 'outer_depth', 'outer_unit', 'mounting_depth', 'weight', 'max_weight',
|
||||
'weight_unit', 'description', 'owner', 'comments', 'tags',
|
||||
'weight_unit', 'description', 'comments', 'tags',
|
||||
]
|
||||
|
||||
|
||||
class RackForm(TenancyForm, PrimaryModelForm):
|
||||
class RackForm(TenancyForm, NetBoxModelForm):
|
||||
site = DynamicModelChoiceField(
|
||||
label=_('Site'),
|
||||
queryset=Site.objects.all(),
|
||||
@@ -261,6 +271,7 @@ class RackForm(TenancyForm, PrimaryModelForm):
|
||||
required=False,
|
||||
help_text=_("Select a pre-defined rack type, or set physical characteristics below.")
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
fieldsets = (
|
||||
FieldSet(
|
||||
@@ -277,7 +288,7 @@ class RackForm(TenancyForm, PrimaryModelForm):
|
||||
'site', 'location', 'name', 'facility_id', 'tenant_group', 'tenant', 'status', 'role', 'serial',
|
||||
'asset_tag', 'rack_type', 'form_factor', 'width', 'u_height', 'starting_unit', 'desc_units', 'outer_width',
|
||||
'outer_height', 'outer_depth', 'outer_unit', 'mounting_depth', 'airflow', 'weight', 'max_weight',
|
||||
'weight_unit', 'description', 'owner', 'comments', 'tags',
|
||||
'weight_unit', 'description', 'comments', 'tags',
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -307,7 +318,7 @@ class RackForm(TenancyForm, PrimaryModelForm):
|
||||
)
|
||||
|
||||
|
||||
class RackReservationForm(TenancyForm, PrimaryModelForm):
|
||||
class RackReservationForm(TenancyForm, NetBoxModelForm):
|
||||
rack = DynamicModelChoiceField(
|
||||
label=_('Rack'),
|
||||
queryset=Rack.objects.all(),
|
||||
@@ -322,6 +333,7 @@ class RackReservationForm(TenancyForm, PrimaryModelForm):
|
||||
label=_('User'),
|
||||
queryset=User.objects.order_by('username')
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
fieldsets = (
|
||||
FieldSet('rack', 'units', 'status', 'user', 'description', 'tags', name=_('Reservation')),
|
||||
@@ -331,11 +343,13 @@ class RackReservationForm(TenancyForm, PrimaryModelForm):
|
||||
class Meta:
|
||||
model = RackReservation
|
||||
fields = [
|
||||
'rack', 'units', 'status', 'user', 'tenant_group', 'tenant', 'description', 'owner', 'comments', 'tags',
|
||||
'rack', 'units', 'status', 'user', 'tenant_group', 'tenant', 'description', 'comments', 'tags',
|
||||
]
|
||||
|
||||
|
||||
class ManufacturerForm(OrganizationalModelForm):
|
||||
class ManufacturerForm(NetBoxModelForm):
|
||||
slug = SlugField()
|
||||
|
||||
fieldsets = (
|
||||
FieldSet('name', 'slug', 'description', 'tags', name=_('Manufacturer')),
|
||||
)
|
||||
@@ -343,11 +357,11 @@ class ManufacturerForm(OrganizationalModelForm):
|
||||
class Meta:
|
||||
model = Manufacturer
|
||||
fields = [
|
||||
'name', 'slug', 'description', 'owner', 'tags',
|
||||
'name', 'slug', 'description', 'tags',
|
||||
]
|
||||
|
||||
|
||||
class DeviceTypeForm(PrimaryModelForm):
|
||||
class DeviceTypeForm(NetBoxModelForm):
|
||||
manufacturer = DynamicModelChoiceField(
|
||||
label=_('Manufacturer'),
|
||||
queryset=Manufacturer.objects.all(),
|
||||
@@ -366,6 +380,7 @@ class DeviceTypeForm(PrimaryModelForm):
|
||||
label=_('Slug'),
|
||||
slug_source='model'
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
fieldsets = (
|
||||
FieldSet('manufacturer', 'model', 'slug', 'default_platform', 'description', 'tags', name=_('Device Type')),
|
||||
@@ -381,7 +396,7 @@ class DeviceTypeForm(PrimaryModelForm):
|
||||
fields = [
|
||||
'manufacturer', 'model', 'slug', 'default_platform', 'part_number', 'u_height', 'exclude_from_utilization',
|
||||
'is_full_depth', 'subdevice_role', 'airflow', 'weight', 'weight_unit', 'front_image', 'rear_image',
|
||||
'description', 'owner', 'comments', 'tags',
|
||||
'description', 'comments', 'tags',
|
||||
]
|
||||
widgets = {
|
||||
'front_image': ClearableFileInput(attrs={
|
||||
@@ -393,12 +408,13 @@ class DeviceTypeForm(PrimaryModelForm):
|
||||
}
|
||||
|
||||
|
||||
class ModuleTypeProfileForm(PrimaryModelForm):
|
||||
class ModuleTypeProfileForm(NetBoxModelForm):
|
||||
schema = JSONField(
|
||||
label=_('Schema'),
|
||||
required=False,
|
||||
help_text=_("Enter a valid JSON schema to define supported attributes.")
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
fieldsets = (
|
||||
FieldSet('name', 'description', 'schema', 'tags', name=_('Profile')),
|
||||
@@ -407,11 +423,11 @@ class ModuleTypeProfileForm(PrimaryModelForm):
|
||||
class Meta:
|
||||
model = ModuleTypeProfile
|
||||
fields = [
|
||||
'name', 'description', 'schema', 'owner', 'comments', 'tags',
|
||||
'name', 'description', 'schema', 'comments', 'tags',
|
||||
]
|
||||
|
||||
|
||||
class ModuleTypeForm(PrimaryModelForm):
|
||||
class ModuleTypeForm(NetBoxModelForm):
|
||||
profile = forms.ModelChoiceField(
|
||||
queryset=ModuleTypeProfile.objects.all(),
|
||||
label=_('Profile'),
|
||||
@@ -422,6 +438,7 @@ class ModuleTypeForm(PrimaryModelForm):
|
||||
label=_('Manufacturer'),
|
||||
queryset=Manufacturer.objects.all()
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
@property
|
||||
def fieldsets(self):
|
||||
@@ -435,7 +452,7 @@ class ModuleTypeForm(PrimaryModelForm):
|
||||
model = ModuleType
|
||||
fields = [
|
||||
'profile', 'manufacturer', 'model', 'part_number', 'description', 'airflow', 'weight', 'weight_unit',
|
||||
'owner', 'comments', 'tags',
|
||||
'comments', 'tags',
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -490,17 +507,19 @@ class ModuleTypeForm(PrimaryModelForm):
|
||||
return super()._post_clean()
|
||||
|
||||
|
||||
class DeviceRoleForm(NestedGroupModelForm):
|
||||
class DeviceRoleForm(NetBoxModelForm):
|
||||
config_template = DynamicModelChoiceField(
|
||||
label=_('Config template'),
|
||||
queryset=ConfigTemplate.objects.all(),
|
||||
required=False
|
||||
)
|
||||
slug = SlugField()
|
||||
parent = DynamicModelChoiceField(
|
||||
label=_('Parent'),
|
||||
queryset=DeviceRole.objects.all(),
|
||||
required=False,
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
fieldsets = (
|
||||
FieldSet(
|
||||
@@ -512,11 +531,11 @@ class DeviceRoleForm(NestedGroupModelForm):
|
||||
class Meta:
|
||||
model = DeviceRole
|
||||
fields = [
|
||||
'name', 'slug', 'parent', 'color', 'vm_role', 'config_template', 'description', 'owner', 'comments', 'tags',
|
||||
'name', 'slug', 'parent', 'color', 'vm_role', 'config_template', 'description', 'comments', 'tags',
|
||||
]
|
||||
|
||||
|
||||
class PlatformForm(NestedGroupModelForm):
|
||||
class PlatformForm(NetBoxModelForm):
|
||||
parent = DynamicModelChoiceField(
|
||||
label=_('Parent'),
|
||||
queryset=Platform.objects.all(),
|
||||
@@ -537,6 +556,7 @@ class PlatformForm(NestedGroupModelForm):
|
||||
label=_('Slug'),
|
||||
max_length=64
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
fieldsets = (
|
||||
FieldSet(
|
||||
@@ -547,11 +567,11 @@ class PlatformForm(NestedGroupModelForm):
|
||||
class Meta:
|
||||
model = Platform
|
||||
fields = [
|
||||
'name', 'slug', 'parent', 'manufacturer', 'config_template', 'description', 'owner', 'comments', 'tags',
|
||||
'name', 'slug', 'parent', 'manufacturer', 'config_template', 'description', 'comments', 'tags',
|
||||
]
|
||||
|
||||
|
||||
class DeviceForm(TenancyForm, PrimaryModelForm):
|
||||
class DeviceForm(TenancyForm, NetBoxModelForm):
|
||||
site = DynamicModelChoiceField(
|
||||
label=_('Site'),
|
||||
queryset=Site.objects.all(),
|
||||
@@ -621,6 +641,7 @@ class DeviceForm(TenancyForm, PrimaryModelForm):
|
||||
'site_id': ['$site', 'null']
|
||||
},
|
||||
)
|
||||
comments = CommentField()
|
||||
local_context_data = JSONField(
|
||||
required=False,
|
||||
label=''
|
||||
@@ -656,7 +677,7 @@ class DeviceForm(TenancyForm, PrimaryModelForm):
|
||||
'name', 'role', 'device_type', 'serial', 'asset_tag', 'site', 'rack', 'location', 'position', 'face',
|
||||
'latitude', 'longitude', 'status', 'airflow', 'platform', 'primary_ip4', 'primary_ip6', 'oob_ip', 'cluster',
|
||||
'tenant_group', 'tenant', 'virtual_chassis', 'vc_position', 'vc_priority', 'description', 'config_template',
|
||||
'owner', 'comments', 'tags', 'local_context_data',
|
||||
'comments', 'tags', 'local_context_data',
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -721,7 +742,7 @@ class DeviceForm(TenancyForm, PrimaryModelForm):
|
||||
self.fields['position'].widget.choices = [(position, f'U{position}')]
|
||||
|
||||
|
||||
class ModuleForm(ModuleCommonForm, PrimaryModelForm):
|
||||
class ModuleForm(ModuleCommonForm, NetBoxModelForm):
|
||||
device = DynamicModelChoiceField(
|
||||
label=_('Device'),
|
||||
queryset=Device.objects.all(),
|
||||
@@ -734,7 +755,10 @@ class ModuleForm(ModuleCommonForm, PrimaryModelForm):
|
||||
queryset=ModuleBay.objects.all(),
|
||||
query_params={
|
||||
'device_id': '$device'
|
||||
}
|
||||
},
|
||||
context={
|
||||
'disabled': 'installed_module',
|
||||
},
|
||||
)
|
||||
module_type = DynamicModelChoiceField(
|
||||
label=_('Module type'),
|
||||
@@ -744,6 +768,7 @@ class ModuleForm(ModuleCommonForm, PrimaryModelForm):
|
||||
},
|
||||
selector=True
|
||||
)
|
||||
comments = CommentField()
|
||||
replicate_components = forms.BooleanField(
|
||||
label=_('Replicate components'),
|
||||
required=False,
|
||||
@@ -766,7 +791,7 @@ class ModuleForm(ModuleCommonForm, PrimaryModelForm):
|
||||
model = Module
|
||||
fields = [
|
||||
'device', 'module_bay', 'module_type', 'status', 'serial', 'asset_tag', 'tags', 'replicate_components',
|
||||
'adopt_components', 'description', 'owner', 'comments',
|
||||
'adopt_components', 'description', 'comments',
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -787,7 +812,7 @@ def get_termination_type_choices():
|
||||
])
|
||||
|
||||
|
||||
class CableForm(TenancyForm, PrimaryModelForm):
|
||||
class CableForm(TenancyForm, NetBoxModelForm):
|
||||
a_terminations_type = forms.ChoiceField(
|
||||
choices=get_termination_type_choices,
|
||||
required=False,
|
||||
@@ -800,16 +825,17 @@ class CableForm(TenancyForm, PrimaryModelForm):
|
||||
widget=HTMXSelect(),
|
||||
label=_('Type')
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
class Meta:
|
||||
model = Cable
|
||||
fields = [
|
||||
'a_terminations_type', 'b_terminations_type', 'type', 'status', 'tenant_group', 'tenant', 'label', 'color',
|
||||
'length', 'length_unit', 'description', 'owner', 'comments', 'tags',
|
||||
'length', 'length_unit', 'description', 'comments', 'tags',
|
||||
]
|
||||
|
||||
|
||||
class PowerPanelForm(PrimaryModelForm):
|
||||
class PowerPanelForm(NetBoxModelForm):
|
||||
site = DynamicModelChoiceField(
|
||||
label=_('Site'),
|
||||
queryset=Site.objects.all(),
|
||||
@@ -823,6 +849,7 @@ class PowerPanelForm(PrimaryModelForm):
|
||||
'site_id': '$site'
|
||||
}
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
fieldsets = (
|
||||
FieldSet('site', 'location', 'name', 'description', 'tags', name=_('Power Panel')),
|
||||
@@ -831,11 +858,11 @@ class PowerPanelForm(PrimaryModelForm):
|
||||
class Meta:
|
||||
model = PowerPanel
|
||||
fields = [
|
||||
'site', 'location', 'name', 'description', 'owner', 'comments', 'tags',
|
||||
'site', 'location', 'name', 'description', 'comments', 'tags',
|
||||
]
|
||||
|
||||
|
||||
class PowerFeedForm(TenancyForm, PrimaryModelForm):
|
||||
class PowerFeedForm(TenancyForm, NetBoxModelForm):
|
||||
power_panel = DynamicModelChoiceField(
|
||||
label=_('Power panel'),
|
||||
queryset=PowerPanel.objects.all(),
|
||||
@@ -848,6 +875,7 @@ class PowerFeedForm(TenancyForm, PrimaryModelForm):
|
||||
required=False,
|
||||
selector=True
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
fieldsets = (
|
||||
FieldSet(
|
||||
@@ -862,7 +890,7 @@ class PowerFeedForm(TenancyForm, PrimaryModelForm):
|
||||
model = PowerFeed
|
||||
fields = [
|
||||
'power_panel', 'rack', 'name', 'status', 'type', 'mark_connected', 'supply', 'phase', 'voltage', 'amperage',
|
||||
'max_utilization', 'tenant_group', 'tenant', 'description', 'owner', 'comments', 'tags'
|
||||
'max_utilization', 'tenant_group', 'tenant', 'description', 'comments', 'tags'
|
||||
]
|
||||
|
||||
|
||||
@@ -870,17 +898,18 @@ class PowerFeedForm(TenancyForm, PrimaryModelForm):
|
||||
# Virtual chassis
|
||||
#
|
||||
|
||||
class VirtualChassisForm(PrimaryModelForm):
|
||||
class VirtualChassisForm(NetBoxModelForm):
|
||||
master = forms.ModelChoiceField(
|
||||
label=_('Master'),
|
||||
queryset=Device.objects.all(),
|
||||
required=False,
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
class Meta:
|
||||
model = VirtualChassis
|
||||
fields = [
|
||||
'name', 'domain', 'master', 'description', 'owner', 'comments', 'tags',
|
||||
'name', 'domain', 'master', 'description', 'comments', 'tags',
|
||||
]
|
||||
widgets = {
|
||||
'master': SelectWithPK(),
|
||||
@@ -1066,14 +1095,14 @@ class PowerOutletTemplateForm(ModularComponentTemplateForm):
|
||||
FieldSet('device_type', name=_('Device Type')),
|
||||
FieldSet('module_type', name=_('Module Type')),
|
||||
),
|
||||
'name', 'label', 'type', 'color', 'power_port', 'feed_leg', 'description',
|
||||
'name', 'label', 'type', 'power_port', 'feed_leg', 'description',
|
||||
),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = PowerOutletTemplate
|
||||
fields = [
|
||||
'device_type', 'module_type', 'name', 'label', 'type', 'color', 'power_port', 'feed_leg', 'description',
|
||||
'device_type', 'module_type', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description',
|
||||
]
|
||||
|
||||
|
||||
@@ -1334,7 +1363,7 @@ class InventoryItemTemplateForm(ComponentTemplateForm):
|
||||
# Device components
|
||||
#
|
||||
|
||||
class DeviceComponentForm(OwnerMixin, NetBoxModelForm):
|
||||
class DeviceComponentForm(NetBoxModelForm):
|
||||
device = DynamicModelChoiceField(
|
||||
label=_('Device'),
|
||||
queryset=Device.objects.all(),
|
||||
@@ -1370,7 +1399,7 @@ class ConsolePortForm(ModularDeviceComponentForm):
|
||||
class Meta:
|
||||
model = ConsolePort
|
||||
fields = [
|
||||
'device', 'module', 'name', 'label', 'type', 'speed', 'mark_connected', 'description', 'owner', 'tags',
|
||||
'device', 'module', 'name', 'label', 'type', 'speed', 'mark_connected', 'description', 'tags',
|
||||
]
|
||||
|
||||
|
||||
@@ -1384,7 +1413,7 @@ class ConsoleServerPortForm(ModularDeviceComponentForm):
|
||||
class Meta:
|
||||
model = ConsoleServerPort
|
||||
fields = [
|
||||
'device', 'module', 'name', 'label', 'type', 'speed', 'mark_connected', 'description', 'owner', 'tags',
|
||||
'device', 'module', 'name', 'label', 'type', 'speed', 'mark_connected', 'description', 'tags',
|
||||
]
|
||||
|
||||
|
||||
@@ -1400,7 +1429,7 @@ class PowerPortForm(ModularDeviceComponentForm):
|
||||
model = PowerPort
|
||||
fields = [
|
||||
'device', 'module', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'mark_connected',
|
||||
'description', 'owner', 'tags',
|
||||
'description', 'tags',
|
||||
]
|
||||
|
||||
|
||||
@@ -1417,7 +1446,7 @@ class PowerOutletForm(ModularDeviceComponentForm):
|
||||
fieldsets = (
|
||||
FieldSet(
|
||||
'device', 'module', 'name', 'label', 'type', 'status', 'color', 'power_port', 'feed_leg', 'mark_connected',
|
||||
'description', 'owner', 'tags',
|
||||
'description', 'tags',
|
||||
),
|
||||
)
|
||||
|
||||
@@ -1561,7 +1590,7 @@ class InterfaceForm(InterfaceCommonForm, ModularDeviceComponentForm):
|
||||
'lag', 'wwn', 'mtu', 'mgmt_only', 'mark_connected', 'description', 'poe_mode', 'poe_type', 'mode',
|
||||
'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'wireless_lans',
|
||||
'untagged_vlan', 'tagged_vlans', 'qinq_svlan', 'vlan_translation_policy', 'vrf', 'primary_mac_address',
|
||||
'owner', 'tags',
|
||||
'tags',
|
||||
]
|
||||
widgets = {
|
||||
'speed': NumberWithOptions(
|
||||
@@ -1593,7 +1622,7 @@ class FrontPortForm(ModularDeviceComponentForm):
|
||||
model = FrontPort
|
||||
fields = [
|
||||
'device', 'module', 'name', 'label', 'type', 'color', 'rear_port', 'rear_port_position', 'mark_connected',
|
||||
'description', 'owner', 'tags',
|
||||
'description', 'tags',
|
||||
]
|
||||
|
||||
|
||||
@@ -1607,8 +1636,7 @@ class RearPortForm(ModularDeviceComponentForm):
|
||||
class Meta:
|
||||
model = RearPort
|
||||
fields = [
|
||||
'device', 'module', 'name', 'label', 'type', 'color', 'positions', 'mark_connected', 'description', 'owner',
|
||||
'tags',
|
||||
'device', 'module', 'name', 'label', 'type', 'color', 'positions', 'mark_connected', 'description', 'tags',
|
||||
]
|
||||
|
||||
|
||||
@@ -1620,7 +1648,7 @@ class ModuleBayForm(ModularDeviceComponentForm):
|
||||
class Meta:
|
||||
model = ModuleBay
|
||||
fields = [
|
||||
'device', 'module', 'name', 'label', 'position', 'description', 'owner', 'tags',
|
||||
'device', 'module', 'name', 'label', 'position', 'description', 'tags',
|
||||
]
|
||||
|
||||
|
||||
@@ -1632,7 +1660,7 @@ class DeviceBayForm(DeviceComponentForm):
|
||||
class Meta:
|
||||
model = DeviceBay
|
||||
fields = [
|
||||
'device', 'name', 'label', 'description', 'owner', 'tags',
|
||||
'device', 'name', 'label', 'description', 'tags',
|
||||
]
|
||||
|
||||
|
||||
@@ -1757,7 +1785,7 @@ class InventoryItemForm(DeviceComponentForm):
|
||||
model = InventoryItem
|
||||
fields = [
|
||||
'device', 'parent', 'name', 'label', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag',
|
||||
'status', 'description', 'owner', 'tags',
|
||||
'status', 'description', 'tags',
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -1803,7 +1831,12 @@ class InventoryItemForm(DeviceComponentForm):
|
||||
self.instance.component = None
|
||||
|
||||
|
||||
class InventoryItemRoleForm(OrganizationalModelForm):
|
||||
# Device component roles
|
||||
#
|
||||
|
||||
class InventoryItemRoleForm(NetBoxModelForm):
|
||||
slug = SlugField()
|
||||
|
||||
fieldsets = (
|
||||
FieldSet('name', 'slug', 'color', 'description', 'tags', name=_('Inventory Item Role')),
|
||||
)
|
||||
@@ -1811,11 +1844,11 @@ class InventoryItemRoleForm(OrganizationalModelForm):
|
||||
class Meta:
|
||||
model = InventoryItemRole
|
||||
fields = [
|
||||
'name', 'slug', 'color', 'description', 'owner', 'tags',
|
||||
'name', 'slug', 'color', 'description', 'tags',
|
||||
]
|
||||
|
||||
|
||||
class VirtualDeviceContextForm(TenancyForm, PrimaryModelForm):
|
||||
class VirtualDeviceContextForm(TenancyForm, NetBoxModelForm):
|
||||
device = DynamicModelChoiceField(
|
||||
label=_('Device'),
|
||||
queryset=Device.objects.all(),
|
||||
@@ -1851,7 +1884,7 @@ class VirtualDeviceContextForm(TenancyForm, PrimaryModelForm):
|
||||
class Meta:
|
||||
model = VirtualDeviceContext
|
||||
fields = [
|
||||
'device', 'name', 'status', 'identifier', 'primary_ip4', 'primary_ip6', 'tenant_group', 'tenant', 'owner',
|
||||
'device', 'name', 'status', 'identifier', 'primary_ip4', 'primary_ip6', 'tenant_group', 'tenant',
|
||||
'comments', 'tags'
|
||||
]
|
||||
|
||||
@@ -1860,7 +1893,7 @@ class VirtualDeviceContextForm(TenancyForm, PrimaryModelForm):
|
||||
# Addressing
|
||||
#
|
||||
|
||||
class MACAddressForm(PrimaryModelForm):
|
||||
class MACAddressForm(NetBoxModelForm):
|
||||
mac_address = forms.CharField(
|
||||
required=True,
|
||||
label=_('MAC address')
|
||||
@@ -1899,7 +1932,7 @@ class MACAddressForm(PrimaryModelForm):
|
||||
class Meta:
|
||||
model = MACAddress
|
||||
fields = [
|
||||
'mac_address', 'interface', 'vminterface', 'description', 'owner', 'tags',
|
||||
'mac_address', 'interface', 'vminterface', 'description', 'tags',
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
@@ -434,8 +434,8 @@ class VirtualChassisCreateForm(NetBoxModelForm):
|
||||
class Meta:
|
||||
model = VirtualChassis
|
||||
fields = [
|
||||
'name', 'domain', 'description', 'region', 'site_group', 'site', 'rack', 'owner', 'members',
|
||||
'initial_position', 'tags',
|
||||
'name', 'domain', 'description', 'region', 'site_group', 'site', 'rack', 'members', 'initial_position',
|
||||
'tags',
|
||||
]
|
||||
|
||||
def clean(self):
|
||||
|
||||
@@ -18,7 +18,9 @@ from netbox.graphql.filter_mixins import (
|
||||
ImageAttachmentFilterMixin,
|
||||
WeightFilterMixin,
|
||||
)
|
||||
from tenancy.graphql.filter_mixins import TenancyFilterMixin, ContactFilterMixin
|
||||
from tenancy.graphql.filter_mixins import ContactFilterMixin, TenancyFilterMixin
|
||||
from virtualization.models import VMInterface
|
||||
|
||||
from .filter_mixins import (
|
||||
CabledObjectModelFilterMixin,
|
||||
ComponentModelFilterMixin,
|
||||
@@ -419,6 +421,24 @@ class MACAddressFilter(PrimaryModelFilterMixin):
|
||||
)
|
||||
assigned_object_id: ID | None = strawberry_django.filter_field()
|
||||
|
||||
@strawberry_django.filter_field()
|
||||
def assigned(self, value: bool, prefix) -> Q:
|
||||
return Q(**{f'{prefix}assigned_object_id__isnull': (not value)})
|
||||
|
||||
@strawberry_django.filter_field()
|
||||
def primary(self, value: bool, prefix) -> Q:
|
||||
interface_mac_ids = models.Interface.objects.filter(primary_mac_address_id__isnull=False).values_list(
|
||||
'primary_mac_address_id', flat=True
|
||||
)
|
||||
vminterface_mac_ids = VMInterface.objects.filter(primary_mac_address_id__isnull=False).values_list(
|
||||
'primary_mac_address_id', flat=True
|
||||
)
|
||||
query = Q(**{f'{prefix}pk__in': interface_mac_ids}) | Q(**{f'{prefix}pk__in': vminterface_mac_ids})
|
||||
if value:
|
||||
return Q(query)
|
||||
else:
|
||||
return ~Q(query)
|
||||
|
||||
|
||||
@strawberry_django.filter_type(models.Interface, lookups=True)
|
||||
class InterfaceFilter(ModularComponentModelFilterMixin, InterfaceBaseFilterMixin, CabledObjectModelFilterMixin):
|
||||
|
||||
@@ -5,13 +5,16 @@ import strawberry_django
|
||||
|
||||
from core.graphql.mixins import ChangelogMixin
|
||||
from dcim import models
|
||||
from extras.graphql.mixins import ConfigContextMixin, ContactsMixin, ImageAttachmentsMixin
|
||||
from extras.graphql.mixins import (
|
||||
ConfigContextMixin,
|
||||
ContactsMixin,
|
||||
CustomFieldsMixin,
|
||||
ImageAttachmentsMixin,
|
||||
TagsMixin,
|
||||
)
|
||||
from ipam.graphql.mixins import IPAddressesMixin, VLANGroupsMixin
|
||||
from netbox.graphql.scalars import BigInt
|
||||
from netbox.graphql.types import (
|
||||
BaseObjectType, NestedGroupObjectType, NetBoxObjectType, OrganizationalObjectType, PrimaryObjectType,
|
||||
)
|
||||
from users.graphql.mixins import OwnerMixin
|
||||
from netbox.graphql.types import BaseObjectType, NetBoxObjectType, OrganizationalObjectType
|
||||
from .filters import *
|
||||
from .mixins import CabledObjectMixin, PathEndpointMixin
|
||||
|
||||
@@ -88,7 +91,12 @@ __all__ = (
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class ComponentType(OwnerMixin, NetBoxObjectType):
|
||||
class ComponentType(
|
||||
ChangelogMixin,
|
||||
CustomFieldsMixin,
|
||||
TagsMixin,
|
||||
BaseObjectType
|
||||
):
|
||||
"""
|
||||
Base type for device/VM components
|
||||
"""
|
||||
@@ -151,7 +159,7 @@ class CableTerminationType(NetBoxObjectType):
|
||||
filters=CableFilter,
|
||||
pagination=True
|
||||
)
|
||||
class CableType(PrimaryObjectType):
|
||||
class CableType(NetBoxObjectType):
|
||||
color: str
|
||||
tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
|
||||
|
||||
@@ -228,7 +236,7 @@ class ConsoleServerPortTemplateType(ModularComponentTemplateType):
|
||||
filters=DeviceFilter,
|
||||
pagination=True
|
||||
)
|
||||
class DeviceType(ConfigContextMixin, ImageAttachmentsMixin, ContactsMixin, PrimaryObjectType):
|
||||
class DeviceType(ConfigContextMixin, ImageAttachmentsMixin, ContactsMixin, NetBoxObjectType):
|
||||
console_port_count: BigInt
|
||||
console_server_port_count: BigInt
|
||||
power_port_count: BigInt
|
||||
@@ -331,7 +339,7 @@ class InventoryItemTemplateType(ComponentTemplateType):
|
||||
filters=DeviceRoleFilter,
|
||||
pagination=True
|
||||
)
|
||||
class DeviceRoleType(NestedGroupObjectType):
|
||||
class DeviceRoleType(OrganizationalObjectType):
|
||||
parent: Annotated['DeviceRoleType', strawberry.lazy('dcim.graphql.types')] | None
|
||||
children: List[Annotated['DeviceRoleType', strawberry.lazy('dcim.graphql.types')]]
|
||||
color: str
|
||||
@@ -347,7 +355,7 @@ class DeviceRoleType(NestedGroupObjectType):
|
||||
filters=DeviceTypeFilter,
|
||||
pagination=True
|
||||
)
|
||||
class DeviceTypeType(PrimaryObjectType):
|
||||
class DeviceTypeType(NetBoxObjectType):
|
||||
console_port_template_count: BigInt
|
||||
console_server_port_template_count: BigInt
|
||||
power_port_template_count: BigInt
|
||||
@@ -404,7 +412,7 @@ class FrontPortTemplateType(ModularComponentTemplateType):
|
||||
filters=MACAddressFilter,
|
||||
pagination=True
|
||||
)
|
||||
class MACAddressType(PrimaryObjectType):
|
||||
class MACAddressType(NetBoxObjectType):
|
||||
mac_address: str
|
||||
|
||||
@strawberry_django.field
|
||||
@@ -504,7 +512,7 @@ class InventoryItemRoleType(OrganizationalObjectType):
|
||||
filters=LocationFilter,
|
||||
pagination=True
|
||||
)
|
||||
class LocationType(VLANGroupsMixin, ImageAttachmentsMixin, ContactsMixin, NestedGroupObjectType):
|
||||
class LocationType(VLANGroupsMixin, ImageAttachmentsMixin, ContactsMixin, OrganizationalObjectType):
|
||||
site: Annotated["SiteType", strawberry.lazy('dcim.graphql.types')]
|
||||
tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
|
||||
parent: Annotated["LocationType", strawberry.lazy('dcim.graphql.types')] | None
|
||||
@@ -547,7 +555,7 @@ class ManufacturerType(OrganizationalObjectType, ContactsMixin):
|
||||
filters=ModuleFilter,
|
||||
pagination=True
|
||||
)
|
||||
class ModuleType(PrimaryObjectType):
|
||||
class ModuleType(NetBoxObjectType):
|
||||
device: Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')]
|
||||
module_bay: Annotated["ModuleBayType", strawberry.lazy('dcim.graphql.types')]
|
||||
module_type: Annotated["ModuleTypeType", strawberry.lazy('dcim.graphql.types')]
|
||||
@@ -594,7 +602,7 @@ class ModuleBayTemplateType(ModularComponentTemplateType):
|
||||
filters=ModuleTypeProfileFilter,
|
||||
pagination=True
|
||||
)
|
||||
class ModuleTypeProfileType(PrimaryObjectType):
|
||||
class ModuleTypeProfileType(NetBoxObjectType):
|
||||
module_types: List[Annotated["ModuleType", strawberry.lazy('dcim.graphql.types')]]
|
||||
|
||||
|
||||
@@ -604,7 +612,7 @@ class ModuleTypeProfileType(PrimaryObjectType):
|
||||
filters=ModuleTypeFilter,
|
||||
pagination=True
|
||||
)
|
||||
class ModuleTypeType(PrimaryObjectType):
|
||||
class ModuleTypeType(NetBoxObjectType):
|
||||
profile: Annotated["ModuleTypeProfileType", strawberry.lazy('dcim.graphql.types')] | None
|
||||
manufacturer: Annotated["ManufacturerType", strawberry.lazy('dcim.graphql.types')]
|
||||
|
||||
@@ -624,7 +632,7 @@ class ModuleTypeType(PrimaryObjectType):
|
||||
filters=PlatformFilter,
|
||||
pagination=True
|
||||
)
|
||||
class PlatformType(NestedGroupObjectType):
|
||||
class PlatformType(OrganizationalObjectType):
|
||||
parent: Annotated['PlatformType', strawberry.lazy('dcim.graphql.types')] | None
|
||||
children: List[Annotated['PlatformType', strawberry.lazy('dcim.graphql.types')]]
|
||||
manufacturer: Annotated["ManufacturerType", strawberry.lazy('dcim.graphql.types')] | None
|
||||
@@ -640,7 +648,7 @@ class PlatformType(NestedGroupObjectType):
|
||||
filters=PowerFeedFilter,
|
||||
pagination=True
|
||||
)
|
||||
class PowerFeedType(CabledObjectMixin, PathEndpointMixin, PrimaryObjectType):
|
||||
class PowerFeedType(NetBoxObjectType, CabledObjectMixin, PathEndpointMixin):
|
||||
power_panel: Annotated["PowerPanelType", strawberry.lazy('dcim.graphql.types')]
|
||||
rack: Annotated["RackType", strawberry.lazy('dcim.graphql.types')] | None
|
||||
tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
|
||||
@@ -665,7 +673,6 @@ class PowerOutletType(ModularComponentType, CabledObjectMixin, PathEndpointMixin
|
||||
)
|
||||
class PowerOutletTemplateType(ModularComponentTemplateType):
|
||||
power_port: Annotated["PowerPortTemplateType", strawberry.lazy('dcim.graphql.types')] | None
|
||||
color: str
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
@@ -674,7 +681,7 @@ class PowerOutletTemplateType(ModularComponentTemplateType):
|
||||
filters=PowerPanelFilter,
|
||||
pagination=True
|
||||
)
|
||||
class PowerPanelType(ContactsMixin, PrimaryObjectType):
|
||||
class PowerPanelType(NetBoxObjectType, ContactsMixin):
|
||||
site: Annotated["SiteType", strawberry.lazy('dcim.graphql.types')]
|
||||
location: Annotated["LocationType", strawberry.lazy('dcim.graphql.types')] | None
|
||||
|
||||
@@ -708,7 +715,7 @@ class PowerPortTemplateType(ModularComponentTemplateType):
|
||||
filters=RackTypeFilter,
|
||||
pagination=True
|
||||
)
|
||||
class RackTypeType(PrimaryObjectType):
|
||||
class RackTypeType(NetBoxObjectType):
|
||||
manufacturer: Annotated["ManufacturerType", strawberry.lazy('dcim.graphql.types')]
|
||||
|
||||
|
||||
@@ -718,7 +725,7 @@ class RackTypeType(PrimaryObjectType):
|
||||
filters=RackFilter,
|
||||
pagination=True
|
||||
)
|
||||
class RackType(VLANGroupsMixin, ImageAttachmentsMixin, ContactsMixin, PrimaryObjectType):
|
||||
class RackType(VLANGroupsMixin, ImageAttachmentsMixin, ContactsMixin, NetBoxObjectType):
|
||||
site: Annotated["SiteType", strawberry.lazy('dcim.graphql.types')]
|
||||
location: Annotated["LocationType", strawberry.lazy('dcim.graphql.types')] | None
|
||||
tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
|
||||
@@ -737,7 +744,7 @@ class RackType(VLANGroupsMixin, ImageAttachmentsMixin, ContactsMixin, PrimaryObj
|
||||
filters=RackReservationFilter,
|
||||
pagination=True
|
||||
)
|
||||
class RackReservationType(PrimaryObjectType):
|
||||
class RackReservationType(NetBoxObjectType):
|
||||
units: List[int]
|
||||
rack: Annotated["RackType", strawberry.lazy('dcim.graphql.types')]
|
||||
tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
|
||||
@@ -786,7 +793,7 @@ class RearPortTemplateType(ModularComponentTemplateType):
|
||||
filters=RegionFilter,
|
||||
pagination=True
|
||||
)
|
||||
class RegionType(VLANGroupsMixin, ContactsMixin, NestedGroupObjectType):
|
||||
class RegionType(VLANGroupsMixin, ContactsMixin, OrganizationalObjectType):
|
||||
|
||||
sites: List[Annotated["SiteType", strawberry.lazy('dcim.graphql.types')]]
|
||||
children: List[Annotated["RegionType", strawberry.lazy('dcim.graphql.types')]]
|
||||
@@ -812,7 +819,7 @@ class RegionType(VLANGroupsMixin, ContactsMixin, NestedGroupObjectType):
|
||||
filters=SiteFilter,
|
||||
pagination=True
|
||||
)
|
||||
class SiteType(VLANGroupsMixin, ImageAttachmentsMixin, ContactsMixin, PrimaryObjectType):
|
||||
class SiteType(VLANGroupsMixin, ImageAttachmentsMixin, ContactsMixin, NetBoxObjectType):
|
||||
time_zone: str | None
|
||||
region: Annotated["RegionType", strawberry.lazy('dcim.graphql.types')] | None
|
||||
group: Annotated["SiteGroupType", strawberry.lazy('dcim.graphql.types')] | None
|
||||
@@ -847,7 +854,7 @@ class SiteType(VLANGroupsMixin, ImageAttachmentsMixin, ContactsMixin, PrimaryObj
|
||||
filters=SiteGroupFilter,
|
||||
pagination=True
|
||||
)
|
||||
class SiteGroupType(VLANGroupsMixin, ContactsMixin, NestedGroupObjectType):
|
||||
class SiteGroupType(VLANGroupsMixin, ContactsMixin, OrganizationalObjectType):
|
||||
|
||||
sites: List[Annotated["SiteType", strawberry.lazy('dcim.graphql.types')]]
|
||||
children: List[Annotated["SiteGroupType", strawberry.lazy('dcim.graphql.types')]]
|
||||
@@ -873,7 +880,7 @@ class SiteGroupType(VLANGroupsMixin, ContactsMixin, NestedGroupObjectType):
|
||||
filters=VirtualChassisFilter,
|
||||
pagination=True
|
||||
)
|
||||
class VirtualChassisType(PrimaryObjectType):
|
||||
class VirtualChassisType(NetBoxObjectType):
|
||||
member_count: BigInt
|
||||
master: Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')] | None
|
||||
|
||||
@@ -886,7 +893,7 @@ class VirtualChassisType(PrimaryObjectType):
|
||||
filters=VirtualDeviceContextFilter,
|
||||
pagination=True
|
||||
)
|
||||
class VirtualDeviceContextType(PrimaryObjectType):
|
||||
class VirtualDeviceContextType(NetBoxObjectType):
|
||||
device: Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')] | None
|
||||
primary_ip4: Annotated["IPAddressType", strawberry.lazy('ipam.graphql.types')] | None
|
||||
primary_ip6: Annotated["IPAddressType", strawberry.lazy('ipam.graphql.types')] | None
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
import utilities.fields
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('dcim', '0215_rackreservation_status'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='poweroutlettemplate',
|
||||
name='color',
|
||||
field=utilities.fields.ColorField(blank=True, max_length=6),
|
||||
),
|
||||
]
|
||||
@@ -1,243 +0,0 @@
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('dcim', '0216_poweroutlettemplate_color'),
|
||||
('users', '0015_owner'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='cable',
|
||||
name='owner',
|
||||
field=models.ForeignKey(
|
||||
blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='consoleport',
|
||||
name='owner',
|
||||
field=models.ForeignKey(
|
||||
blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='consoleserverport',
|
||||
name='owner',
|
||||
field=models.ForeignKey(
|
||||
blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='device',
|
||||
name='owner',
|
||||
field=models.ForeignKey(
|
||||
blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='devicebay',
|
||||
name='owner',
|
||||
field=models.ForeignKey(
|
||||
blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='devicerole',
|
||||
name='owner',
|
||||
field=models.ForeignKey(
|
||||
blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='devicetype',
|
||||
name='owner',
|
||||
field=models.ForeignKey(
|
||||
blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='frontport',
|
||||
name='owner',
|
||||
field=models.ForeignKey(
|
||||
blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='interface',
|
||||
name='owner',
|
||||
field=models.ForeignKey(
|
||||
blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='inventoryitem',
|
||||
name='owner',
|
||||
field=models.ForeignKey(
|
||||
blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='inventoryitemrole',
|
||||
name='owner',
|
||||
field=models.ForeignKey(
|
||||
blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='location',
|
||||
name='owner',
|
||||
field=models.ForeignKey(
|
||||
blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='macaddress',
|
||||
name='owner',
|
||||
field=models.ForeignKey(
|
||||
blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='manufacturer',
|
||||
name='owner',
|
||||
field=models.ForeignKey(
|
||||
blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='module',
|
||||
name='owner',
|
||||
field=models.ForeignKey(
|
||||
blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='modulebay',
|
||||
name='owner',
|
||||
field=models.ForeignKey(
|
||||
blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='moduletype',
|
||||
name='owner',
|
||||
field=models.ForeignKey(
|
||||
blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='moduletypeprofile',
|
||||
name='owner',
|
||||
field=models.ForeignKey(
|
||||
blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='platform',
|
||||
name='owner',
|
||||
field=models.ForeignKey(
|
||||
blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='powerfeed',
|
||||
name='owner',
|
||||
field=models.ForeignKey(
|
||||
blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='poweroutlet',
|
||||
name='owner',
|
||||
field=models.ForeignKey(
|
||||
blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='powerpanel',
|
||||
name='owner',
|
||||
field=models.ForeignKey(
|
||||
blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='powerport',
|
||||
name='owner',
|
||||
field=models.ForeignKey(
|
||||
blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='rack',
|
||||
name='owner',
|
||||
field=models.ForeignKey(
|
||||
blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='rackreservation',
|
||||
name='owner',
|
||||
field=models.ForeignKey(
|
||||
blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='rackrole',
|
||||
name='owner',
|
||||
field=models.ForeignKey(
|
||||
blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='racktype',
|
||||
name='owner',
|
||||
field=models.ForeignKey(
|
||||
blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='rearport',
|
||||
name='owner',
|
||||
field=models.ForeignKey(
|
||||
blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='region',
|
||||
name='owner',
|
||||
field=models.ForeignKey(
|
||||
blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='site',
|
||||
name='owner',
|
||||
field=models.ForeignKey(
|
||||
blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='sitegroup',
|
||||
name='owner',
|
||||
field=models.ForeignKey(
|
||||
blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='virtualchassis',
|
||||
name='owner',
|
||||
field=models.ForeignKey(
|
||||
blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='virtualdevicecontext',
|
||||
name='owner',
|
||||
field=models.ForeignKey(
|
||||
blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='users.owner'
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -339,10 +339,6 @@ class PowerOutletTemplate(ModularComponentTemplateModel):
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
color = ColorField(
|
||||
verbose_name=_('color'),
|
||||
blank=True
|
||||
)
|
||||
power_port = models.ForeignKey(
|
||||
to='dcim.PowerPortTemplate',
|
||||
on_delete=models.SET_NULL,
|
||||
@@ -393,7 +389,6 @@ class PowerOutletTemplate(ModularComponentTemplateModel):
|
||||
name=self.resolve_name(kwargs.get('module')),
|
||||
label=self.resolve_label(kwargs.get('module')),
|
||||
type=self.type,
|
||||
color=self.color,
|
||||
power_port=power_port,
|
||||
feed_leg=self.feed_leg,
|
||||
**kwargs
|
||||
@@ -404,7 +399,6 @@ class PowerOutletTemplate(ModularComponentTemplateModel):
|
||||
return {
|
||||
'name': self.name,
|
||||
'type': self.type,
|
||||
'color': self.color,
|
||||
'power_port': self.power_port.name if self.power_port else None,
|
||||
'feed_leg': self.feed_leg,
|
||||
'label': self.label,
|
||||
|
||||
@@ -14,7 +14,6 @@ from dcim.fields import WWNField
|
||||
from dcim.models.mixins import InterfaceValidationMixin
|
||||
from netbox.choices import ColorChoices
|
||||
from netbox.models import OrganizationalModel, NetBoxModel
|
||||
from netbox.models.mixins import OwnerMixin
|
||||
from utilities.fields import ColorField, NaturalOrderingField
|
||||
from utilities.mptt import TreeManager
|
||||
from utilities.ordering import naturalize_interface
|
||||
@@ -41,7 +40,7 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class ComponentModel(OwnerMixin, NetBoxModel):
|
||||
class ComponentModel(NetBoxModel):
|
||||
"""
|
||||
An abstract model inherited by any model which has a parent Device.
|
||||
"""
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
import django_tables2 as tables
|
||||
from django_tables2.utils import Accessor
|
||||
from django.utils.html import escape
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django_tables2.utils import Accessor
|
||||
|
||||
from dcim.models import Cable
|
||||
from netbox.tables import PrimaryModelTable, columns
|
||||
from netbox.tables import NetBoxTable, columns
|
||||
from tenancy.tables import TenancyColumnsMixin
|
||||
from .template_code import CABLE_LENGTH
|
||||
|
||||
@@ -48,7 +48,7 @@ class CableTerminationsColumn(tables.Column):
|
||||
# Cables
|
||||
#
|
||||
|
||||
class CableTable(TenancyColumnsMixin, PrimaryModelTable):
|
||||
class CableTable(TenancyColumnsMixin, NetBoxTable):
|
||||
a_terminations = CableTerminationsColumn(
|
||||
cable_end='A',
|
||||
orderable=False,
|
||||
@@ -117,11 +117,12 @@ class CableTable(TenancyColumnsMixin, PrimaryModelTable):
|
||||
verbose_name=_('Color Name'),
|
||||
orderable=False
|
||||
)
|
||||
comments = columns.MarkdownColumn()
|
||||
tags = columns.TagColumn(
|
||||
url_name='dcim:cable_list'
|
||||
)
|
||||
|
||||
class Meta(PrimaryModelTable.Meta):
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = Cable
|
||||
fields = (
|
||||
'pk', 'id', 'label', 'a_terminations', 'b_terminations', 'device_a', 'device_b', 'rack_a', 'rack_b',
|
||||
|
||||
@@ -3,7 +3,7 @@ from django.utils.translation import gettext_lazy as _
|
||||
from django_tables2.utils import Accessor
|
||||
|
||||
from dcim import models
|
||||
from netbox.tables import NestedGroupModelTable, NetBoxTable, OrganizationalModelTable, PrimaryModelTable, columns
|
||||
from netbox.tables import NetBoxTable, columns
|
||||
from tenancy.tables import ContactsColumnMixin, TenancyColumnsMixin
|
||||
from .template_code import *
|
||||
|
||||
@@ -58,7 +58,15 @@ MACADDRESS_COPY_BUTTON = """
|
||||
# Device roles
|
||||
#
|
||||
|
||||
class DeviceRoleTable(NestedGroupModelTable):
|
||||
class DeviceRoleTable(NetBoxTable):
|
||||
name = columns.MPTTColumn(
|
||||
verbose_name=_('Name'),
|
||||
linkify=True
|
||||
)
|
||||
parent = tables.Column(
|
||||
verbose_name=_('Parent'),
|
||||
linkify=True,
|
||||
)
|
||||
device_count = columns.LinkedCountColumn(
|
||||
viewname='dcim:device_list',
|
||||
url_params={'role_id': 'pk'},
|
||||
@@ -81,7 +89,7 @@ class DeviceRoleTable(NestedGroupModelTable):
|
||||
url_name='dcim:devicerole_list'
|
||||
)
|
||||
|
||||
class Meta(NestedGroupModelTable.Meta):
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = models.DeviceRole
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'parent', 'device_count', 'vm_count', 'color', 'vm_role', 'config_template',
|
||||
@@ -94,7 +102,15 @@ class DeviceRoleTable(NestedGroupModelTable):
|
||||
# Platforms
|
||||
#
|
||||
|
||||
class PlatformTable(NestedGroupModelTable):
|
||||
class PlatformTable(NetBoxTable):
|
||||
name = columns.MPTTColumn(
|
||||
verbose_name=_('Name'),
|
||||
linkify=True
|
||||
)
|
||||
parent = tables.Column(
|
||||
verbose_name=_('Parent'),
|
||||
linkify=True,
|
||||
)
|
||||
manufacturer = tables.Column(
|
||||
verbose_name=_('Manufacturer'),
|
||||
linkify=True
|
||||
@@ -117,7 +133,7 @@ class PlatformTable(NestedGroupModelTable):
|
||||
url_name='dcim:platform_list'
|
||||
)
|
||||
|
||||
class Meta(NestedGroupModelTable.Meta):
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = models.Platform
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'parent', 'manufacturer', 'device_count', 'vm_count', 'slug', 'config_template',
|
||||
@@ -132,7 +148,7 @@ class PlatformTable(NestedGroupModelTable):
|
||||
# Devices
|
||||
#
|
||||
|
||||
class DeviceTable(TenancyColumnsMixin, ContactsColumnMixin, PrimaryModelTable):
|
||||
class DeviceTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
|
||||
name = tables.TemplateColumn(
|
||||
verbose_name=_('Name'),
|
||||
template_code=DEVICE_LINK,
|
||||
@@ -233,6 +249,7 @@ class DeviceTable(TenancyColumnsMixin, ContactsColumnMixin, PrimaryModelTable):
|
||||
accessor='parent_bay',
|
||||
linkify=True
|
||||
)
|
||||
comments = columns.MarkdownColumn()
|
||||
tags = columns.TagColumn(
|
||||
url_name='dcim:device_list'
|
||||
)
|
||||
@@ -267,7 +284,7 @@ class DeviceTable(TenancyColumnsMixin, ContactsColumnMixin, PrimaryModelTable):
|
||||
verbose_name=_('Inventory items')
|
||||
)
|
||||
|
||||
class Meta(PrimaryModelTable.Meta):
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = models.Device
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'status', 'tenant', 'tenant_group', 'role', 'manufacturer', 'device_type',
|
||||
@@ -1033,7 +1050,7 @@ class DeviceInventoryItemTable(InventoryItemTable):
|
||||
)
|
||||
|
||||
|
||||
class InventoryItemRoleTable(OrganizationalModelTable):
|
||||
class InventoryItemRoleTable(NetBoxTable):
|
||||
name = tables.Column(
|
||||
verbose_name=_('Name'),
|
||||
linkify=True
|
||||
@@ -1050,7 +1067,7 @@ class InventoryItemRoleTable(OrganizationalModelTable):
|
||||
url_name='dcim:inventoryitemrole_list'
|
||||
)
|
||||
|
||||
class Meta(OrganizationalModelTable.Meta):
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = models.InventoryItemRole
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'inventoryitem_count', 'color', 'description', 'slug', 'tags', 'actions',
|
||||
@@ -1062,7 +1079,7 @@ class InventoryItemRoleTable(OrganizationalModelTable):
|
||||
# Virtual chassis
|
||||
#
|
||||
|
||||
class VirtualChassisTable(PrimaryModelTable):
|
||||
class VirtualChassisTable(NetBoxTable):
|
||||
name = tables.Column(
|
||||
verbose_name=_('Name'),
|
||||
linkify=True
|
||||
@@ -1076,11 +1093,14 @@ class VirtualChassisTable(PrimaryModelTable):
|
||||
url_params={'virtual_chassis_id': 'pk'},
|
||||
verbose_name=_('Members')
|
||||
)
|
||||
comments = columns.MarkdownColumn(
|
||||
verbose_name=_('Comments'),
|
||||
)
|
||||
tags = columns.TagColumn(
|
||||
url_name='dcim:virtualchassis_list'
|
||||
)
|
||||
|
||||
class Meta(PrimaryModelTable.Meta):
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = models.VirtualChassis
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'domain', 'master', 'member_count', 'description', 'comments', 'tags', 'created',
|
||||
@@ -1089,7 +1109,7 @@ class VirtualChassisTable(PrimaryModelTable):
|
||||
default_columns = ('pk', 'name', 'domain', 'master', 'member_count')
|
||||
|
||||
|
||||
class VirtualDeviceContextTable(TenancyColumnsMixin, PrimaryModelTable):
|
||||
class VirtualDeviceContextTable(TenancyColumnsMixin, NetBoxTable):
|
||||
name = tables.Column(
|
||||
verbose_name=_('Name'),
|
||||
linkify=True
|
||||
@@ -1120,11 +1140,14 @@ class VirtualDeviceContextTable(TenancyColumnsMixin, PrimaryModelTable):
|
||||
url_params={'vdc_id': 'pk'},
|
||||
verbose_name=_('Interfaces')
|
||||
)
|
||||
|
||||
comments = columns.MarkdownColumn()
|
||||
|
||||
tags = columns.TagColumn(
|
||||
url_name='dcim:virtualdevicecontext_list'
|
||||
)
|
||||
|
||||
class Meta(PrimaryModelTable.Meta):
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = models.VirtualDeviceContext
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'status', 'identifier', 'tenant', 'tenant_group', 'primary_ip', 'primary_ip4',
|
||||
@@ -1135,7 +1158,7 @@ class VirtualDeviceContextTable(TenancyColumnsMixin, PrimaryModelTable):
|
||||
)
|
||||
|
||||
|
||||
class MACAddressTable(PrimaryModelTable):
|
||||
class MACAddressTable(NetBoxTable):
|
||||
mac_address = tables.TemplateColumn(
|
||||
template_code=MACADDRESS_LINK,
|
||||
verbose_name=_('MAC Address')
|
||||
@@ -1151,6 +1174,9 @@ class MACAddressTable(PrimaryModelTable):
|
||||
orderable=False,
|
||||
verbose_name=_('Parent')
|
||||
)
|
||||
is_primary = columns.BooleanColumn(
|
||||
verbose_name=_('Primary')
|
||||
)
|
||||
tags = columns.TagColumn(
|
||||
url_name='dcim:macaddress_list'
|
||||
)
|
||||
@@ -1158,10 +1184,10 @@ class MACAddressTable(PrimaryModelTable):
|
||||
extra_buttons=MACADDRESS_COPY_BUTTON
|
||||
)
|
||||
|
||||
class Meta(PrimaryModelTable.Meta):
|
||||
class Meta(DeviceComponentTable.Meta):
|
||||
model = models.MACAddress
|
||||
fields = (
|
||||
'pk', 'id', 'mac_address', 'assigned_object_parent', 'assigned_object', 'description', 'comments', 'tags',
|
||||
'created', 'last_updated',
|
||||
'pk', 'id', 'mac_address', 'assigned_object_parent', 'assigned_object', 'description', 'is_primary',
|
||||
'comments', 'tags', 'created', 'last_updated',
|
||||
)
|
||||
default_columns = ('pk', 'mac_address', 'assigned_object_parent', 'assigned_object', 'description')
|
||||
|
||||
@@ -2,7 +2,7 @@ import django_tables2 as tables
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from dcim import models
|
||||
from netbox.tables import NetBoxTable, OrganizationalModelTable, PrimaryModelTable, columns
|
||||
from netbox.tables import NetBoxTable, columns
|
||||
from tenancy.tables import ContactsColumnMixin
|
||||
from .template_code import MODULAR_COMPONENT_TEMPLATE_BUTTONS, WEIGHT
|
||||
|
||||
@@ -26,7 +26,7 @@ __all__ = (
|
||||
# Manufacturers
|
||||
#
|
||||
|
||||
class ManufacturerTable(ContactsColumnMixin, OrganizationalModelTable):
|
||||
class ManufacturerTable(ContactsColumnMixin, NetBoxTable):
|
||||
name = tables.Column(
|
||||
verbose_name=_('Name'),
|
||||
linkify=True
|
||||
@@ -60,7 +60,7 @@ class ManufacturerTable(ContactsColumnMixin, OrganizationalModelTable):
|
||||
url_name='dcim:manufacturer_list'
|
||||
)
|
||||
|
||||
class Meta(OrganizationalModelTable.Meta):
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = models.Manufacturer
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'racktype_count', 'devicetype_count', 'moduletype_count', 'inventoryitem_count',
|
||||
@@ -76,7 +76,7 @@ class ManufacturerTable(ContactsColumnMixin, OrganizationalModelTable):
|
||||
# Device types
|
||||
#
|
||||
|
||||
class DeviceTypeTable(PrimaryModelTable):
|
||||
class DeviceTypeTable(NetBoxTable):
|
||||
model = tables.Column(
|
||||
linkify=True,
|
||||
verbose_name=_('Device Type')
|
||||
@@ -93,6 +93,9 @@ class DeviceTypeTable(PrimaryModelTable):
|
||||
verbose_name=_('Full Depth'),
|
||||
false_mark=None
|
||||
)
|
||||
comments = columns.MarkdownColumn(
|
||||
verbose_name=_('Comments'),
|
||||
)
|
||||
tags = columns.TagColumn(
|
||||
url_name='dcim:devicetype_list'
|
||||
)
|
||||
@@ -145,7 +148,7 @@ class DeviceTypeTable(PrimaryModelTable):
|
||||
verbose_name=_('Inventory Items')
|
||||
)
|
||||
|
||||
class Meta(PrimaryModelTable.Meta):
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = models.DeviceType
|
||||
fields = (
|
||||
'pk', 'id', 'model', 'manufacturer', 'default_platform', 'slug', 'part_number', 'u_height',
|
||||
@@ -208,9 +211,6 @@ class PowerPortTemplateTable(ComponentTemplateTable):
|
||||
|
||||
|
||||
class PowerOutletTemplateTable(ComponentTemplateTable):
|
||||
color = columns.ColorColumn(
|
||||
verbose_name=_('Color'),
|
||||
)
|
||||
actions = columns.ActionsColumn(
|
||||
actions=('edit', 'delete'),
|
||||
extra_buttons=MODULAR_COMPONENT_TEMPLATE_BUTTONS
|
||||
@@ -218,7 +218,7 @@ class PowerOutletTemplateTable(ComponentTemplateTable):
|
||||
|
||||
class Meta(ComponentTemplateTable.Meta):
|
||||
model = models.PowerOutletTemplate
|
||||
fields = ('pk', 'name', 'label', 'type', 'color', 'power_port', 'feed_leg', 'description', 'actions')
|
||||
fields = ('pk', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description', 'actions')
|
||||
empty_text = "None"
|
||||
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import django_tables2 as tables
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
import django_tables2 as tables
|
||||
|
||||
from dcim.models import Module, ModuleType, ModuleTypeProfile
|
||||
from netbox.tables import PrimaryModelTable, columns
|
||||
from netbox.tables import NetBoxTable, columns
|
||||
from .template_code import MODULETYPEPROFILE_ATTRIBUTES, WEIGHT
|
||||
|
||||
__all__ = (
|
||||
@@ -12,7 +12,7 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class ModuleTypeProfileTable(PrimaryModelTable):
|
||||
class ModuleTypeProfileTable(NetBoxTable):
|
||||
name = tables.Column(
|
||||
verbose_name=_('Name'),
|
||||
linkify=True
|
||||
@@ -23,11 +23,14 @@ class ModuleTypeProfileTable(PrimaryModelTable):
|
||||
orderable=False,
|
||||
verbose_name=_('Attributes')
|
||||
)
|
||||
comments = columns.MarkdownColumn(
|
||||
verbose_name=_('Comments'),
|
||||
)
|
||||
tags = columns.TagColumn(
|
||||
url_name='dcim:moduletypeprofile_list'
|
||||
)
|
||||
|
||||
class Meta(PrimaryModelTable.Meta):
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = ModuleTypeProfile
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'description', 'comments', 'tags', 'created', 'last_updated',
|
||||
@@ -37,7 +40,7 @@ class ModuleTypeProfileTable(PrimaryModelTable):
|
||||
)
|
||||
|
||||
|
||||
class ModuleTypeTable(PrimaryModelTable):
|
||||
class ModuleTypeTable(NetBoxTable):
|
||||
profile = tables.Column(
|
||||
verbose_name=_('Profile'),
|
||||
linkify=True
|
||||
@@ -61,11 +64,14 @@ class ModuleTypeTable(PrimaryModelTable):
|
||||
url_params={'module_type_id': 'pk'},
|
||||
verbose_name=_('Instances')
|
||||
)
|
||||
comments = columns.MarkdownColumn(
|
||||
verbose_name=_('Comments'),
|
||||
)
|
||||
tags = columns.TagColumn(
|
||||
url_name='dcim:moduletype_list'
|
||||
)
|
||||
|
||||
class Meta(PrimaryModelTable.Meta):
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = ModuleType
|
||||
fields = (
|
||||
'pk', 'id', 'model', 'profile', 'manufacturer', 'part_number', 'airflow', 'weight', 'description',
|
||||
@@ -76,7 +82,7 @@ class ModuleTypeTable(PrimaryModelTable):
|
||||
)
|
||||
|
||||
|
||||
class ModuleTable(PrimaryModelTable):
|
||||
class ModuleTable(NetBoxTable):
|
||||
device = tables.Column(
|
||||
verbose_name=_('Device'),
|
||||
linkify=True
|
||||
@@ -97,11 +103,14 @@ class ModuleTable(PrimaryModelTable):
|
||||
status = columns.ChoiceFieldColumn(
|
||||
verbose_name=_('Status'),
|
||||
)
|
||||
comments = columns.MarkdownColumn(
|
||||
verbose_name=_('Comments'),
|
||||
)
|
||||
tags = columns.TagColumn(
|
||||
url_name='dcim:module_list'
|
||||
)
|
||||
|
||||
class Meta(PrimaryModelTable.Meta):
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = Module
|
||||
fields = (
|
||||
'pk', 'id', 'device', 'module_bay', 'manufacturer', 'module_type', 'status', 'serial', 'asset_tag',
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import django_tables2 as tables
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
import django_tables2 as tables
|
||||
from dcim.models import PowerFeed, PowerPanel
|
||||
from netbox.tables import PrimaryModelTable, columns
|
||||
from tenancy.tables import ContactsColumnMixin, TenancyColumnsMixin
|
||||
|
||||
from netbox.tables import NetBoxTable, columns
|
||||
|
||||
from .devices import CableTerminationTable
|
||||
|
||||
__all__ = (
|
||||
@@ -16,7 +17,7 @@ __all__ = (
|
||||
# Power panels
|
||||
#
|
||||
|
||||
class PowerPanelTable(ContactsColumnMixin, PrimaryModelTable):
|
||||
class PowerPanelTable(ContactsColumnMixin, NetBoxTable):
|
||||
name = tables.Column(
|
||||
verbose_name=_('Name'),
|
||||
linkify=True
|
||||
@@ -34,11 +35,14 @@ class PowerPanelTable(ContactsColumnMixin, PrimaryModelTable):
|
||||
url_params={'power_panel_id': 'pk'},
|
||||
verbose_name=_('Power Feeds')
|
||||
)
|
||||
comments = columns.MarkdownColumn(
|
||||
verbose_name=_('Comments'),
|
||||
)
|
||||
tags = columns.TagColumn(
|
||||
url_name='dcim:powerpanel_list'
|
||||
)
|
||||
|
||||
class Meta(PrimaryModelTable.Meta):
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = PowerPanel
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'site', 'location', 'powerfeed_count', 'contacts', 'description', 'comments', 'tags',
|
||||
@@ -53,7 +57,7 @@ class PowerPanelTable(ContactsColumnMixin, PrimaryModelTable):
|
||||
|
||||
# We're not using PathEndpointTable for PowerFeed because power connections
|
||||
# cannot traverse pass-through ports.
|
||||
class PowerFeedTable(TenancyColumnsMixin, CableTerminationTable, PrimaryModelTable):
|
||||
class PowerFeedTable(TenancyColumnsMixin, CableTerminationTable):
|
||||
name = tables.Column(
|
||||
verbose_name=_('Name'),
|
||||
linkify=True
|
||||
@@ -88,11 +92,14 @@ class PowerFeedTable(TenancyColumnsMixin, CableTerminationTable, PrimaryModelTab
|
||||
linkify=True,
|
||||
verbose_name=_('Site'),
|
||||
)
|
||||
comments = columns.MarkdownColumn(
|
||||
verbose_name=_('Comments'),
|
||||
)
|
||||
tags = columns.TagColumn(
|
||||
url_name='dcim:powerfeed_list'
|
||||
)
|
||||
|
||||
class Meta(CableTerminationTable.Meta, PrimaryModelTable.Meta):
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = PowerFeed
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'power_panel', 'site', 'rack', 'status', 'type', 'supply', 'voltage', 'amperage',
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import django_tables2 as tables
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
import django_tables2 as tables
|
||||
from django_tables2.utils import Accessor
|
||||
|
||||
from dcim.models import Rack, RackReservation, RackRole, RackType
|
||||
from netbox.tables import OrganizationalModelTable, PrimaryModelTable, columns
|
||||
from netbox.tables import NetBoxTable, columns
|
||||
from tenancy.tables import ContactsColumnMixin, TenancyColumnsMixin
|
||||
from .template_code import OUTER_UNIT, WEIGHT
|
||||
|
||||
@@ -15,7 +15,11 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class RackRoleTable(OrganizationalModelTable):
|
||||
#
|
||||
# Rack roles
|
||||
#
|
||||
|
||||
class RackRoleTable(NetBoxTable):
|
||||
name = tables.Column(
|
||||
verbose_name=_('Name'),
|
||||
linkify=True
|
||||
@@ -32,7 +36,7 @@ class RackRoleTable(OrganizationalModelTable):
|
||||
url_name='dcim:rackrole_list'
|
||||
)
|
||||
|
||||
class Meta(OrganizationalModelTable.Meta):
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = RackRole
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'rack_count', 'color', 'description', 'slug', 'tags', 'actions', 'created',
|
||||
@@ -41,7 +45,11 @@ class RackRoleTable(OrganizationalModelTable):
|
||||
default_columns = ('pk', 'name', 'rack_count', 'color', 'description')
|
||||
|
||||
|
||||
class RackTypeTable(PrimaryModelTable):
|
||||
#
|
||||
# Rack Types
|
||||
#
|
||||
|
||||
class RackTypeTable(NetBoxTable):
|
||||
model = tables.Column(
|
||||
verbose_name=_('Model'),
|
||||
linkify=True
|
||||
@@ -76,6 +84,9 @@ class RackTypeTable(PrimaryModelTable):
|
||||
template_code=WEIGHT,
|
||||
order_by=('_abs_max_weight', 'weight_unit')
|
||||
)
|
||||
comments = columns.MarkdownColumn(
|
||||
verbose_name=_('Comments'),
|
||||
)
|
||||
instance_count = columns.LinkedCountColumn(
|
||||
viewname='dcim:rack_list',
|
||||
url_params={'rack_type_id': 'pk'},
|
||||
@@ -85,7 +96,7 @@ class RackTypeTable(PrimaryModelTable):
|
||||
url_name='dcim:rack_list'
|
||||
)
|
||||
|
||||
class Meta(PrimaryModelTable.Meta):
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = RackType
|
||||
fields = (
|
||||
'pk', 'id', 'model', 'manufacturer', 'form_factor', 'u_height', 'starting_unit', 'width', 'outer_width',
|
||||
@@ -97,7 +108,11 @@ class RackTypeTable(PrimaryModelTable):
|
||||
)
|
||||
|
||||
|
||||
class RackTable(TenancyColumnsMixin, ContactsColumnMixin, PrimaryModelTable):
|
||||
#
|
||||
# Racks
|
||||
#
|
||||
|
||||
class RackTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
|
||||
name = tables.Column(
|
||||
verbose_name=_('Name'),
|
||||
linkify=True
|
||||
@@ -129,6 +144,9 @@ class RackTable(TenancyColumnsMixin, ContactsColumnMixin, PrimaryModelTable):
|
||||
template_code="{{ value }}U",
|
||||
verbose_name=_('Height')
|
||||
)
|
||||
comments = columns.MarkdownColumn(
|
||||
verbose_name=_('Comments'),
|
||||
)
|
||||
device_count = columns.LinkedCountColumn(
|
||||
viewname='dcim:device_list',
|
||||
url_params={'rack_id': 'pk'},
|
||||
@@ -168,7 +186,7 @@ class RackTable(TenancyColumnsMixin, ContactsColumnMixin, PrimaryModelTable):
|
||||
order_by=('_abs_max_weight', 'weight_unit')
|
||||
)
|
||||
|
||||
class Meta(PrimaryModelTable.Meta):
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = Rack
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'site', 'location', 'status', 'facility_id', 'tenant', 'tenant_group', 'role',
|
||||
@@ -183,7 +201,11 @@ class RackTable(TenancyColumnsMixin, ContactsColumnMixin, PrimaryModelTable):
|
||||
)
|
||||
|
||||
|
||||
class RackReservationTable(TenancyColumnsMixin, PrimaryModelTable):
|
||||
#
|
||||
# Rack reservations
|
||||
#
|
||||
|
||||
class RackReservationTable(TenancyColumnsMixin, NetBoxTable):
|
||||
reservation = tables.Column(
|
||||
verbose_name=_('Reservation'),
|
||||
accessor='pk',
|
||||
@@ -210,11 +232,14 @@ class RackReservationTable(TenancyColumnsMixin, PrimaryModelTable):
|
||||
status = columns.ChoiceFieldColumn(
|
||||
verbose_name=_('Status'),
|
||||
)
|
||||
comments = columns.MarkdownColumn(
|
||||
verbose_name=_('Comments'),
|
||||
)
|
||||
tags = columns.TagColumn(
|
||||
url_name='dcim:rackreservation_list'
|
||||
)
|
||||
|
||||
class Meta(PrimaryModelTable.Meta):
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = RackReservation
|
||||
fields = (
|
||||
'pk', 'id', 'reservation', 'site', 'location', 'rack', 'unit_list', 'status', 'user', 'created', 'tenant',
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import django_tables2 as tables
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
import django_tables2 as tables
|
||||
from dcim.models import Location, Region, Site, SiteGroup
|
||||
from netbox.tables import NestedGroupModelTable, PrimaryModelTable, columns
|
||||
from tenancy.tables import ContactsColumnMixin, TenancyColumnsMixin
|
||||
|
||||
from netbox.tables import NetBoxTable, columns
|
||||
|
||||
from .template_code import LOCATION_BUTTONS
|
||||
|
||||
__all__ = (
|
||||
@@ -14,7 +15,19 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class RegionTable(ContactsColumnMixin, NestedGroupModelTable):
|
||||
#
|
||||
# Regions
|
||||
#
|
||||
|
||||
class RegionTable(ContactsColumnMixin, NetBoxTable):
|
||||
name = columns.MPTTColumn(
|
||||
verbose_name=_('Name'),
|
||||
linkify=True
|
||||
)
|
||||
parent = tables.Column(
|
||||
verbose_name=_('Parent'),
|
||||
linkify=True,
|
||||
)
|
||||
site_count = columns.LinkedCountColumn(
|
||||
viewname='dcim:site_list',
|
||||
url_params={'region_id': 'pk'},
|
||||
@@ -23,8 +36,11 @@ class RegionTable(ContactsColumnMixin, NestedGroupModelTable):
|
||||
tags = columns.TagColumn(
|
||||
url_name='dcim:region_list'
|
||||
)
|
||||
comments = columns.MarkdownColumn(
|
||||
verbose_name=_('Comments'),
|
||||
)
|
||||
|
||||
class Meta(NestedGroupModelTable.Meta):
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = Region
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'parent', 'slug', 'site_count', 'description', 'comments', 'contacts', 'tags',
|
||||
@@ -33,7 +49,19 @@ class RegionTable(ContactsColumnMixin, NestedGroupModelTable):
|
||||
default_columns = ('pk', 'name', 'site_count', 'description')
|
||||
|
||||
|
||||
class SiteGroupTable(ContactsColumnMixin, NestedGroupModelTable):
|
||||
#
|
||||
# Site groups
|
||||
#
|
||||
|
||||
class SiteGroupTable(ContactsColumnMixin, NetBoxTable):
|
||||
name = columns.MPTTColumn(
|
||||
verbose_name=_('Name'),
|
||||
linkify=True
|
||||
)
|
||||
parent = tables.Column(
|
||||
verbose_name=_('Parent'),
|
||||
linkify=True,
|
||||
)
|
||||
site_count = columns.LinkedCountColumn(
|
||||
viewname='dcim:site_list',
|
||||
url_params={'group_id': 'pk'},
|
||||
@@ -42,8 +70,11 @@ class SiteGroupTable(ContactsColumnMixin, NestedGroupModelTable):
|
||||
tags = columns.TagColumn(
|
||||
url_name='dcim:sitegroup_list'
|
||||
)
|
||||
comments = columns.MarkdownColumn(
|
||||
verbose_name=_('Comments'),
|
||||
)
|
||||
|
||||
class Meta(NestedGroupModelTable.Meta):
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = SiteGroup
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'parent', 'slug', 'site_count', 'description', 'comments', 'contacts', 'tags',
|
||||
@@ -52,7 +83,11 @@ class SiteGroupTable(ContactsColumnMixin, NestedGroupModelTable):
|
||||
default_columns = ('pk', 'name', 'site_count', 'description')
|
||||
|
||||
|
||||
class SiteTable(TenancyColumnsMixin, ContactsColumnMixin, PrimaryModelTable):
|
||||
#
|
||||
# Sites
|
||||
#
|
||||
|
||||
class SiteTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
|
||||
name = tables.Column(
|
||||
verbose_name=_('Name'),
|
||||
linkify=True
|
||||
@@ -82,11 +117,14 @@ class SiteTable(TenancyColumnsMixin, ContactsColumnMixin, PrimaryModelTable):
|
||||
url_params={'site_id': 'pk'},
|
||||
verbose_name=_('Devices')
|
||||
)
|
||||
comments = columns.MarkdownColumn(
|
||||
verbose_name=_('Comments'),
|
||||
)
|
||||
tags = columns.TagColumn(
|
||||
url_name='dcim:site_list'
|
||||
)
|
||||
|
||||
class Meta(PrimaryModelTable.Meta):
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = Site
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'slug', 'status', 'facility', 'region', 'group', 'tenant', 'tenant_group', 'asns',
|
||||
@@ -96,7 +134,19 @@ class SiteTable(TenancyColumnsMixin, ContactsColumnMixin, PrimaryModelTable):
|
||||
default_columns = ('pk', 'name', 'status', 'facility', 'region', 'group', 'tenant', 'description')
|
||||
|
||||
|
||||
class LocationTable(TenancyColumnsMixin, ContactsColumnMixin, NestedGroupModelTable):
|
||||
#
|
||||
# Locations
|
||||
#
|
||||
|
||||
class LocationTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
|
||||
name = columns.MPTTColumn(
|
||||
verbose_name=_('Name'),
|
||||
linkify=True
|
||||
)
|
||||
parent = tables.Column(
|
||||
verbose_name=_('Parent'),
|
||||
linkify=True,
|
||||
)
|
||||
site = tables.Column(
|
||||
verbose_name=_('Site'),
|
||||
linkify=True
|
||||
@@ -125,8 +175,11 @@ class LocationTable(TenancyColumnsMixin, ContactsColumnMixin, NestedGroupModelTa
|
||||
actions = columns.ActionsColumn(
|
||||
extra_buttons=LOCATION_BUTTONS
|
||||
)
|
||||
comments = columns.MarkdownColumn(
|
||||
verbose_name=_('Comments'),
|
||||
)
|
||||
|
||||
class Meta(NestedGroupModelTable.Meta):
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = Location
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'parent', 'site', 'status', 'facility', 'tenant', 'tenant_group', 'rack_count',
|
||||
|
||||
@@ -13,8 +13,7 @@ from ipam.choices import VLANQinQRoleChoices
|
||||
from ipam.models import ASN, RIR, VLAN, VRF
|
||||
from netbox.api.serializers import GenericObjectSerializer
|
||||
from tenancy.models import Tenant
|
||||
from users.constants import TOKEN_PREFIX
|
||||
from users.models import Token, User
|
||||
from users.models import User
|
||||
from utilities.testing import APITestCase, APIViewTestCases, create_test_device, disable_logging
|
||||
from virtualization.models import Cluster, ClusterType
|
||||
from wireless.choices import WirelessChannelChoices
|
||||
@@ -1307,6 +1306,7 @@ class DeviceTest(APIViewTestCases.APIViewTestCase):
|
||||
}
|
||||
user_permissions = (
|
||||
'dcim.view_site', 'dcim.view_rack', 'dcim.view_location', 'dcim.view_devicerole', 'dcim.view_devicetype',
|
||||
'extras.view_configtemplate',
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@@ -1486,58 +1486,12 @@ class DeviceTest(APIViewTestCases.APIViewTestCase):
|
||||
device.config_template = configtemplate
|
||||
device.save()
|
||||
|
||||
self.add_permissions('dcim.render_config_device', 'dcim.view_device')
|
||||
url = reverse('dcim-api:device-render-config', kwargs={'pk': device.pk})
|
||||
self.add_permissions('dcim.add_device')
|
||||
url = reverse('dcim-api:device-detail', kwargs={'pk': device.pk}) + 'render-config/'
|
||||
response = self.client.post(url, {}, format='json', **self.header)
|
||||
self.assertHttpStatus(response, status.HTTP_200_OK)
|
||||
self.assertEqual(response.data['content'], f'Config for device {device.name}')
|
||||
|
||||
def test_render_config_without_permission(self):
|
||||
configtemplate = ConfigTemplate.objects.create(
|
||||
name='Config Template 1',
|
||||
template_code='Config for device {{ device.name }}'
|
||||
)
|
||||
|
||||
device = Device.objects.first()
|
||||
device.config_template = configtemplate
|
||||
device.save()
|
||||
|
||||
# No permissions added - user has no render_config permission
|
||||
url = reverse('dcim-api:device-render-config', kwargs={'pk': device.pk})
|
||||
response = self.client.post(url, {}, format='json', **self.header)
|
||||
self.assertHttpStatus(response, status.HTTP_404_NOT_FOUND)
|
||||
|
||||
def test_render_config_token_write_enabled(self):
|
||||
configtemplate = ConfigTemplate.objects.create(
|
||||
name='Config Template 1',
|
||||
template_code='Config for device {{ device.name }}'
|
||||
)
|
||||
|
||||
device = Device.objects.first()
|
||||
device.config_template = configtemplate
|
||||
device.save()
|
||||
|
||||
self.add_permissions('dcim.render_config_device', 'dcim.view_device')
|
||||
url = reverse('dcim-api:device-render-config', kwargs={'pk': device.pk})
|
||||
|
||||
# Request without token auth should fail with PermissionDenied
|
||||
response = self.client.post(url, {}, format='json')
|
||||
self.assertHttpStatus(response, status.HTTP_403_FORBIDDEN)
|
||||
|
||||
# Create token with write_enabled=False
|
||||
token = Token.objects.create(version=2, user=self.user, write_enabled=False)
|
||||
token_header = f'Bearer {TOKEN_PREFIX}{token.key}.{token.token}'
|
||||
|
||||
# Request with write-disabled token should fail
|
||||
response = self.client.post(url, {}, format='json', HTTP_AUTHORIZATION=token_header)
|
||||
self.assertHttpStatus(response, status.HTTP_403_FORBIDDEN)
|
||||
|
||||
# Enable write and retry
|
||||
token.write_enabled = True
|
||||
token.save()
|
||||
response = self.client.post(url, {}, format='json', HTTP_AUTHORIZATION=token_header)
|
||||
self.assertHttpStatus(response, status.HTTP_200_OK)
|
||||
|
||||
|
||||
class ModuleTest(APIViewTestCases.APIViewTestCase):
|
||||
model = Module
|
||||
@@ -2422,33 +2376,6 @@ class CableTest(APIViewTestCases.APIViewTestCase):
|
||||
]
|
||||
|
||||
|
||||
class CableTerminationTest(
|
||||
APIViewTestCases.GetObjectViewTestCase,
|
||||
APIViewTestCases.ListObjectsViewTestCase,
|
||||
):
|
||||
model = CableTermination
|
||||
brief_fields = ['cable', 'cable_end', 'display', 'id', 'termination_id', 'termination_type', 'url']
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
device1 = create_test_device('Device 1')
|
||||
device2 = create_test_device('Device 2')
|
||||
|
||||
interfaces = []
|
||||
for device in (device1, device2):
|
||||
for i in range(0, 10):
|
||||
interfaces.append(Interface(device=device, type=InterfaceTypeChoices.TYPE_1GE_FIXED, name=f'eth{i}'))
|
||||
Interface.objects.bulk_create(interfaces)
|
||||
|
||||
cables = (
|
||||
Cable(a_terminations=[interfaces[0]], b_terminations=[interfaces[10]], label='Cable 1'),
|
||||
Cable(a_terminations=[interfaces[1]], b_terminations=[interfaces[11]], label='Cable 2'),
|
||||
Cable(a_terminations=[interfaces[2]], b_terminations=[interfaces[12]], label='Cable 3'),
|
||||
)
|
||||
for cable in cables:
|
||||
cable.save()
|
||||
|
||||
|
||||
class ConnectedDeviceTest(APITestCase):
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -10,7 +10,7 @@ from netbox.choices import ColorChoices, WeightUnitChoices
|
||||
from tenancy.models import Tenant, TenantGroup
|
||||
from users.models import User
|
||||
from utilities.testing import ChangeLoggedFilterSetTests, create_test_device, create_test_virtualmachine
|
||||
from virtualization.models import Cluster, ClusterType, ClusterGroup, VMInterface, VirtualMachine
|
||||
from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
|
||||
from wireless.choices import WirelessChannelChoices, WirelessRoleChoices
|
||||
from wireless.models import WirelessLink
|
||||
|
||||
@@ -1919,21 +1919,18 @@ class PowerOutletTemplateTestCase(TestCase, DeviceComponentTemplateFilterSetTest
|
||||
device_type=device_types[0],
|
||||
name='Power Outlet 1',
|
||||
feed_leg=PowerOutletFeedLegChoices.FEED_LEG_A,
|
||||
color=ColorChoices.COLOR_RED,
|
||||
description='foobar1'
|
||||
),
|
||||
PowerOutletTemplate(
|
||||
device_type=device_types[1],
|
||||
name='Power Outlet 2',
|
||||
feed_leg=PowerOutletFeedLegChoices.FEED_LEG_B,
|
||||
color=ColorChoices.COLOR_GREEN,
|
||||
description='foobar2'
|
||||
),
|
||||
PowerOutletTemplate(
|
||||
device_type=device_types[2],
|
||||
name='Power Outlet 3',
|
||||
feed_leg=PowerOutletFeedLegChoices.FEED_LEG_C,
|
||||
color=ColorChoices.COLOR_BLUE,
|
||||
description='foobar3'
|
||||
),
|
||||
))
|
||||
@@ -1946,10 +1943,6 @@ class PowerOutletTemplateTestCase(TestCase, DeviceComponentTemplateFilterSetTest
|
||||
params = {'feed_leg': [PowerOutletFeedLegChoices.FEED_LEG_A, PowerOutletFeedLegChoices.FEED_LEG_B]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_color(self):
|
||||
params = {'color': [ColorChoices.COLOR_RED, ColorChoices.COLOR_GREEN]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
|
||||
class InterfaceTemplateTestCase(TestCase, DeviceComponentTemplateFilterSetTests, ChangeLoggedFilterSetTests):
|
||||
queryset = InterfaceTemplate.objects.all()
|
||||
@@ -7171,9 +7164,20 @@ class MACAddressTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
MACAddress(mac_address='00-00-00-05-01-01', assigned_object=vm_interfaces[1]),
|
||||
MACAddress(mac_address='00-00-00-06-01-01', assigned_object=vm_interfaces[2]),
|
||||
MACAddress(mac_address='00-00-00-06-01-02', assigned_object=vm_interfaces[2]),
|
||||
# unassigned
|
||||
MACAddress(mac_address='00-00-00-07-01-01'),
|
||||
)
|
||||
MACAddress.objects.bulk_create(mac_addresses)
|
||||
|
||||
# Set MAC addresses as primary
|
||||
for idx, interface in enumerate(interfaces):
|
||||
interface.primary_mac_address = mac_addresses[idx]
|
||||
interface.save()
|
||||
for idx, vm_interface in enumerate(vm_interfaces):
|
||||
# Offset by 4 for device MACs
|
||||
vm_interface.primary_mac_address = mac_addresses[idx + 4]
|
||||
vm_interface.save()
|
||||
|
||||
def test_mac_address(self):
|
||||
params = {'mac_address': ['00-00-00-01-01-01', '00-00-00-02-01-01']}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
@@ -7205,3 +7209,15 @@ class MACAddressTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
params = {'vminterface': [vm_interfaces[0].name, vm_interfaces[1].name]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_assigned(self):
|
||||
params = {'assigned': True}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 8)
|
||||
params = {'assigned': False}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||
|
||||
def test_primary(self):
|
||||
params = {'primary': True}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
|
||||
params = {'primary': False}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
||||
|
||||
@@ -4,7 +4,6 @@ from rest_framework.renderers import JSONRenderer
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.status import HTTP_400_BAD_REQUEST
|
||||
|
||||
from netbox.api.authentication import TokenWritePermission
|
||||
from netbox.api.renderers import TextRenderer
|
||||
from .serializers import ConfigTemplateSerializer
|
||||
|
||||
@@ -65,24 +64,12 @@ class RenderConfigMixin(ConfigTemplateRenderMixin):
|
||||
"""
|
||||
Provides a /render-config/ endpoint for REST API views whose model may have a ConfigTemplate assigned.
|
||||
"""
|
||||
|
||||
def get_permissions(self):
|
||||
# For render_config action, check only token write ability (not model permissions)
|
||||
if self.action == 'render_config':
|
||||
return [TokenWritePermission()]
|
||||
return super().get_permissions()
|
||||
|
||||
@action(detail=True, methods=['post'], url_path='render-config', renderer_classes=[JSONRenderer, TextRenderer])
|
||||
def render_config(self, request, pk):
|
||||
"""
|
||||
Resolve and render the preferred ConfigTemplate for this Device.
|
||||
"""
|
||||
# Override restrict() on the default queryset to enforce the render_config & view actions
|
||||
self.queryset = self.queryset.model.objects.restrict(request.user, 'render_config').restrict(
|
||||
request.user, 'view'
|
||||
)
|
||||
instance = self.get_object()
|
||||
|
||||
object_type = instance._meta.model_name
|
||||
configtemplate = instance.get_config_template()
|
||||
if not configtemplate:
|
||||
|
||||
@@ -8,8 +8,7 @@ from dcim.api.serializers_.sites import LocationSerializer, RegionSerializer, Si
|
||||
from dcim.models import DeviceRole, DeviceType, Location, Platform, Region, Site, SiteGroup
|
||||
from extras.models import ConfigContext, ConfigContextProfile, Tag
|
||||
from netbox.api.fields import SerializedPKRelatedField
|
||||
from netbox.api.serializers import ChangeLogMessageSerializer, PrimaryModelSerializer, ValidatedModelSerializer
|
||||
from users.api.serializers_.mixins import OwnerMixin
|
||||
from netbox.api.serializers import ChangeLogMessageSerializer, ValidatedModelSerializer
|
||||
from tenancy.api.serializers_.tenants import TenantSerializer, TenantGroupSerializer
|
||||
from tenancy.models import Tenant, TenantGroup
|
||||
from virtualization.api.serializers_.clusters import ClusterSerializer, ClusterGroupSerializer, ClusterTypeSerializer
|
||||
@@ -21,7 +20,13 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class ConfigContextProfileSerializer(PrimaryModelSerializer):
|
||||
class ConfigContextProfileSerializer(ChangeLogMessageSerializer, ValidatedModelSerializer):
|
||||
tags = serializers.SlugRelatedField(
|
||||
queryset=Tag.objects.all(),
|
||||
slug_field='slug',
|
||||
required=False,
|
||||
many=True
|
||||
)
|
||||
data_source = DataSourceSerializer(
|
||||
nested=True,
|
||||
required=False
|
||||
@@ -34,13 +39,13 @@ class ConfigContextProfileSerializer(PrimaryModelSerializer):
|
||||
class Meta:
|
||||
model = ConfigContextProfile
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'name', 'description', 'schema', 'tags', 'owner', 'comments',
|
||||
'data_source', 'data_path', 'data_file', 'data_synced', 'created', 'last_updated',
|
||||
'id', 'url', 'display_url', 'display', 'name', 'description', 'schema', 'tags', 'comments', 'data_source',
|
||||
'data_path', 'data_file', 'data_synced', 'created', 'last_updated',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'name', 'description')
|
||||
|
||||
|
||||
class ConfigContextSerializer(OwnerMixin, ChangeLogMessageSerializer, ValidatedModelSerializer):
|
||||
class ConfigContextSerializer(ChangeLogMessageSerializer, ValidatedModelSerializer):
|
||||
profile = ConfigContextProfileSerializer(
|
||||
nested=True,
|
||||
required=False,
|
||||
@@ -151,7 +156,7 @@ class ConfigContextSerializer(OwnerMixin, ChangeLogMessageSerializer, ValidatedM
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'name', 'weight', 'profile', 'description', 'is_active', 'regions',
|
||||
'site_groups', 'sites', 'locations', 'device_types', 'roles', 'platforms', 'cluster_types',
|
||||
'cluster_groups', 'clusters', 'tenant_groups', 'tenants', 'owner', 'tags', 'data_source', 'data_path',
|
||||
'data_file', 'data_synced', 'data', 'created', 'last_updated',
|
||||
'cluster_groups', 'clusters', 'tenant_groups', 'tenants', 'tags', 'data_source', 'data_path', 'data_file',
|
||||
'data_synced', 'data', 'created', 'last_updated',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'name', 'description')
|
||||
|
||||
@@ -2,19 +2,13 @@ from core.api.serializers_.data import DataFileSerializer, DataSourceSerializer
|
||||
from extras.models import ConfigTemplate
|
||||
from netbox.api.serializers import ChangeLogMessageSerializer, ValidatedModelSerializer
|
||||
from netbox.api.serializers.features import TaggableModelSerializer
|
||||
from users.api.serializers_.mixins import OwnerMixin
|
||||
|
||||
__all__ = (
|
||||
'ConfigTemplateSerializer',
|
||||
)
|
||||
|
||||
|
||||
class ConfigTemplateSerializer(
|
||||
OwnerMixin,
|
||||
ChangeLogMessageSerializer,
|
||||
TaggableModelSerializer,
|
||||
ValidatedModelSerializer
|
||||
):
|
||||
class ConfigTemplateSerializer(ChangeLogMessageSerializer, TaggableModelSerializer, ValidatedModelSerializer):
|
||||
data_source = DataSourceSerializer(
|
||||
nested=True,
|
||||
required=False
|
||||
@@ -29,6 +23,6 @@ class ConfigTemplateSerializer(
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'name', 'description', 'environment_params', 'template_code',
|
||||
'mime_type', 'file_name', 'file_extension', 'as_attachment', 'data_source', 'data_path', 'data_file',
|
||||
'data_synced', 'owner', 'tags', 'created', 'last_updated',
|
||||
'data_synced', 'tags', 'created', 'last_updated',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'name', 'description')
|
||||
|
||||
@@ -8,7 +8,6 @@ from extras.choices import *
|
||||
from extras.models import CustomField, CustomFieldChoiceSet
|
||||
from netbox.api.fields import ChoiceField, ContentTypeField
|
||||
from netbox.api.serializers import ChangeLogMessageSerializer, ValidatedModelSerializer
|
||||
from users.api.serializers_.mixins import OwnerMixin
|
||||
|
||||
__all__ = (
|
||||
'CustomFieldChoiceSetSerializer',
|
||||
@@ -16,7 +15,7 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class CustomFieldChoiceSetSerializer(OwnerMixin, ChangeLogMessageSerializer, ValidatedModelSerializer):
|
||||
class CustomFieldChoiceSetSerializer(ChangeLogMessageSerializer, ValidatedModelSerializer):
|
||||
base_choices = ChoiceField(
|
||||
choices=CustomFieldChoiceSetBaseChoices,
|
||||
required=False
|
||||
@@ -33,12 +32,12 @@ class CustomFieldChoiceSetSerializer(OwnerMixin, ChangeLogMessageSerializer, Val
|
||||
model = CustomFieldChoiceSet
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'name', 'description', 'base_choices', 'extra_choices',
|
||||
'order_alphabetically', 'choices_count', 'owner', 'created', 'last_updated',
|
||||
'order_alphabetically', 'choices_count', 'created', 'last_updated',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'name', 'description', 'choices_count')
|
||||
|
||||
|
||||
class CustomFieldSerializer(OwnerMixin, ChangeLogMessageSerializer, ValidatedModelSerializer):
|
||||
class CustomFieldSerializer(ChangeLogMessageSerializer, ValidatedModelSerializer):
|
||||
object_types = ContentTypeField(
|
||||
queryset=ObjectType.objects.with_feature('custom_fields'),
|
||||
many=True
|
||||
@@ -65,8 +64,8 @@ class CustomFieldSerializer(OwnerMixin, ChangeLogMessageSerializer, ValidatedMod
|
||||
'id', 'url', 'display_url', 'display', 'object_types', 'type', 'related_object_type', 'data_type',
|
||||
'name', 'label', 'group_name', 'description', 'required', 'unique', 'search_weight', 'filter_logic',
|
||||
'ui_visible', 'ui_editable', 'is_cloneable', 'default', 'related_object_filter', 'weight',
|
||||
'validation_minimum', 'validation_maximum', 'validation_regex', 'choice_set', 'owner', 'comments',
|
||||
'created', 'last_updated',
|
||||
'validation_minimum', 'validation_maximum', 'validation_regex', 'choice_set', 'comments', 'created',
|
||||
'last_updated',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'name', 'description')
|
||||
|
||||
|
||||
@@ -2,14 +2,13 @@ from core.models import ObjectType
|
||||
from extras.models import CustomLink
|
||||
from netbox.api.fields import ContentTypeField
|
||||
from netbox.api.serializers import ChangeLogMessageSerializer, ValidatedModelSerializer
|
||||
from users.api.serializers_.mixins import OwnerMixin
|
||||
|
||||
__all__ = (
|
||||
'CustomLinkSerializer',
|
||||
)
|
||||
|
||||
|
||||
class CustomLinkSerializer(OwnerMixin, ChangeLogMessageSerializer, ValidatedModelSerializer):
|
||||
class CustomLinkSerializer(ChangeLogMessageSerializer, ValidatedModelSerializer):
|
||||
object_types = ContentTypeField(
|
||||
queryset=ObjectType.objects.with_feature('custom_links'),
|
||||
many=True
|
||||
@@ -19,6 +18,6 @@ class CustomLinkSerializer(OwnerMixin, ChangeLogMessageSerializer, ValidatedMode
|
||||
model = CustomLink
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'object_types', 'name', 'enabled', 'link_text', 'link_url',
|
||||
'weight', 'group_name', 'button_class', 'new_window', 'owner', 'created', 'last_updated',
|
||||
'weight', 'group_name', 'button_class', 'new_window', 'created', 'last_updated',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'name')
|
||||
|
||||
@@ -7,7 +7,6 @@ from extras.choices import *
|
||||
from extras.models import EventRule, Webhook
|
||||
from netbox.api.fields import ChoiceField, ContentTypeField
|
||||
from netbox.api.serializers import NetBoxModelSerializer
|
||||
from users.api.serializers_.mixins import OwnerMixin
|
||||
from utilities.api import get_serializer_for_model
|
||||
from .scripts import ScriptSerializer
|
||||
|
||||
@@ -21,7 +20,7 @@ __all__ = (
|
||||
# Event Rules
|
||||
#
|
||||
|
||||
class EventRuleSerializer(OwnerMixin, NetBoxModelSerializer):
|
||||
class EventRuleSerializer(NetBoxModelSerializer):
|
||||
object_types = ContentTypeField(
|
||||
queryset=ObjectType.objects.with_feature('event_rules'),
|
||||
many=True
|
||||
@@ -37,7 +36,7 @@ class EventRuleSerializer(OwnerMixin, NetBoxModelSerializer):
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'object_types', 'name', 'enabled', 'event_types', 'conditions',
|
||||
'action_type', 'action_object_type', 'action_object_id', 'action_object', 'description', 'custom_fields',
|
||||
'owner', 'tags', 'created', 'last_updated',
|
||||
'tags', 'created', 'last_updated',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'name', 'description')
|
||||
|
||||
@@ -57,13 +56,13 @@ class EventRuleSerializer(OwnerMixin, NetBoxModelSerializer):
|
||||
# Webhooks
|
||||
#
|
||||
|
||||
class WebhookSerializer(OwnerMixin, NetBoxModelSerializer):
|
||||
class WebhookSerializer(NetBoxModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = Webhook
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'name', 'description', 'payload_url', 'http_method',
|
||||
'http_content_type', 'additional_headers', 'body_template', 'secret', 'ssl_verification', 'ca_file_path',
|
||||
'custom_fields', 'owner', 'tags', 'created', 'last_updated',
|
||||
'custom_fields', 'tags', 'created', 'last_updated',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'name', 'description')
|
||||
|
||||
@@ -3,14 +3,13 @@ from core.models import ObjectType
|
||||
from extras.models import ExportTemplate
|
||||
from netbox.api.fields import ContentTypeField
|
||||
from netbox.api.serializers import ChangeLogMessageSerializer, ValidatedModelSerializer
|
||||
from users.api.serializers_.mixins import OwnerMixin
|
||||
|
||||
__all__ = (
|
||||
'ExportTemplateSerializer',
|
||||
)
|
||||
|
||||
|
||||
class ExportTemplateSerializer(OwnerMixin, ChangeLogMessageSerializer, ValidatedModelSerializer):
|
||||
class ExportTemplateSerializer(ChangeLogMessageSerializer, ValidatedModelSerializer):
|
||||
object_types = ContentTypeField(
|
||||
queryset=ObjectType.objects.with_feature('export_templates'),
|
||||
many=True
|
||||
@@ -29,6 +28,6 @@ class ExportTemplateSerializer(OwnerMixin, ChangeLogMessageSerializer, Validated
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'object_types', 'name', 'description', 'environment_params',
|
||||
'template_code', 'mime_type', 'file_name', 'file_extension', 'as_attachment', 'data_source',
|
||||
'data_path', 'data_file', 'data_synced', 'owner', 'created', 'last_updated',
|
||||
'data_path', 'data_file', 'data_synced', 'created', 'last_updated',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'name', 'description')
|
||||
|
||||
@@ -2,14 +2,13 @@ from core.models import ObjectType
|
||||
from extras.models import SavedFilter
|
||||
from netbox.api.fields import ContentTypeField
|
||||
from netbox.api.serializers import ChangeLogMessageSerializer, ValidatedModelSerializer
|
||||
from users.api.serializers_.mixins import OwnerMixin
|
||||
|
||||
__all__ = (
|
||||
'SavedFilterSerializer',
|
||||
)
|
||||
|
||||
|
||||
class SavedFilterSerializer(OwnerMixin, ChangeLogMessageSerializer, ValidatedModelSerializer):
|
||||
class SavedFilterSerializer(ChangeLogMessageSerializer, ValidatedModelSerializer):
|
||||
object_types = ContentTypeField(
|
||||
queryset=ObjectType.objects.all(),
|
||||
many=True
|
||||
@@ -19,6 +18,6 @@ class SavedFilterSerializer(OwnerMixin, ChangeLogMessageSerializer, ValidatedMod
|
||||
model = SavedFilter
|
||||
fields = [
|
||||
'id', 'url', 'display_url', 'display', 'object_types', 'name', 'slug', 'description', 'user', 'weight',
|
||||
'enabled', 'shared', 'parameters', 'owner', 'created', 'last_updated',
|
||||
'enabled', 'shared', 'parameters', 'created', 'last_updated',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'name', 'slug', 'description')
|
||||
|
||||
@@ -5,6 +5,7 @@ from rest_framework import serializers
|
||||
from core.api.serializers_.jobs import JobSerializer
|
||||
from extras.models import Script
|
||||
from netbox.api.serializers import ValidatedModelSerializer
|
||||
from utilities.datetime import local_now
|
||||
|
||||
__all__ = (
|
||||
'ScriptDetailSerializer',
|
||||
@@ -66,11 +67,31 @@ class ScriptInputSerializer(serializers.Serializer):
|
||||
interval = serializers.IntegerField(required=False, allow_null=True)
|
||||
|
||||
def validate_schedule_at(self, value):
|
||||
if value and not self.context['script'].python_class.scheduling_enabled:
|
||||
raise serializers.ValidationError(_("Scheduling is not enabled for this script."))
|
||||
"""
|
||||
Validates the specified schedule time for a script execution.
|
||||
"""
|
||||
if value:
|
||||
if not self.context['script'].python_class.scheduling_enabled:
|
||||
raise serializers.ValidationError(_('Scheduling is not enabled for this script.'))
|
||||
if value < local_now():
|
||||
raise serializers.ValidationError(_('Scheduled time must be in the future.'))
|
||||
return value
|
||||
|
||||
def validate_interval(self, value):
|
||||
"""
|
||||
Validates the provided interval based on the script's scheduling configuration.
|
||||
"""
|
||||
if value and not self.context['script'].python_class.scheduling_enabled:
|
||||
raise serializers.ValidationError(_("Scheduling is not enabled for this script."))
|
||||
raise serializers.ValidationError(_('Scheduling is not enabled for this script.'))
|
||||
return value
|
||||
|
||||
def validate(self, data):
|
||||
"""
|
||||
Validates the given data and ensures the necessary fields are populated.
|
||||
"""
|
||||
# Set the schedule_at time to now if only an interval is provided
|
||||
# while handling the case where schedule_at is null.
|
||||
if data.get('interval') and not data.get('schedule_at'):
|
||||
data['schedule_at'] = local_now()
|
||||
|
||||
return super().validate(data)
|
||||
|
||||
@@ -6,7 +6,6 @@ from extras.models import Tag, TaggedItem
|
||||
from netbox.api.exceptions import SerializerNotFound
|
||||
from netbox.api.fields import ContentTypeField, RelatedObjectCountField
|
||||
from netbox.api.serializers import BaseModelSerializer, ChangeLogMessageSerializer, ValidatedModelSerializer
|
||||
from users.api.serializers_.mixins import OwnerMixin
|
||||
from utilities.api import get_serializer_for_model
|
||||
|
||||
__all__ = (
|
||||
@@ -15,7 +14,7 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class TagSerializer(OwnerMixin, ChangeLogMessageSerializer, ValidatedModelSerializer):
|
||||
class TagSerializer(ChangeLogMessageSerializer, ValidatedModelSerializer):
|
||||
object_types = ContentTypeField(
|
||||
queryset=ObjectType.objects.with_feature('tags'),
|
||||
many=True,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user