mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-27 19:08:38 -06:00
Merge branch 'feature' into 9583-add_column_specific_search_field_to_tables
This commit is contained in:
commit
25a4e9448c
5
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
5
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
@ -17,15 +17,16 @@ body:
|
|||||||
How are you running NetBox? (For issues with the Docker image, please go to the
|
How are you running NetBox? (For issues with the Docker image, please go to the
|
||||||
[netbox-docker](https://github.com/netbox-community/netbox-docker) repo.)
|
[netbox-docker](https://github.com/netbox-community/netbox-docker) repo.)
|
||||||
options:
|
options:
|
||||||
- Self-hosted
|
|
||||||
- NetBox Cloud
|
- NetBox Cloud
|
||||||
|
- NetBox Enterprise
|
||||||
|
- Self-hosted
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: input
|
- type: input
|
||||||
attributes:
|
attributes:
|
||||||
label: NetBox Version
|
label: NetBox Version
|
||||||
description: What version of NetBox are you currently running?
|
description: What version of NetBox are you currently running?
|
||||||
placeholder: v3.7.3
|
placeholder: v3.7.4
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
|
2
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
2
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
@ -14,7 +14,7 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: NetBox version
|
label: NetBox version
|
||||||
description: What version of NetBox are you currently running?
|
description: What version of NetBox are you currently running?
|
||||||
placeholder: v3.7.3
|
placeholder: v3.7.4
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
|
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@ -84,4 +84,4 @@ jobs:
|
|||||||
run: coverage run --source="netbox/" netbox/manage.py test netbox/ --parallel
|
run: coverage run --source="netbox/" netbox/manage.py test netbox/ --parallel
|
||||||
|
|
||||||
- name: Show coverage report
|
- name: Show coverage report
|
||||||
run: coverage report --skip-covered --omit *migrations*
|
run: coverage report --skip-covered --omit '*/migrations/*,*/tests/*'
|
||||||
|
@ -101,7 +101,7 @@ markdown-include
|
|||||||
mkdocs-material
|
mkdocs-material
|
||||||
|
|
||||||
# Introspection for embedded code
|
# Introspection for embedded code
|
||||||
# https://github.com/mkdocstrings/mkdocstrings/blob/master/CHANGELOG.md
|
# https://github.com/mkdocstrings/mkdocstrings/blob/main/CHANGELOG.md
|
||||||
mkdocstrings[python-legacy]
|
mkdocstrings[python-legacy]
|
||||||
|
|
||||||
# Library for manipulating IP prefixes and addresses
|
# Library for manipulating IP prefixes and addresses
|
||||||
|
@ -384,7 +384,10 @@
|
|||||||
"8gfc-sfpp",
|
"8gfc-sfpp",
|
||||||
"16gfc-sfpp",
|
"16gfc-sfpp",
|
||||||
"32gfc-sfp28",
|
"32gfc-sfp28",
|
||||||
|
"32gfc-sfpp",
|
||||||
"64gfc-qsfpp",
|
"64gfc-qsfpp",
|
||||||
|
"64gfc-sfpdd",
|
||||||
|
"64gfc-sfpp",
|
||||||
"128gfc-qsfp28",
|
"128gfc-qsfp28",
|
||||||
"infiniband-sdr",
|
"infiniband-sdr",
|
||||||
"infiniband-ddr",
|
"infiniband-ddr",
|
||||||
|
@ -62,10 +62,11 @@ class Circuit(PrimaryModel):
|
|||||||
|
|
||||||
1. Import `gettext_lazy` as `_`.
|
1. Import `gettext_lazy` as `_`.
|
||||||
2. All form fields must specify a `label` wrapped with `gettext_lazy()`.
|
2. All form fields must specify a `label` wrapped with `gettext_lazy()`.
|
||||||
3. All headers under a form's `fieldsets` property must be wrapped with `gettext_lazy()`.
|
3. The name of each FieldSet on a form must be wrapped with `gettext_lazy()`.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from utilities.forms.rendering import FieldSet
|
||||||
|
|
||||||
class CircuitBulkEditForm(NetBoxModelBulkEditForm):
|
class CircuitBulkEditForm(NetBoxModelBulkEditForm):
|
||||||
description = forms.CharField(
|
description = forms.CharField(
|
||||||
@ -74,7 +75,7 @@ class CircuitBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
)
|
)
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Circuit'), ('provider', 'type', 'status', 'description')),
|
FieldSet('provider', 'type', 'status', 'description', name=_('Circuit')),
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -31,8 +31,7 @@ This section entails the installation and configuration of a local PostgreSQL da
|
|||||||
Once PostgreSQL has been installed, start the service and enable it to run at boot:
|
Once PostgreSQL has been installed, start the service and enable it to run at boot:
|
||||||
|
|
||||||
```no-highlight
|
```no-highlight
|
||||||
sudo systemctl start postgresql
|
sudo systemctl enable --now postgresql
|
||||||
sudo systemctl enable postgresql
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Before continuing, verify that you have installed PostgreSQL 12 or later:
|
Before continuing, verify that you have installed PostgreSQL 12 or later:
|
||||||
|
@ -14,8 +14,7 @@
|
|||||||
|
|
||||||
```no-highlight
|
```no-highlight
|
||||||
sudo yum install -y redis
|
sudo yum install -y redis
|
||||||
sudo systemctl start redis
|
sudo systemctl enable --now redis
|
||||||
sudo systemctl enable redis
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Before continuing, verify that your installed version of Redis is at least v4.0:
|
Before continuing, verify that your installed version of Redis is at least v4.0:
|
||||||
|
@ -27,8 +27,7 @@ sudo systemctl daemon-reload
|
|||||||
Then, start the `netbox` and `netbox-rq` services and enable them to initiate at boot time:
|
Then, start the `netbox` and `netbox-rq` services and enable them to initiate at boot time:
|
||||||
|
|
||||||
```no-highlight
|
```no-highlight
|
||||||
sudo systemctl start netbox netbox-rq
|
sudo systemctl enable --now netbox netbox-rq
|
||||||
sudo systemctl enable netbox netbox-rq
|
|
||||||
```
|
```
|
||||||
|
|
||||||
You can use the command `systemctl status netbox` to verify that the WSGI service is running:
|
You can use the command `systemctl status netbox` to verify that the WSGI service is running:
|
||||||
|
@ -26,3 +26,7 @@ The location's operational status.
|
|||||||
|
|
||||||
!!! tip
|
!!! tip
|
||||||
Additional statuses may be defined by setting `Location.status` under the [`FIELD_CHOICES`](../../configuration/data-validation.md#field_choices) configuration parameter.
|
Additional statuses may be defined by setting `Location.status` under the [`FIELD_CHOICES`](../../configuration/data-validation.md#field_choices) configuration parameter.
|
||||||
|
|
||||||
|
### Facility
|
||||||
|
|
||||||
|
Data center or facility designation for identifying the location.
|
||||||
|
@ -38,7 +38,7 @@ The type of data this field holds. This must be one of the following:
|
|||||||
| Object | A single NetBox object of the type defined by `object_type` |
|
| Object | A single NetBox object of the type defined by `object_type` |
|
||||||
| Multiple object | One or more NetBox objects of the type defined by `object_type` |
|
| Multiple object | One or more NetBox objects of the type defined by `object_type` |
|
||||||
|
|
||||||
### Object Type
|
### Related Object Type
|
||||||
|
|
||||||
For object and multiple-object fields only. Designates the type of NetBox object being referenced.
|
For object and multiple-object fields only. Designates the type of NetBox object being referenced.
|
||||||
|
|
||||||
|
@ -15,16 +15,18 @@ NetBox provides several base form classes for use by plugins.
|
|||||||
|
|
||||||
This is the base form for creating and editing NetBox models. It extends Django's ModelForm to add support for tags and custom fields.
|
This is the base form for creating and editing NetBox models. It extends Django's ModelForm to add support for tags and custom fields.
|
||||||
|
|
||||||
| Attribute | Description |
|
| Attribute | Description |
|
||||||
|-------------|-------------------------------------------------------------|
|
|-------------|---------------------------------------------------------------------------------------|
|
||||||
| `fieldsets` | A tuple of two-tuples defining the form's layout (optional) |
|
| `fieldsets` | A tuple of `FieldSet` instances which control how form fields are rendered (optional) |
|
||||||
|
|
||||||
**Example**
|
**Example**
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
from dcim.models import Site
|
from dcim.models import Site
|
||||||
from netbox.forms import NetBoxModelForm
|
from netbox.forms import NetBoxModelForm
|
||||||
from utilities.forms.fields import CommentField, DynamicModelChoiceField
|
from utilities.forms.fields import CommentField, DynamicModelChoiceField
|
||||||
|
from utilities.forms.rendering import FieldSet
|
||||||
from .models import MyModel
|
from .models import MyModel
|
||||||
|
|
||||||
class MyModelForm(NetBoxModelForm):
|
class MyModelForm(NetBoxModelForm):
|
||||||
@ -33,8 +35,8 @@ class MyModelForm(NetBoxModelForm):
|
|||||||
)
|
)
|
||||||
comments = CommentField()
|
comments = CommentField()
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
('Model Stuff', ('name', 'status', 'site', 'tags')),
|
FieldSet('name', 'status', 'site', 'tags', name=_('Model Stuff')),
|
||||||
('Tenancy', ('tenant_group', 'tenant')),
|
FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -52,6 +54,7 @@ This form facilitates the bulk import of new objects from CSV, JSON, or YAML dat
|
|||||||
**Example**
|
**Example**
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
from dcim.models import Site
|
from dcim.models import Site
|
||||||
from netbox.forms import NetBoxModelImportForm
|
from netbox.forms import NetBoxModelImportForm
|
||||||
from utilities.forms import CSVModelChoiceField
|
from utilities.forms import CSVModelChoiceField
|
||||||
@ -62,7 +65,7 @@ class MyModelImportForm(NetBoxModelImportForm):
|
|||||||
site = CSVModelChoiceField(
|
site = CSVModelChoiceField(
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
help_text='Assigned site'
|
help_text=_('Assigned site')
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -77,16 +80,18 @@ This form facilitates editing multiple objects in bulk. Unlike a model form, thi
|
|||||||
| Attribute | Description |
|
| Attribute | Description |
|
||||||
|-------------------|---------------------------------------------------------------------------------------------|
|
|-------------------|---------------------------------------------------------------------------------------------|
|
||||||
| `model` | The model of object being edited |
|
| `model` | The model of object being edited |
|
||||||
| `fieldsets` | A tuple of two-tuples defining the form's layout (optional) |
|
| `fieldsets` | A tuple of `FieldSet` instances which control how form fields are rendered (optional) |
|
||||||
| `nullable_fields` | A tuple of fields which can be nullified (set to empty) using the bulk edit form (optional) |
|
| `nullable_fields` | A tuple of fields which can be nullified (set to empty) using the bulk edit form (optional) |
|
||||||
|
|
||||||
**Example**
|
**Example**
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from django import forms
|
from django import forms
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
from dcim.models import Site
|
from dcim.models import Site
|
||||||
from netbox.forms import NetBoxModelImportForm
|
from netbox.forms import NetBoxModelImportForm
|
||||||
from utilities.forms import CommentField, DynamicModelChoiceField
|
from utilities.forms import CommentField, DynamicModelChoiceField
|
||||||
|
from utilities.forms.rendering import FieldSet
|
||||||
from .models import MyModel, MyModelStatusChoices
|
from .models import MyModel, MyModelStatusChoices
|
||||||
|
|
||||||
|
|
||||||
@ -106,7 +111,7 @@ class MyModelEditForm(NetBoxModelImportForm):
|
|||||||
|
|
||||||
model = MyModel
|
model = MyModel
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
('Model Stuff', ('name', 'status', 'site')),
|
FieldSet('name', 'status', 'site', name=_('Model Stuff')),
|
||||||
)
|
)
|
||||||
nullable_fields = ('site', 'comments')
|
nullable_fields = ('site', 'comments')
|
||||||
```
|
```
|
||||||
@ -115,10 +120,10 @@ class MyModelEditForm(NetBoxModelImportForm):
|
|||||||
|
|
||||||
This form class is used to render a form expressly for filtering a list of objects. Its fields should correspond to filters defined on the model's filter set.
|
This form class is used to render a form expressly for filtering a list of objects. Its fields should correspond to filters defined on the model's filter set.
|
||||||
|
|
||||||
| Attribute | Description |
|
| Attribute | Description |
|
||||||
|-------------------|-------------------------------------------------------------|
|
|-------------|---------------------------------------------------------------------------------------|
|
||||||
| `model` | The model of object being edited |
|
| `model` | The model of object being edited |
|
||||||
| `fieldsets` | A tuple of two-tuples defining the form's layout (optional) |
|
| `fieldsets` | A tuple of `FieldSet` instances which control how form fields are rendered (optional) |
|
||||||
|
|
||||||
**Example**
|
**Example**
|
||||||
|
|
||||||
@ -206,3 +211,13 @@ In addition to the [form fields provided by Django](https://docs.djangoproject.c
|
|||||||
::: utilities.forms.fields.CSVMultipleContentTypeField
|
::: utilities.forms.fields.CSVMultipleContentTypeField
|
||||||
options:
|
options:
|
||||||
members: false
|
members: false
|
||||||
|
|
||||||
|
## Form Rendering
|
||||||
|
|
||||||
|
::: utilities.forms.rendering.FieldSet
|
||||||
|
|
||||||
|
::: utilities.forms.rendering.InlineFields
|
||||||
|
|
||||||
|
::: utilities.forms.rendering.TabbedGroups
|
||||||
|
|
||||||
|
::: utilities.forms.rendering.ObjectAttribute
|
||||||
|
@ -1,6 +1,31 @@
|
|||||||
# NetBox v3.7
|
# NetBox v3.7
|
||||||
|
|
||||||
## v3.7.4 (FUTURE)
|
## v3.7.5 (FUTURE)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## v3.7.4 (2024-03-13)
|
||||||
|
|
||||||
|
### Enhancements
|
||||||
|
|
||||||
|
* [#14206](https://github.com/netbox-community/netbox/issues/14206) - Add additional FibreChannel SFP+ interface types
|
||||||
|
* [#14366](https://github.com/netbox-community/netbox/issues/14366) - Enable custom links for config contexts & templates
|
||||||
|
* [#15291](https://github.com/netbox-community/netbox/issues/15291) - Add tunnel termination buttons to VM interfaces table
|
||||||
|
* [#15297](https://github.com/netbox-community/netbox/issues/15297) - Linkify platform column in device & virtual machine tables
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* [#13722](https://github.com/netbox-community/netbox/issues/13722) - Fix range expansion for comma-separated numerical values
|
||||||
|
* [#14832](https://github.com/netbox-community/netbox/issues/14832) - Enable querying IP addresses for an FHRP group via GraphQL
|
||||||
|
* [#15220](https://github.com/netbox-community/netbox/issues/15220) - Fix validation check when bulk editing the mask length of IP addresses
|
||||||
|
* [#15232](https://github.com/netbox-community/netbox/issues/15232) - Permit user with sufficient permissions to assign an inventory item to a device type
|
||||||
|
* [#15241](https://github.com/netbox-community/netbox/issues/15241) - Restore missing `display` field on VirtualDisk serialization in REST API
|
||||||
|
* [#15243](https://github.com/netbox-community/netbox/issues/15243) - Correct representation of installed module when listing module bays using REST API brief mode
|
||||||
|
* [#15316](https://github.com/netbox-community/netbox/issues/15316) - Fix selection of 3DES encryption for IKE & IPSec proposals
|
||||||
|
* [#15322](https://github.com/netbox-community/netbox/issues/15322) - Add description field to YAML export for device & module types
|
||||||
|
* [#15336](https://github.com/netbox-community/netbox/issues/15336) - Correct label for recurring scheduled jobs
|
||||||
|
* [#15347](https://github.com/netbox-community/netbox/issues/15347) - Fix querying virtual machine contacts via GraphQL
|
||||||
|
* [#15356](https://github.com/netbox-community/netbox/issues/15356) - Fix assignment of front & rear images to device types via REST API
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
* The deprecated `device_role` & `device_role_id` filters for devices have been removed. (Use `role` and `role_id` instead.)
|
* The deprecated `device_role` & `device_role_id` filters for devices have been removed. (Use `role` and `role_id` instead.)
|
||||||
* The legacy reports functionality has been dropped. Reports will be automatically converted to custom scripts on upgrade.
|
* The legacy reports functionality has been dropped. Reports will be automatically converted to custom scripts on upgrade.
|
||||||
|
* The `parent` and `parent_id` filters for locations now return only immediate children of the specified location. (Use `ancestor` and `ancestor_id` to return _all_ descendants.)
|
||||||
|
|
||||||
### New Features
|
### New Features
|
||||||
|
|
||||||
@ -17,18 +18,26 @@ The NetBox user interface has been completely refreshed and updated.
|
|||||||
|
|
||||||
The REST API now supports specifying which fields to include in the response data.
|
The REST API now supports specifying which fields to include in the response data.
|
||||||
|
|
||||||
|
#### Advanced FieldSet Functionality ([#14739](https://github.com/netbox-community/netbox/issues/14739))
|
||||||
|
|
||||||
|
New resources have been introduced to enable advanced form rendering without a need for custom HTML templates.
|
||||||
|
|
||||||
### Enhancements
|
### Enhancements
|
||||||
|
|
||||||
* [#12851](https://github.com/netbox-community/netbox/issues/12851) - Replace bleach HTML sanitization library with nh3
|
* [#12851](https://github.com/netbox-community/netbox/issues/12851) - Replace bleach HTML sanitization library with nh3
|
||||||
* [#13283](https://github.com/netbox-community/netbox/issues/13283) - Display additional context on API-backed dropdown fields
|
* [#13283](https://github.com/netbox-community/netbox/issues/13283) - Display additional context on API-backed dropdown fields
|
||||||
|
* [#13918](https://github.com/netbox-community/netbox/issues/13918) - Add `facility` field to Location model
|
||||||
* [#14237](https://github.com/netbox-community/netbox/issues/14237) - Automatically clear dependent selection fields when modifying a parent selection
|
* [#14237](https://github.com/netbox-community/netbox/issues/14237) - Automatically clear dependent selection fields when modifying a parent selection
|
||||||
|
* [#14454](https://github.com/netbox-community/netbox/issues/14454) - Include member devices for virtual chassis in REST API
|
||||||
* [#14637](https://github.com/netbox-community/netbox/issues/14637) - Upgrade to Django 5.0
|
* [#14637](https://github.com/netbox-community/netbox/issues/14637) - Upgrade to Django 5.0
|
||||||
* [#14672](https://github.com/netbox-community/netbox/issues/14672) - Add support for Python 3.12
|
* [#14672](https://github.com/netbox-community/netbox/issues/14672) - Add support for Python 3.12
|
||||||
* [#14728](https://github.com/netbox-community/netbox/issues/14728) - The plugins list view has been moved from the legacy admin UI to the main NetBox UI
|
* [#14728](https://github.com/netbox-community/netbox/issues/14728) - The plugins list view has been moved from the legacy admin UI to the main NetBox UI
|
||||||
* [#14729](https://github.com/netbox-community/netbox/issues/14729) - All background task views have been moved from the legacy admin UI to the main NetBox UI
|
* [#14729](https://github.com/netbox-community/netbox/issues/14729) - All background task views have been moved from the legacy admin UI to the main NetBox UI
|
||||||
* [#14438](https://github.com/netbox-community/netbox/issues/14438) - Track individual custom scripts as database objects
|
* [#14438](https://github.com/netbox-community/netbox/issues/14438) - Track individual custom scripts as database objects
|
||||||
* [#15131](https://github.com/netbox-community/netbox/issues/15131) - Automatically annotate related object counts on REST API querysets
|
* [#15131](https://github.com/netbox-community/netbox/issues/15131) - Automatically annotate related object counts on REST API querysets
|
||||||
|
* [#15237](https://github.com/netbox-community/netbox/issues/15237) - Ensure consistent filtering ability for all model fields
|
||||||
* [#15238](https://github.com/netbox-community/netbox/issues/15238) - Include the `description` field in "brief" REST API serializations
|
* [#15238](https://github.com/netbox-community/netbox/issues/15238) - Include the `description` field in "brief" REST API serializations
|
||||||
|
* [#15383](https://github.com/netbox-community/netbox/issues/15383) - Standardize filtering logic for the parents of recursively-nested models (parent & ancestor filters)
|
||||||
|
|
||||||
### Other Changes
|
### Other Changes
|
||||||
|
|
||||||
@ -44,6 +53,7 @@ The REST API now supports specifying which fields to include in the response dat
|
|||||||
* [#15042](https://github.com/netbox-community/netbox/issues/15042) - Rearchitect the logic for registering models & model features
|
* [#15042](https://github.com/netbox-community/netbox/issues/15042) - Rearchitect the logic for registering models & model features
|
||||||
* [#15099](https://github.com/netbox-community/netbox/issues/15099) - Remove obsolete `device_role` and `device_role_id` filters for devices
|
* [#15099](https://github.com/netbox-community/netbox/issues/15099) - Remove obsolete `device_role` and `device_role_id` filters for devices
|
||||||
* [#15100](https://github.com/netbox-community/netbox/issues/15100) - Remove obsolete `NullableCharField` class
|
* [#15100](https://github.com/netbox-community/netbox/issues/15100) - Remove obsolete `NullableCharField` class
|
||||||
|
* [#15193](https://github.com/netbox-community/netbox/issues/15193) - Switch to compiled distribution of the `psycopg` library
|
||||||
* [#15277](https://github.com/netbox-community/netbox/issues/15277) - Replace references to ContentType without ObjectType proxy model & standardize field names
|
* [#15277](https://github.com/netbox-community/netbox/issues/15277) - Replace references to ContentType without ObjectType proxy model & standardize field names
|
||||||
* [#15292](https://github.com/netbox-community/netbox/issues/15292) - Remove obsolete `device_role` attribute from Device model (this field was renamed to `role` in v3.6)
|
* [#15292](https://github.com/netbox-community/netbox/issues/15292) - Remove obsolete `device_role` attribute from Device model (this field was renamed to `role` in v3.6)
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ class ProviderFilterSet(NetBoxModelFilterSet, ContactModelFilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Provider
|
model = Provider
|
||||||
fields = ['id', 'name', 'slug', 'description']
|
fields = ('id', 'name', 'slug', 'description')
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
@ -95,7 +95,7 @@ class ProviderAccountFilterSet(NetBoxModelFilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ProviderAccount
|
model = ProviderAccount
|
||||||
fields = ['id', 'name', 'account', 'description']
|
fields = ('id', 'name', 'account', 'description')
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
@ -122,7 +122,7 @@ class ProviderNetworkFilterSet(NetBoxModelFilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ProviderNetwork
|
model = ProviderNetwork
|
||||||
fields = ['id', 'name', 'service_id', 'description']
|
fields = ('id', 'name', 'service_id', 'description')
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
@ -139,7 +139,7 @@ class CircuitTypeFilterSet(OrganizationalModelFilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CircuitType
|
model = CircuitType
|
||||||
fields = ['id', 'name', 'slug', 'color', 'description']
|
fields = ('id', 'name', 'slug', 'color', 'description')
|
||||||
|
|
||||||
|
|
||||||
class CircuitFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilterSet):
|
class CircuitFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilterSet):
|
||||||
@ -158,6 +158,12 @@ class CircuitFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilte
|
|||||||
queryset=ProviderAccount.objects.all(),
|
queryset=ProviderAccount.objects.all(),
|
||||||
label=_('Provider account (ID)'),
|
label=_('Provider account (ID)'),
|
||||||
)
|
)
|
||||||
|
provider_account = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='provider_account__account',
|
||||||
|
queryset=Provider.objects.all(),
|
||||||
|
to_field_name='account',
|
||||||
|
label=_('Provider account (account)'),
|
||||||
|
)
|
||||||
provider_network_id = django_filters.ModelMultipleChoiceFilter(
|
provider_network_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
field_name='terminations__provider_network',
|
field_name='terminations__provider_network',
|
||||||
queryset=ProviderNetwork.objects.all(),
|
queryset=ProviderNetwork.objects.all(),
|
||||||
@ -214,10 +220,18 @@ class CircuitFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilte
|
|||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label=_('Site (slug)'),
|
label=_('Site (slug)'),
|
||||||
)
|
)
|
||||||
|
termination_a_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
queryset=CircuitTermination.objects.all(),
|
||||||
|
label=_('Termination A (ID)'),
|
||||||
|
)
|
||||||
|
termination_z_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
queryset=CircuitTermination.objects.all(),
|
||||||
|
label=_('Termination A (ID)'),
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Circuit
|
model = Circuit
|
||||||
fields = ['id', 'cid', 'description', 'install_date', 'termination_date', 'commit_rate']
|
fields = ('id', 'cid', 'description', 'install_date', 'termination_date', 'commit_rate')
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
@ -258,7 +272,10 @@ class CircuitTerminationFilterSet(NetBoxModelFilterSet, CabledObjectFilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CircuitTermination
|
model = CircuitTermination
|
||||||
fields = ['id', 'term_side', 'port_speed', 'upstream_speed', 'xconnect_id', 'description', 'cable_end']
|
fields = (
|
||||||
|
'id', 'term_side', 'port_speed', 'upstream_speed', 'xconnect_id', 'description', 'mark_connected',
|
||||||
|
'pp_info', 'cable_end',
|
||||||
|
)
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
|
@ -8,6 +8,7 @@ from netbox.forms import NetBoxModelBulkEditForm
|
|||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
from utilities.forms import add_blank_choice
|
from utilities.forms import add_blank_choice
|
||||||
from utilities.forms.fields import ColorField, CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField
|
from utilities.forms.fields import ColorField, CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField
|
||||||
|
from utilities.forms.rendering import FieldSet
|
||||||
from utilities.forms.widgets import DatePicker, NumberWithOptions
|
from utilities.forms.widgets import DatePicker, NumberWithOptions
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
@ -34,7 +35,7 @@ class ProviderBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
|
|
||||||
model = Provider
|
model = Provider
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('asns', 'description')),
|
FieldSet('asns', 'description'),
|
||||||
)
|
)
|
||||||
nullable_fields = (
|
nullable_fields = (
|
||||||
'asns', 'description', 'comments',
|
'asns', 'description', 'comments',
|
||||||
@ -56,7 +57,7 @@ class ProviderAccountBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
|
|
||||||
model = ProviderAccount
|
model = ProviderAccount
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('provider', 'description')),
|
FieldSet('provider', 'description'),
|
||||||
)
|
)
|
||||||
nullable_fields = (
|
nullable_fields = (
|
||||||
'description', 'comments',
|
'description', 'comments',
|
||||||
@ -83,7 +84,7 @@ class ProviderNetworkBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
|
|
||||||
model = ProviderNetwork
|
model = ProviderNetwork
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('provider', 'service_id', 'description')),
|
FieldSet('provider', 'service_id', 'description'),
|
||||||
)
|
)
|
||||||
nullable_fields = (
|
nullable_fields = (
|
||||||
'service_id', 'description', 'comments',
|
'service_id', 'description', 'comments',
|
||||||
@ -103,7 +104,7 @@ class CircuitTypeBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
|
|
||||||
model = CircuitType
|
model = CircuitType
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('color', 'description')),
|
FieldSet('color', 'description'),
|
||||||
)
|
)
|
||||||
nullable_fields = ('color', 'description')
|
nullable_fields = ('color', 'description')
|
||||||
|
|
||||||
@ -164,9 +165,9 @@ class CircuitBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
|
|
||||||
model = Circuit
|
model = Circuit
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Circuit'), ('provider', 'type', 'status', 'description')),
|
FieldSet('provider', 'type', 'status', 'description', name=_('Circuit')),
|
||||||
(_('Service Parameters'), ('provider_account', 'install_date', 'termination_date', 'commit_rate')),
|
FieldSet('provider_account', 'install_date', 'termination_date', 'commit_rate', name=_('Service Parameters')),
|
||||||
(_('Tenancy'), ('tenant',)),
|
FieldSet('tenant', name=_('Tenancy')),
|
||||||
)
|
)
|
||||||
nullable_fields = (
|
nullable_fields = (
|
||||||
'tenant', 'commit_rate', 'description', 'comments',
|
'tenant', 'commit_rate', 'description', 'comments',
|
||||||
|
@ -8,6 +8,7 @@ from ipam.models import ASN
|
|||||||
from netbox.forms import NetBoxModelFilterSetForm
|
from netbox.forms import NetBoxModelFilterSetForm
|
||||||
from tenancy.forms import TenancyFilterForm, ContactModelFilterForm
|
from tenancy.forms import TenancyFilterForm, ContactModelFilterForm
|
||||||
from utilities.forms.fields import ColorField, DynamicModelMultipleChoiceField, TagFilterField
|
from utilities.forms.fields import ColorField, DynamicModelMultipleChoiceField, TagFilterField
|
||||||
|
from utilities.forms.rendering import FieldSet
|
||||||
from utilities.forms.widgets import DatePicker, NumberWithOptions
|
from utilities.forms.widgets import DatePicker, NumberWithOptions
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
@ -22,10 +23,10 @@ __all__ = (
|
|||||||
class ProviderFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
|
class ProviderFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
|
||||||
model = Provider
|
model = Provider
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
FieldSet('q', 'filter_id', 'tag'),
|
||||||
(_('Location'), ('region_id', 'site_group_id', 'site_id')),
|
FieldSet('region_id', 'site_group_id', 'site_id', name=_('Location')),
|
||||||
(_('ASN'), ('asn',)),
|
FieldSet('asn', name=_('ASN')),
|
||||||
(_('Contacts'), ('contact', 'contact_role', 'contact_group')),
|
FieldSet('contact', 'contact_role', 'contact_group', name=_('Contacts')),
|
||||||
)
|
)
|
||||||
region_id = DynamicModelMultipleChoiceField(
|
region_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=Region.objects.all(),
|
queryset=Region.objects.all(),
|
||||||
@ -61,8 +62,8 @@ class ProviderFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
|
|||||||
class ProviderAccountFilterForm(NetBoxModelFilterSetForm):
|
class ProviderAccountFilterForm(NetBoxModelFilterSetForm):
|
||||||
model = ProviderAccount
|
model = ProviderAccount
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
FieldSet('q', 'filter_id', 'tag'),
|
||||||
(_('Attributes'), ('provider_id', 'account')),
|
FieldSet('provider_id', 'account', name=_('Attributes')),
|
||||||
)
|
)
|
||||||
provider_id = DynamicModelMultipleChoiceField(
|
provider_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=Provider.objects.all(),
|
queryset=Provider.objects.all(),
|
||||||
@ -79,8 +80,8 @@ class ProviderAccountFilterForm(NetBoxModelFilterSetForm):
|
|||||||
class ProviderNetworkFilterForm(NetBoxModelFilterSetForm):
|
class ProviderNetworkFilterForm(NetBoxModelFilterSetForm):
|
||||||
model = ProviderNetwork
|
model = ProviderNetwork
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
FieldSet('q', 'filter_id', 'tag'),
|
||||||
(_('Attributes'), ('provider_id', 'service_id')),
|
FieldSet('provider_id', 'service_id', name=_('Attributes')),
|
||||||
)
|
)
|
||||||
provider_id = DynamicModelMultipleChoiceField(
|
provider_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=Provider.objects.all(),
|
queryset=Provider.objects.all(),
|
||||||
@ -98,8 +99,8 @@ class ProviderNetworkFilterForm(NetBoxModelFilterSetForm):
|
|||||||
class CircuitTypeFilterForm(NetBoxModelFilterSetForm):
|
class CircuitTypeFilterForm(NetBoxModelFilterSetForm):
|
||||||
model = CircuitType
|
model = CircuitType
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
FieldSet('q', 'filter_id', 'tag'),
|
||||||
(_('Attributes'), ('color',)),
|
FieldSet('color', name=_('Attributes')),
|
||||||
)
|
)
|
||||||
tag = TagFilterField(model)
|
tag = TagFilterField(model)
|
||||||
|
|
||||||
@ -112,12 +113,12 @@ class CircuitTypeFilterForm(NetBoxModelFilterSetForm):
|
|||||||
class CircuitFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilterSetForm):
|
class CircuitFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilterSetForm):
|
||||||
model = Circuit
|
model = Circuit
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
FieldSet('q', 'filter_id', 'tag'),
|
||||||
(_('Provider'), ('provider_id', 'provider_account_id', 'provider_network_id')),
|
FieldSet('provider_id', 'provider_account_id', 'provider_network_id', name=_('Provider')),
|
||||||
(_('Attributes'), ('type_id', 'status', 'install_date', 'termination_date', 'commit_rate')),
|
FieldSet('type_id', 'status', 'install_date', 'termination_date', 'commit_rate', name=_('Attributes')),
|
||||||
(_('Location'), ('region_id', 'site_group_id', 'site_id')),
|
FieldSet('region_id', 'site_group_id', 'site_id', name=_('Location')),
|
||||||
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
|
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||||
(_('Contacts'), ('contact', 'contact_role', 'contact_group')),
|
FieldSet('contact', 'contact_role', 'contact_group', name=_('Contacts')),
|
||||||
)
|
)
|
||||||
selector_fields = ('filter_id', 'q', 'region_id', 'site_group_id', 'site_id', 'provider_id', 'provider_network_id')
|
selector_fields = ('filter_id', 'q', 'region_id', 'site_group_id', 'site_id', 'provider_id', 'provider_network_id')
|
||||||
type_id = DynamicModelMultipleChoiceField(
|
type_id = DynamicModelMultipleChoiceField(
|
||||||
|
@ -7,6 +7,7 @@ from ipam.models import ASN
|
|||||||
from netbox.forms import NetBoxModelForm
|
from netbox.forms import NetBoxModelForm
|
||||||
from tenancy.forms import TenancyForm
|
from tenancy.forms import TenancyForm
|
||||||
from utilities.forms.fields import CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, SlugField
|
from utilities.forms.fields import CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, SlugField
|
||||||
|
from utilities.forms.rendering import FieldSet, TabbedGroups
|
||||||
from utilities.forms.widgets import DatePicker, NumberWithOptions
|
from utilities.forms.widgets import DatePicker, NumberWithOptions
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
@ -29,7 +30,7 @@ class ProviderForm(NetBoxModelForm):
|
|||||||
comments = CommentField()
|
comments = CommentField()
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Provider'), ('name', 'slug', 'asns', 'description', 'tags')),
|
FieldSet('name', 'slug', 'asns', 'description', 'tags'),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -61,7 +62,7 @@ class ProviderNetworkForm(NetBoxModelForm):
|
|||||||
comments = CommentField()
|
comments = CommentField()
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Provider Network'), ('provider', 'name', 'service_id', 'description', 'tags')),
|
FieldSet('provider', 'name', 'service_id', 'description', 'tags'),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -75,9 +76,7 @@ class CircuitTypeForm(NetBoxModelForm):
|
|||||||
slug = SlugField()
|
slug = SlugField()
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Circuit Type'), (
|
FieldSet('name', 'slug', 'color', 'description', 'tags'),
|
||||||
'name', 'slug', 'color', 'description', 'tags',
|
|
||||||
)),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -107,9 +106,9 @@ class CircuitForm(TenancyForm, NetBoxModelForm):
|
|||||||
comments = CommentField()
|
comments = CommentField()
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Circuit'), ('provider', 'provider_account', 'cid', 'type', 'status', 'description', 'tags')),
|
FieldSet('provider', 'provider_account', 'cid', 'type', 'status', 'description', 'tags', name=_('Circuit')),
|
||||||
(_('Service Parameters'), ('install_date', 'termination_date', 'commit_rate')),
|
FieldSet('install_date', 'termination_date', 'commit_rate', name=_('Service Parameters')),
|
||||||
(_('Tenancy'), ('tenant_group', 'tenant')),
|
FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -146,6 +145,18 @@ class CircuitTerminationForm(NetBoxModelForm):
|
|||||||
selector=True
|
selector=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fieldsets = (
|
||||||
|
FieldSet(
|
||||||
|
'circuit', 'term_side', 'description', 'tags',
|
||||||
|
TabbedGroups(
|
||||||
|
FieldSet('site', name=_('Site')),
|
||||||
|
FieldSet('provider_network', name=_('Provider Network')),
|
||||||
|
),
|
||||||
|
'mark_connected', name=_('Circuit Termination')
|
||||||
|
),
|
||||||
|
FieldSet('port_speed', 'upstream_speed', 'xconnect_id', 'pp_info', name=_('Termination Details')),
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CircuitTermination
|
model = CircuitTermination
|
||||||
fields = [
|
fields = [
|
||||||
|
@ -330,6 +330,7 @@ class CircuitTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
class CircuitTerminationTestCase(TestCase, ChangeLoggedFilterSetTests):
|
class CircuitTerminationTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = CircuitTermination.objects.all()
|
queryset = CircuitTermination.objects.all()
|
||||||
filterset = CircuitTerminationFilterSet
|
filterset = CircuitTerminationFilterSet
|
||||||
|
ignore_fields = ('cable',)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
|
@ -412,7 +412,6 @@ class CircuitContactsView(ObjectContactsView):
|
|||||||
class CircuitTerminationEditView(generic.ObjectEditView):
|
class CircuitTerminationEditView(generic.ObjectEditView):
|
||||||
queryset = CircuitTermination.objects.all()
|
queryset = CircuitTermination.objects.all()
|
||||||
form = forms.CircuitTerminationForm
|
form = forms.CircuitTerminationForm
|
||||||
template_name = 'circuits/circuittermination_edit.html'
|
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(CircuitTermination, 'delete')
|
@register_model_view(CircuitTermination, 'delete')
|
||||||
|
@ -28,7 +28,7 @@ class DataSourceFilterSet(NetBoxModelFilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = DataSource
|
model = DataSource
|
||||||
fields = ('id', 'name', 'enabled', 'description')
|
fields = ('id', 'name', 'enabled', 'description', 'source_url', 'last_synced')
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
@ -115,7 +115,7 @@ class JobFilterSet(BaseFilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Job
|
model = Job
|
||||||
fields = ('id', 'object_type', 'object_id', 'name', 'interval', 'status', 'user')
|
fields = ('id', 'object_type', 'object_id', 'name', 'interval', 'status', 'user', 'job_id')
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
@ -134,9 +134,7 @@ class ConfigRevisionFilterSet(BaseFilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ConfigRevision
|
model = ConfigRevision
|
||||||
fields = [
|
fields = ('id', 'created', 'comment')
|
||||||
'id',
|
|
||||||
]
|
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
|
@ -5,6 +5,7 @@ from core.models import *
|
|||||||
from netbox.forms import NetBoxModelBulkEditForm
|
from netbox.forms import NetBoxModelBulkEditForm
|
||||||
from netbox.utils import get_data_backend_choices
|
from netbox.utils import get_data_backend_choices
|
||||||
from utilities.forms.fields import CommentField
|
from utilities.forms.fields import CommentField
|
||||||
|
from utilities.forms.rendering import FieldSet
|
||||||
from utilities.forms.widgets import BulkEditNullBooleanSelect
|
from utilities.forms.widgets import BulkEditNullBooleanSelect
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
@ -41,7 +42,7 @@ class DataSourceBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
|
|
||||||
model = DataSource
|
model = DataSource
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('type', 'enabled', 'description', 'comments', 'parameters', 'ignore_rules')),
|
FieldSet('type', 'enabled', 'description', 'comments', 'parameters', 'ignore_rules'),
|
||||||
)
|
)
|
||||||
nullable_fields = (
|
nullable_fields = (
|
||||||
'description', 'description', 'parameters', 'comments', 'parameters', 'ignore_rules',
|
'description', 'description', 'parameters', 'comments', 'parameters', 'ignore_rules',
|
||||||
|
@ -9,7 +9,8 @@ from netbox.forms.mixins import SavedFiltersMixin
|
|||||||
from netbox.utils import get_data_backend_choices
|
from netbox.utils import get_data_backend_choices
|
||||||
from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES, FilterForm
|
from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES, FilterForm
|
||||||
from utilities.forms.fields import ContentTypeChoiceField, DynamicModelMultipleChoiceField
|
from utilities.forms.fields import ContentTypeChoiceField, DynamicModelMultipleChoiceField
|
||||||
from utilities.forms.widgets import APISelectMultiple, DateTimePicker
|
from utilities.forms.rendering import FieldSet
|
||||||
|
from utilities.forms.widgets import DateTimePicker
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'ConfigRevisionFilterForm',
|
'ConfigRevisionFilterForm',
|
||||||
@ -22,8 +23,8 @@ __all__ = (
|
|||||||
class DataSourceFilterForm(NetBoxModelFilterSetForm):
|
class DataSourceFilterForm(NetBoxModelFilterSetForm):
|
||||||
model = DataSource
|
model = DataSource
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id')),
|
FieldSet('q', 'filter_id'),
|
||||||
(_('Data Source'), ('type', 'status')),
|
FieldSet('type', 'status', name=_('Data Source')),
|
||||||
)
|
)
|
||||||
type = forms.MultipleChoiceField(
|
type = forms.MultipleChoiceField(
|
||||||
label=_('Type'),
|
label=_('Type'),
|
||||||
@ -47,8 +48,8 @@ class DataSourceFilterForm(NetBoxModelFilterSetForm):
|
|||||||
class DataFileFilterForm(NetBoxModelFilterSetForm):
|
class DataFileFilterForm(NetBoxModelFilterSetForm):
|
||||||
model = DataFile
|
model = DataFile
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id')),
|
FieldSet('q', 'filter_id'),
|
||||||
(_('File'), ('source_id',)),
|
FieldSet('source_id', name=_('File')),
|
||||||
)
|
)
|
||||||
source_id = DynamicModelMultipleChoiceField(
|
source_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=DataSource.objects.all(),
|
queryset=DataSource.objects.all(),
|
||||||
@ -59,12 +60,12 @@ class DataFileFilterForm(NetBoxModelFilterSetForm):
|
|||||||
|
|
||||||
class JobFilterForm(SavedFiltersMixin, FilterForm):
|
class JobFilterForm(SavedFiltersMixin, FilterForm):
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id')),
|
FieldSet('q', 'filter_id'),
|
||||||
(_('Attributes'), ('object_type', 'status')),
|
FieldSet('object_type', 'status', name=_('Attributes')),
|
||||||
(_('Creation'), (
|
FieldSet(
|
||||||
'created__before', 'created__after', 'scheduled__before', 'scheduled__after', 'started__before',
|
'created__before', 'created__after', 'scheduled__before', 'scheduled__after', 'started__before',
|
||||||
'started__after', 'completed__before', 'completed__after', 'user',
|
'started__after', 'completed__before', 'completed__after', 'user', name=_('Creation')
|
||||||
)),
|
),
|
||||||
)
|
)
|
||||||
object_type = ContentTypeChoiceField(
|
object_type = ContentTypeChoiceField(
|
||||||
label=_('Object Type'),
|
label=_('Object Type'),
|
||||||
@ -125,5 +126,5 @@ class JobFilterForm(SavedFiltersMixin, FilterForm):
|
|||||||
|
|
||||||
class ConfigRevisionFilterForm(SavedFiltersMixin, FilterForm):
|
class ConfigRevisionFilterForm(SavedFiltersMixin, FilterForm):
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id')),
|
FieldSet('q', 'filter_id'),
|
||||||
)
|
)
|
||||||
|
@ -13,6 +13,7 @@ from netbox.registry import registry
|
|||||||
from netbox.utils import get_data_backend_choices
|
from netbox.utils import get_data_backend_choices
|
||||||
from utilities.forms import get_field_value
|
from utilities.forms import get_field_value
|
||||||
from utilities.forms.fields import CommentField
|
from utilities.forms.fields import CommentField
|
||||||
|
from utilities.forms.rendering import FieldSet
|
||||||
from utilities.forms.widgets import HTMXSelect
|
from utilities.forms.widgets import HTMXSelect
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
@ -49,11 +50,11 @@ class DataSourceForm(NetBoxModelForm):
|
|||||||
@property
|
@property
|
||||||
def fieldsets(self):
|
def fieldsets(self):
|
||||||
fieldsets = [
|
fieldsets = [
|
||||||
(_('Source'), ('name', 'type', 'source_url', 'enabled', 'description', 'tags', 'ignore_rules')),
|
FieldSet('name', 'type', 'source_url', 'enabled', 'description', 'tags', 'ignore_rules', name=_('Source')),
|
||||||
]
|
]
|
||||||
if self.backend_fields:
|
if self.backend_fields:
|
||||||
fieldsets.append(
|
fieldsets.append(
|
||||||
(_('Backend Parameters'), self.backend_fields)
|
FieldSet(*self.backend_fields, name=_('Backend Parameters'))
|
||||||
)
|
)
|
||||||
|
|
||||||
return fieldsets
|
return fieldsets
|
||||||
@ -91,8 +92,8 @@ class ManagedFileForm(SyncedDataMixin, NetBoxModelForm):
|
|||||||
)
|
)
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('File Upload'), ('upload_file',)),
|
FieldSet('upload_file', name=_('File Upload')),
|
||||||
(_('Data Source'), ('data_source', 'data_file', 'auto_sync_enabled')),
|
FieldSet('data_source', 'data_file', 'auto_sync_enabled', name=_('Data Source')),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -144,18 +145,24 @@ class ConfigRevisionForm(forms.ModelForm, metaclass=ConfigFormMetaclass):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Rack Elevations'), ('RACK_ELEVATION_DEFAULT_UNIT_HEIGHT', 'RACK_ELEVATION_DEFAULT_UNIT_WIDTH')),
|
FieldSet(
|
||||||
(_('Power'), ('POWERFEED_DEFAULT_VOLTAGE', 'POWERFEED_DEFAULT_AMPERAGE', 'POWERFEED_DEFAULT_MAX_UTILIZATION')),
|
'RACK_ELEVATION_DEFAULT_UNIT_HEIGHT', 'RACK_ELEVATION_DEFAULT_UNIT_WIDTH', name=_('Rack Elevations')
|
||||||
(_('IPAM'), ('ENFORCE_GLOBAL_UNIQUE', 'PREFER_IPV4')),
|
),
|
||||||
(_('Security'), ('ALLOWED_URL_SCHEMES',)),
|
FieldSet(
|
||||||
(_('Banners'), ('BANNER_LOGIN', 'BANNER_MAINTENANCE', 'BANNER_TOP', 'BANNER_BOTTOM')),
|
'POWERFEED_DEFAULT_VOLTAGE', 'POWERFEED_DEFAULT_AMPERAGE', 'POWERFEED_DEFAULT_MAX_UTILIZATION',
|
||||||
(_('Pagination'), ('PAGINATE_COUNT', 'MAX_PAGE_SIZE')),
|
name=_('Power')
|
||||||
(_('Validation'), ('CUSTOM_VALIDATORS', 'PROTECTION_RULES')),
|
),
|
||||||
(_('User Preferences'), ('DEFAULT_USER_PREFERENCES',)),
|
FieldSet('ENFORCE_GLOBAL_UNIQUE', 'PREFER_IPV4', name=_('IPAM')),
|
||||||
(_('Miscellaneous'), (
|
FieldSet('ALLOWED_URL_SCHEMES', name=_('Security')),
|
||||||
|
FieldSet('BANNER_LOGIN', 'BANNER_MAINTENANCE', 'BANNER_TOP', 'BANNER_BOTTOM', name=_('Banners')),
|
||||||
|
FieldSet('PAGINATE_COUNT', 'MAX_PAGE_SIZE', name=_('Pagination')),
|
||||||
|
FieldSet('CUSTOM_VALIDATORS', 'PROTECTION_RULES', name=_('Validation')),
|
||||||
|
FieldSet('DEFAULT_USER_PREFERENCES', name=_('User Preferences')),
|
||||||
|
FieldSet(
|
||||||
'MAINTENANCE_MODE', 'GRAPHQL_ENABLED', 'CHANGELOG_RETENTION', 'JOB_RETENTION', 'MAPS_URL',
|
'MAINTENANCE_MODE', 'GRAPHQL_ENABLED', 'CHANGELOG_RETENTION', 'JOB_RETENTION', 'MAPS_URL',
|
||||||
)),
|
name=_('Miscellaneous')
|
||||||
(_('Config Revision'), ('comment',))
|
),
|
||||||
|
FieldSet('comment', name=_('Config Revision'))
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -10,6 +10,7 @@ from ..models import *
|
|||||||
class DataSourceTestCase(TestCase, ChangeLoggedFilterSetTests):
|
class DataSourceTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = DataSource.objects.all()
|
queryset = DataSource.objects.all()
|
||||||
filterset = DataSourceFilterSet
|
filterset = DataSourceFilterSet
|
||||||
|
ignore_fields = ('ignore_rules', 'parameters')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -70,6 +71,7 @@ class DataSourceTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
class DataFileTestCase(TestCase, ChangeLoggedFilterSetTests):
|
class DataFileTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = DataFile.objects.all()
|
queryset = DataFile.objects.all()
|
||||||
filterset = DataFileFilterSet
|
filterset = DataFileFilterSet
|
||||||
|
ignore_fields = ('data',)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
|
@ -309,6 +309,14 @@ class ModuleNestedModuleBaySerializer(WritableNestedSerializer):
|
|||||||
fields = ['id', 'url', 'display', 'name']
|
fields = ['id', 'url', 'display', 'name']
|
||||||
|
|
||||||
|
|
||||||
|
class ModuleBayNestedModuleSerializer(WritableNestedSerializer):
|
||||||
|
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:module-detail')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = models.Module
|
||||||
|
fields = ['id', 'url', 'display', 'serial']
|
||||||
|
|
||||||
|
|
||||||
class NestedModuleSerializer(WritableNestedSerializer):
|
class NestedModuleSerializer(WritableNestedSerializer):
|
||||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:module-detail')
|
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:module-detail')
|
||||||
device = NestedDeviceSerializer(read_only=True)
|
device = NestedDeviceSerializer(read_only=True)
|
||||||
@ -392,11 +400,11 @@ class NestedFrontPortSerializer(WritableNestedSerializer):
|
|||||||
|
|
||||||
class NestedModuleBaySerializer(WritableNestedSerializer):
|
class NestedModuleBaySerializer(WritableNestedSerializer):
|
||||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:modulebay-detail')
|
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:modulebay-detail')
|
||||||
module = NestedModuleSerializer(required=False, read_only=True, allow_null=True)
|
installed_module = ModuleBayNestedModuleSerializer(required=False, allow_null=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.ModuleBay
|
model = models.ModuleBay
|
||||||
fields = ['id', 'url', 'display', 'module', 'name']
|
fields = ['id', 'url', 'display', 'installed_module', 'name']
|
||||||
|
|
||||||
|
|
||||||
class NestedDeviceBaySerializer(WritableNestedSerializer):
|
class NestedDeviceBaySerializer(WritableNestedSerializer):
|
||||||
|
@ -28,8 +28,8 @@ class DeviceTypeSerializer(NetBoxModelSerializer):
|
|||||||
subdevice_role = ChoiceField(choices=SubdeviceRoleChoices, allow_blank=True, required=False, allow_null=True)
|
subdevice_role = ChoiceField(choices=SubdeviceRoleChoices, allow_blank=True, required=False, allow_null=True)
|
||||||
airflow = ChoiceField(choices=DeviceAirflowChoices, allow_blank=True, required=False, allow_null=True)
|
airflow = ChoiceField(choices=DeviceAirflowChoices, allow_blank=True, required=False, allow_null=True)
|
||||||
weight_unit = ChoiceField(choices=WeightUnitChoices, allow_blank=True, required=False, allow_null=True)
|
weight_unit = ChoiceField(choices=WeightUnitChoices, allow_blank=True, required=False, allow_null=True)
|
||||||
front_image = serializers.URLField(allow_null=True, required=False)
|
front_image = serializers.ImageField(required=False, allow_null=True)
|
||||||
rear_image = serializers.URLField(allow_null=True, required=False)
|
rear_image = serializers.ImageField(required=False, allow_null=True)
|
||||||
|
|
||||||
# Counter fields
|
# Counter fields
|
||||||
console_port_template_count = serializers.IntegerField(read_only=True)
|
console_port_template_count = serializers.IntegerField(read_only=True)
|
||||||
|
@ -92,7 +92,7 @@ class LocationSerializer(NestedGroupModelSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Location
|
model = Location
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'url', 'display', 'name', 'slug', 'site', 'parent', 'status', 'tenant', 'description', 'tags',
|
'id', 'url', 'display', 'name', 'slug', 'site', 'parent', 'status', 'tenant', 'facility', 'description',
|
||||||
'custom_fields', 'created', 'last_updated', 'rack_count', 'device_count', '_depth',
|
'tags', 'custom_fields', 'created', 'last_updated', 'rack_count', 'device_count', '_depth',
|
||||||
]
|
]
|
||||||
brief_fields = ('id', 'url', 'display', 'name', 'slug', 'description', 'rack_count', '_depth')
|
brief_fields = ('id', 'url', 'display', 'name', 'slug', 'description', 'rack_count', '_depth')
|
||||||
|
@ -12,6 +12,7 @@ __all__ = (
|
|||||||
class VirtualChassisSerializer(NetBoxModelSerializer):
|
class VirtualChassisSerializer(NetBoxModelSerializer):
|
||||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:virtualchassis-detail')
|
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:virtualchassis-detail')
|
||||||
master = NestedDeviceSerializer(required=False, allow_null=True, default=None)
|
master = NestedDeviceSerializer(required=False, allow_null=True, default=None)
|
||||||
|
members = NestedDeviceSerializer(many=True, read_only=True)
|
||||||
|
|
||||||
# Counter fields
|
# Counter fields
|
||||||
member_count = serializers.IntegerField(read_only=True)
|
member_count = serializers.IntegerField(read_only=True)
|
||||||
@ -20,6 +21,6 @@ class VirtualChassisSerializer(NetBoxModelSerializer):
|
|||||||
model = VirtualChassis
|
model = VirtualChassis
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'url', 'display', 'name', 'domain', 'master', 'description', 'comments', 'tags', 'custom_fields',
|
'id', 'url', 'display', 'name', 'domain', 'master', 'description', 'comments', 'tags', 'custom_fields',
|
||||||
'created', 'last_updated', 'member_count',
|
'created', 'last_updated', 'member_count', 'members',
|
||||||
]
|
]
|
||||||
brief_fields = ('id', 'url', 'display', 'name', 'master', 'description', 'member_count')
|
brief_fields = ('id', 'url', 'display', 'name', 'master', 'description', 'member_count')
|
||||||
|
@ -511,7 +511,10 @@ class CableTerminationViewSet(NetBoxModelViewSet):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class VirtualChassisViewSet(NetBoxModelViewSet):
|
class VirtualChassisViewSet(NetBoxModelViewSet):
|
||||||
queryset = VirtualChassis.objects.all()
|
queryset = VirtualChassis.objects.prefetch_related(
|
||||||
|
# Prefetch related object for the display of unnamed devices
|
||||||
|
'master__virtual_chassis',
|
||||||
|
)
|
||||||
serializer_class = serializers.VirtualChassisSerializer
|
serializer_class = serializers.VirtualChassisSerializer
|
||||||
filterset_class = filtersets.VirtualChassisFilterSet
|
filterset_class = filtersets.VirtualChassisFilterSet
|
||||||
|
|
||||||
|
@ -889,7 +889,10 @@ class InterfaceTypeChoices(ChoiceSet):
|
|||||||
TYPE_8GFC_SFP_PLUS = '8gfc-sfpp'
|
TYPE_8GFC_SFP_PLUS = '8gfc-sfpp'
|
||||||
TYPE_16GFC_SFP_PLUS = '16gfc-sfpp'
|
TYPE_16GFC_SFP_PLUS = '16gfc-sfpp'
|
||||||
TYPE_32GFC_SFP28 = '32gfc-sfp28'
|
TYPE_32GFC_SFP28 = '32gfc-sfp28'
|
||||||
|
TYPE_32GFC_SFP_PLUS = '32gfc-sfpp'
|
||||||
TYPE_64GFC_QSFP_PLUS = '64gfc-qsfpp'
|
TYPE_64GFC_QSFP_PLUS = '64gfc-qsfpp'
|
||||||
|
TYPE_64GFC_SFP_DD = '64gfc-sfpdd'
|
||||||
|
TYPE_64GFC_SFP_PLUS = '64gfc-sfpp'
|
||||||
TYPE_128GFC_QSFP28 = '128gfc-qsfp28'
|
TYPE_128GFC_QSFP28 = '128gfc-qsfp28'
|
||||||
|
|
||||||
# InfiniBand
|
# InfiniBand
|
||||||
@ -1058,7 +1061,10 @@ class InterfaceTypeChoices(ChoiceSet):
|
|||||||
(TYPE_8GFC_SFP_PLUS, 'SFP+ (8GFC)'),
|
(TYPE_8GFC_SFP_PLUS, 'SFP+ (8GFC)'),
|
||||||
(TYPE_16GFC_SFP_PLUS, 'SFP+ (16GFC)'),
|
(TYPE_16GFC_SFP_PLUS, 'SFP+ (16GFC)'),
|
||||||
(TYPE_32GFC_SFP28, 'SFP28 (32GFC)'),
|
(TYPE_32GFC_SFP28, 'SFP28 (32GFC)'),
|
||||||
|
(TYPE_32GFC_SFP_PLUS, 'SFP+ (32GFC)'),
|
||||||
(TYPE_64GFC_QSFP_PLUS, 'QSFP+ (64GFC)'),
|
(TYPE_64GFC_QSFP_PLUS, 'QSFP+ (64GFC)'),
|
||||||
|
(TYPE_64GFC_SFP_DD, 'SFP-DD (64GFC)'),
|
||||||
|
(TYPE_64GFC_SFP_PLUS, 'SFP+ (64GFC)'),
|
||||||
(TYPE_128GFC_QSFP28, 'QSFP28 (128GFC)'),
|
(TYPE_128GFC_QSFP28, 'QSFP28 (128GFC)'),
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
@ -18,11 +18,12 @@ from tenancy.models import *
|
|||||||
from utilities.choices import ColorChoices
|
from utilities.choices import ColorChoices
|
||||||
from utilities.filters import (
|
from utilities.filters import (
|
||||||
ContentTypeFilter, MultiValueCharFilter, MultiValueMACAddressFilter, MultiValueNumberFilter, MultiValueWWNFilter,
|
ContentTypeFilter, MultiValueCharFilter, MultiValueMACAddressFilter, MultiValueNumberFilter, MultiValueWWNFilter,
|
||||||
TreeNodeMultipleChoiceFilter,
|
NumericArrayFilter, TreeNodeMultipleChoiceFilter,
|
||||||
)
|
)
|
||||||
from virtualization.models import Cluster
|
from virtualization.models import Cluster
|
||||||
from vpn.models import L2VPN
|
from vpn.models import L2VPN
|
||||||
from wireless.choices import WirelessRoleChoices, WirelessChannelChoices
|
from wireless.choices import WirelessRoleChoices, WirelessChannelChoices
|
||||||
|
from wireless.models import WirelessLAN, WirelessLink
|
||||||
from .choices import *
|
from .choices import *
|
||||||
from .constants import *
|
from .constants import *
|
||||||
from .models import *
|
from .models import *
|
||||||
@ -89,10 +90,23 @@ class RegionFilterSet(OrganizationalModelFilterSet, ContactModelFilterSet):
|
|||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label=_('Parent region (slug)'),
|
label=_('Parent region (slug)'),
|
||||||
)
|
)
|
||||||
|
ancestor_id = TreeNodeMultipleChoiceFilter(
|
||||||
|
queryset=Region.objects.all(),
|
||||||
|
field_name='parent',
|
||||||
|
lookup_expr='in',
|
||||||
|
label=_('Region (ID)'),
|
||||||
|
)
|
||||||
|
ancestor = TreeNodeMultipleChoiceFilter(
|
||||||
|
queryset=Region.objects.all(),
|
||||||
|
field_name='parent',
|
||||||
|
lookup_expr='in',
|
||||||
|
to_field_name='slug',
|
||||||
|
label=_('Region (slug)'),
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Region
|
model = Region
|
||||||
fields = ['id', 'name', 'slug', 'description']
|
fields = ('id', 'name', 'slug', 'description')
|
||||||
|
|
||||||
|
|
||||||
class SiteGroupFilterSet(OrganizationalModelFilterSet, ContactModelFilterSet):
|
class SiteGroupFilterSet(OrganizationalModelFilterSet, ContactModelFilterSet):
|
||||||
@ -106,10 +120,23 @@ class SiteGroupFilterSet(OrganizationalModelFilterSet, ContactModelFilterSet):
|
|||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label=_('Parent site group (slug)'),
|
label=_('Parent site group (slug)'),
|
||||||
)
|
)
|
||||||
|
ancestor_id = TreeNodeMultipleChoiceFilter(
|
||||||
|
queryset=SiteGroup.objects.all(),
|
||||||
|
field_name='parent',
|
||||||
|
lookup_expr='in',
|
||||||
|
label=_('Site group (ID)'),
|
||||||
|
)
|
||||||
|
ancestor = TreeNodeMultipleChoiceFilter(
|
||||||
|
queryset=SiteGroup.objects.all(),
|
||||||
|
field_name='parent',
|
||||||
|
lookup_expr='in',
|
||||||
|
to_field_name='slug',
|
||||||
|
label=_('Site group (slug)'),
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = SiteGroup
|
model = SiteGroup
|
||||||
fields = ['id', 'name', 'slug', 'description']
|
fields = ('id', 'name', 'slug', 'description')
|
||||||
|
|
||||||
|
|
||||||
class SiteFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilterSet):
|
class SiteFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilterSet):
|
||||||
@ -152,12 +179,11 @@ class SiteFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilterSe
|
|||||||
queryset=ASN.objects.all(),
|
queryset=ASN.objects.all(),
|
||||||
label=_('AS (ID)'),
|
label=_('AS (ID)'),
|
||||||
)
|
)
|
||||||
|
time_zone = MultiValueCharFilter()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Site
|
model = Site
|
||||||
fields = (
|
fields = ('id', 'name', 'slug', 'facility', 'latitude', 'longitude', 'description')
|
||||||
'id', 'name', 'slug', 'facility', 'latitude', 'longitude', 'description'
|
|
||||||
)
|
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
@ -214,13 +240,23 @@ class LocationFilterSet(TenancyFilterSet, ContactModelFilterSet, OrganizationalM
|
|||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label=_('Site (slug)'),
|
label=_('Site (slug)'),
|
||||||
)
|
)
|
||||||
parent_id = TreeNodeMultipleChoiceFilter(
|
parent_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
queryset=Location.objects.all(),
|
||||||
|
label=_('Parent location (ID)'),
|
||||||
|
)
|
||||||
|
parent = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='parent__slug',
|
||||||
|
queryset=Location.objects.all(),
|
||||||
|
to_field_name='slug',
|
||||||
|
label=_('Parent location (slug)'),
|
||||||
|
)
|
||||||
|
ancestor_id = TreeNodeMultipleChoiceFilter(
|
||||||
queryset=Location.objects.all(),
|
queryset=Location.objects.all(),
|
||||||
field_name='parent',
|
field_name='parent',
|
||||||
lookup_expr='in',
|
lookup_expr='in',
|
||||||
label=_('Location (ID)'),
|
label=_('Location (ID)'),
|
||||||
)
|
)
|
||||||
parent = TreeNodeMultipleChoiceFilter(
|
ancestor = TreeNodeMultipleChoiceFilter(
|
||||||
queryset=Location.objects.all(),
|
queryset=Location.objects.all(),
|
||||||
field_name='parent',
|
field_name='parent',
|
||||||
lookup_expr='in',
|
lookup_expr='in',
|
||||||
@ -234,13 +270,14 @@ class LocationFilterSet(TenancyFilterSet, ContactModelFilterSet, OrganizationalM
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Location
|
model = Location
|
||||||
fields = ['id', 'name', 'slug', 'status', 'description']
|
fields = ('id', 'name', 'slug', 'status', 'facility', 'description')
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
return queryset
|
return queryset
|
||||||
return queryset.filter(
|
return queryset.filter(
|
||||||
Q(name__icontains=value) |
|
Q(name__icontains=value) |
|
||||||
|
Q(facility__icontains=value) |
|
||||||
Q(description__icontains=value)
|
Q(description__icontains=value)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -249,7 +286,7 @@ class RackRoleFilterSet(OrganizationalModelFilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = RackRole
|
model = RackRole
|
||||||
fields = ['id', 'name', 'slug', 'color', 'description']
|
fields = ('id', 'name', 'slug', 'color', 'description')
|
||||||
|
|
||||||
|
|
||||||
class RackFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilterSet):
|
class RackFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilterSet):
|
||||||
@ -328,10 +365,10 @@ class RackFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilterSe
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Rack
|
model = Rack
|
||||||
fields = [
|
fields = (
|
||||||
'id', 'name', 'facility_id', 'asset_tag', 'u_height', 'starting_unit', 'desc_units', 'outer_width',
|
'id', 'name', 'facility_id', 'asset_tag', 'u_height', 'starting_unit', 'desc_units', 'outer_width',
|
||||||
'outer_depth', 'outer_unit', 'mounting_depth', 'weight', 'max_weight', 'weight_unit', 'description',
|
'outer_depth', 'outer_unit', 'mounting_depth', 'weight', 'max_weight', 'weight_unit', 'description',
|
||||||
]
|
)
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
@ -411,10 +448,14 @@ class RackReservationFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
|
|||||||
to_field_name='username',
|
to_field_name='username',
|
||||||
label=_('User (name)'),
|
label=_('User (name)'),
|
||||||
)
|
)
|
||||||
|
unit = NumericArrayFilter(
|
||||||
|
field_name='units',
|
||||||
|
lookup_expr='contains'
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = RackReservation
|
model = RackReservation
|
||||||
fields = ['id', 'created', 'description']
|
fields = ('id', 'created', 'description')
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
@ -431,7 +472,7 @@ class ManufacturerFilterSet(OrganizationalModelFilterSet, ContactModelFilterSet)
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Manufacturer
|
model = Manufacturer
|
||||||
fields = ['id', 'name', 'slug', 'description']
|
fields = ('id', 'name', 'slug', 'description')
|
||||||
|
|
||||||
|
|
||||||
class DeviceTypeFilterSet(NetBoxModelFilterSet):
|
class DeviceTypeFilterSet(NetBoxModelFilterSet):
|
||||||
@ -502,10 +543,22 @@ class DeviceTypeFilterSet(NetBoxModelFilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = DeviceType
|
model = DeviceType
|
||||||
fields = [
|
fields = (
|
||||||
'id', 'model', 'slug', 'part_number', 'u_height', 'exclude_from_utilization', 'is_full_depth',
|
'id', 'model', 'slug', 'part_number', 'u_height', 'exclude_from_utilization', 'is_full_depth',
|
||||||
'subdevice_role', 'airflow', 'weight', 'weight_unit', 'description',
|
'subdevice_role', 'airflow', 'weight', 'weight_unit', 'description',
|
||||||
]
|
|
||||||
|
# Counters
|
||||||
|
'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',
|
||||||
|
'device_bay_template_count',
|
||||||
|
'module_bay_template_count',
|
||||||
|
'inventory_item_template_count',
|
||||||
|
)
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
@ -599,7 +652,7 @@ class ModuleTypeFilterSet(NetBoxModelFilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ModuleType
|
model = ModuleType
|
||||||
fields = ['id', 'model', 'part_number', 'weight', 'weight_unit', 'description']
|
fields = ('id', 'model', 'part_number', 'weight', 'weight_unit', 'description')
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
@ -639,12 +692,15 @@ class DeviceTypeComponentFilterSet(django_filters.FilterSet):
|
|||||||
method='search',
|
method='search',
|
||||||
label=_('Search'),
|
label=_('Search'),
|
||||||
)
|
)
|
||||||
devicetype_id = django_filters.ModelMultipleChoiceFilter(
|
device_type_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
queryset=DeviceType.objects.all(),
|
queryset=DeviceType.objects.all(),
|
||||||
field_name='device_type_id',
|
field_name='device_type_id',
|
||||||
label=_('Device type (ID)'),
|
label=_('Device type (ID)'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# TODO: Remove in v4.1
|
||||||
|
devicetype_id = device_type_id
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
return queryset
|
return queryset
|
||||||
@ -655,32 +711,35 @@ class DeviceTypeComponentFilterSet(django_filters.FilterSet):
|
|||||||
|
|
||||||
|
|
||||||
class ModularDeviceTypeComponentFilterSet(DeviceTypeComponentFilterSet):
|
class ModularDeviceTypeComponentFilterSet(DeviceTypeComponentFilterSet):
|
||||||
moduletype_id = django_filters.ModelMultipleChoiceFilter(
|
module_type_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
queryset=ModuleType.objects.all(),
|
queryset=ModuleType.objects.all(),
|
||||||
field_name='module_type_id',
|
field_name='module_type_id',
|
||||||
label=_('Module type (ID)'),
|
label=_('Module type (ID)'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# TODO: Remove in v4.1
|
||||||
|
moduletype_id = module_type_id
|
||||||
|
|
||||||
|
|
||||||
class ConsolePortTemplateFilterSet(ChangeLoggedModelFilterSet, ModularDeviceTypeComponentFilterSet):
|
class ConsolePortTemplateFilterSet(ChangeLoggedModelFilterSet, ModularDeviceTypeComponentFilterSet):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ConsolePortTemplate
|
model = ConsolePortTemplate
|
||||||
fields = ['id', 'name', 'type', 'description']
|
fields = ('id', 'name', 'label', 'type', 'description')
|
||||||
|
|
||||||
|
|
||||||
class ConsoleServerPortTemplateFilterSet(ChangeLoggedModelFilterSet, ModularDeviceTypeComponentFilterSet):
|
class ConsoleServerPortTemplateFilterSet(ChangeLoggedModelFilterSet, ModularDeviceTypeComponentFilterSet):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ConsoleServerPortTemplate
|
model = ConsoleServerPortTemplate
|
||||||
fields = ['id', 'name', 'type', 'description']
|
fields = ('id', 'name', 'label', 'type', 'description')
|
||||||
|
|
||||||
|
|
||||||
class PowerPortTemplateFilterSet(ChangeLoggedModelFilterSet, ModularDeviceTypeComponentFilterSet):
|
class PowerPortTemplateFilterSet(ChangeLoggedModelFilterSet, ModularDeviceTypeComponentFilterSet):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = PowerPortTemplate
|
model = PowerPortTemplate
|
||||||
fields = ['id', 'name', 'type', 'maximum_draw', 'allocated_draw', 'description']
|
fields = ('id', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description')
|
||||||
|
|
||||||
|
|
||||||
class PowerOutletTemplateFilterSet(ChangeLoggedModelFilterSet, ModularDeviceTypeComponentFilterSet):
|
class PowerOutletTemplateFilterSet(ChangeLoggedModelFilterSet, ModularDeviceTypeComponentFilterSet):
|
||||||
@ -688,10 +747,14 @@ class PowerOutletTemplateFilterSet(ChangeLoggedModelFilterSet, ModularDeviceType
|
|||||||
choices=PowerOutletFeedLegChoices,
|
choices=PowerOutletFeedLegChoices,
|
||||||
null_value=None
|
null_value=None
|
||||||
)
|
)
|
||||||
|
power_port_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
queryset=PowerPortTemplate.objects.all(),
|
||||||
|
label=_('Power port (ID)'),
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = PowerOutletTemplate
|
model = PowerOutletTemplate
|
||||||
fields = ['id', 'name', 'type', 'feed_leg', 'description']
|
fields = ('id', 'name', 'label', 'type', 'feed_leg', 'description')
|
||||||
|
|
||||||
|
|
||||||
class InterfaceTemplateFilterSet(ChangeLoggedModelFilterSet, ModularDeviceTypeComponentFilterSet):
|
class InterfaceTemplateFilterSet(ChangeLoggedModelFilterSet, ModularDeviceTypeComponentFilterSet):
|
||||||
@ -715,7 +778,7 @@ class InterfaceTemplateFilterSet(ChangeLoggedModelFilterSet, ModularDeviceTypeCo
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = InterfaceTemplate
|
model = InterfaceTemplate
|
||||||
fields = ['id', 'name', 'type', 'enabled', 'mgmt_only', 'description']
|
fields = ('id', 'name', 'label', 'type', 'enabled', 'mgmt_only', 'description')
|
||||||
|
|
||||||
|
|
||||||
class FrontPortTemplateFilterSet(ChangeLoggedModelFilterSet, ModularDeviceTypeComponentFilterSet):
|
class FrontPortTemplateFilterSet(ChangeLoggedModelFilterSet, ModularDeviceTypeComponentFilterSet):
|
||||||
@ -723,10 +786,13 @@ class FrontPortTemplateFilterSet(ChangeLoggedModelFilterSet, ModularDeviceTypeCo
|
|||||||
choices=PortTypeChoices,
|
choices=PortTypeChoices,
|
||||||
null_value=None
|
null_value=None
|
||||||
)
|
)
|
||||||
|
rear_port_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
queryset=RearPort.objects.all()
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = FrontPortTemplate
|
model = FrontPortTemplate
|
||||||
fields = ['id', 'name', 'type', 'color', 'description']
|
fields = ('id', 'name', 'label', 'type', 'color', 'rear_port_position', 'description')
|
||||||
|
|
||||||
|
|
||||||
class RearPortTemplateFilterSet(ChangeLoggedModelFilterSet, ModularDeviceTypeComponentFilterSet):
|
class RearPortTemplateFilterSet(ChangeLoggedModelFilterSet, ModularDeviceTypeComponentFilterSet):
|
||||||
@ -737,21 +803,21 @@ class RearPortTemplateFilterSet(ChangeLoggedModelFilterSet, ModularDeviceTypeCom
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = RearPortTemplate
|
model = RearPortTemplate
|
||||||
fields = ['id', 'name', 'type', 'color', 'positions', 'description']
|
fields = ('id', 'name', 'label', 'type', 'color', 'positions', 'description')
|
||||||
|
|
||||||
|
|
||||||
class ModuleBayTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet):
|
class ModuleBayTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ModuleBayTemplate
|
model = ModuleBayTemplate
|
||||||
fields = ['id', 'name', 'description']
|
fields = ('id', 'name', 'label', 'position', 'description')
|
||||||
|
|
||||||
|
|
||||||
class DeviceBayTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet):
|
class DeviceBayTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = DeviceBayTemplate
|
model = DeviceBayTemplate
|
||||||
fields = ['id', 'name', 'description']
|
fields = ('id', 'name', 'label', 'description')
|
||||||
|
|
||||||
|
|
||||||
class InventoryItemTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet):
|
class InventoryItemTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet):
|
||||||
@ -784,7 +850,7 @@ class InventoryItemTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeCompo
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = InventoryItemTemplate
|
model = InventoryItemTemplate
|
||||||
fields = ['id', 'name', 'label', 'part_id', 'description']
|
fields = ('id', 'name', 'label', 'part_id', 'description')
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
@ -805,7 +871,7 @@ class DeviceRoleFilterSet(OrganizationalModelFilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = DeviceRole
|
model = DeviceRole
|
||||||
fields = ['id', 'name', 'slug', 'color', 'vm_role', 'description']
|
fields = ('id', 'name', 'slug', 'color', 'vm_role', 'description')
|
||||||
|
|
||||||
|
|
||||||
class PlatformFilterSet(OrganizationalModelFilterSet):
|
class PlatformFilterSet(OrganizationalModelFilterSet):
|
||||||
@ -831,7 +897,7 @@ class PlatformFilterSet(OrganizationalModelFilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Platform
|
model = Platform
|
||||||
fields = ['id', 'name', 'slug', 'description']
|
fields = ('id', 'name', 'slug', 'description')
|
||||||
|
|
||||||
@extend_schema_field(OpenApiTypes.STR)
|
@extend_schema_field(OpenApiTypes.STR)
|
||||||
def get_for_device_type(self, queryset, name, value):
|
def get_for_device_type(self, queryset, name, value):
|
||||||
@ -943,6 +1009,11 @@ class DeviceFilterSet(
|
|||||||
queryset=Rack.objects.all(),
|
queryset=Rack.objects.all(),
|
||||||
label=_('Rack (ID)'),
|
label=_('Rack (ID)'),
|
||||||
)
|
)
|
||||||
|
parent_bay_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='parent_bay',
|
||||||
|
queryset=DeviceBay.objects.all(),
|
||||||
|
label=_('Parent bay (ID)'),
|
||||||
|
)
|
||||||
cluster_id = django_filters.ModelMultipleChoiceFilter(
|
cluster_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
queryset=Cluster.objects.all(),
|
queryset=Cluster.objects.all(),
|
||||||
label=_('VM cluster (ID)'),
|
label=_('VM cluster (ID)'),
|
||||||
@ -1032,10 +1103,22 @@ class DeviceFilterSet(
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Device
|
model = Device
|
||||||
fields = [
|
fields = (
|
||||||
'id', 'asset_tag', 'face', 'position', 'latitude', 'longitude', 'airflow', 'vc_position', 'vc_priority',
|
'id', 'asset_tag', 'face', 'position', 'latitude', 'longitude', 'airflow', 'vc_position', 'vc_priority',
|
||||||
'description',
|
'description',
|
||||||
]
|
|
||||||
|
# Counters
|
||||||
|
'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',
|
||||||
|
)
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
@ -1098,24 +1181,29 @@ class VirtualDeviceContextFilterSet(NetBoxModelFilterSet, TenancyFilterSet, Prim
|
|||||||
device_id = django_filters.ModelMultipleChoiceFilter(
|
device_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
field_name='device',
|
field_name='device',
|
||||||
queryset=Device.objects.all(),
|
queryset=Device.objects.all(),
|
||||||
label='VDC (ID)',
|
label=_('VDC (ID)')
|
||||||
)
|
)
|
||||||
device = django_filters.ModelMultipleChoiceFilter(
|
device = django_filters.ModelMultipleChoiceFilter(
|
||||||
field_name='device',
|
field_name='device',
|
||||||
queryset=Device.objects.all(),
|
queryset=Device.objects.all(),
|
||||||
label='Device model',
|
label=_('Device model')
|
||||||
|
)
|
||||||
|
interface_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='interfaces',
|
||||||
|
queryset=Interface.objects.all(),
|
||||||
|
label=_('Interface (ID)')
|
||||||
)
|
)
|
||||||
status = django_filters.MultipleChoiceFilter(
|
status = django_filters.MultipleChoiceFilter(
|
||||||
choices=VirtualDeviceContextStatusChoices
|
choices=VirtualDeviceContextStatusChoices
|
||||||
)
|
)
|
||||||
has_primary_ip = django_filters.BooleanFilter(
|
has_primary_ip = django_filters.BooleanFilter(
|
||||||
method='_has_primary_ip',
|
method='_has_primary_ip',
|
||||||
label='Has a primary IP',
|
label=_('Has a primary IP')
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = VirtualDeviceContext
|
model = VirtualDeviceContext
|
||||||
fields = ['id', 'device', 'name', 'description']
|
fields = ('id', 'device', 'name', 'identifier', 'description')
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
@ -1181,7 +1269,7 @@ class ModuleFilterSet(NetBoxModelFilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Module
|
model = Module
|
||||||
fields = ['id', 'status', 'asset_tag', 'description']
|
fields = ('id', 'status', 'asset_tag', 'description')
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
@ -1325,6 +1413,10 @@ class ModularDeviceComponentFilterSet(DeviceComponentFilterSet):
|
|||||||
|
|
||||||
|
|
||||||
class CabledObjectFilterSet(django_filters.FilterSet):
|
class CabledObjectFilterSet(django_filters.FilterSet):
|
||||||
|
cable_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
queryset=Cable.objects.all(),
|
||||||
|
label=_('Cable (ID)'),
|
||||||
|
)
|
||||||
cabled = django_filters.BooleanFilter(
|
cabled = django_filters.BooleanFilter(
|
||||||
field_name='cable',
|
field_name='cable',
|
||||||
lookup_expr='isnull',
|
lookup_expr='isnull',
|
||||||
@ -1366,7 +1458,7 @@ class ConsolePortFilterSet(
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ConsolePort
|
model = ConsolePort
|
||||||
fields = ['id', 'name', 'label', 'description', 'cable_end']
|
fields = ('id', 'name', 'label', 'speed', 'description', 'mark_connected', 'cable_end')
|
||||||
|
|
||||||
|
|
||||||
class ConsoleServerPortFilterSet(
|
class ConsoleServerPortFilterSet(
|
||||||
@ -1382,7 +1474,7 @@ class ConsoleServerPortFilterSet(
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ConsoleServerPort
|
model = ConsoleServerPort
|
||||||
fields = ['id', 'name', 'label', 'description', 'cable_end']
|
fields = ('id', 'name', 'label', 'speed', 'description', 'mark_connected', 'cable_end')
|
||||||
|
|
||||||
|
|
||||||
class PowerPortFilterSet(
|
class PowerPortFilterSet(
|
||||||
@ -1398,7 +1490,9 @@ class PowerPortFilterSet(
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = PowerPort
|
model = PowerPort
|
||||||
fields = ['id', 'name', 'label', 'maximum_draw', 'allocated_draw', 'description', 'cable_end']
|
fields = (
|
||||||
|
'id', 'name', 'label', 'maximum_draw', 'allocated_draw', 'description', 'mark_connected', 'cable_end',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class PowerOutletFilterSet(
|
class PowerOutletFilterSet(
|
||||||
@ -1415,10 +1509,16 @@ class PowerOutletFilterSet(
|
|||||||
choices=PowerOutletFeedLegChoices,
|
choices=PowerOutletFeedLegChoices,
|
||||||
null_value=None
|
null_value=None
|
||||||
)
|
)
|
||||||
|
power_port_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
queryset=PowerPort.objects.all(),
|
||||||
|
label=_('Power port (ID)'),
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = PowerOutlet
|
model = PowerOutlet
|
||||||
fields = ['id', 'name', 'label', 'feed_leg', 'description', 'cable_end']
|
fields = (
|
||||||
|
'id', 'name', 'label', 'feed_leg', 'description', 'mark_connected', 'cable_end',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class CommonInterfaceFilterSet(django_filters.FilterSet):
|
class CommonInterfaceFilterSet(django_filters.FilterSet):
|
||||||
@ -1533,27 +1633,37 @@ class InterfaceFilterSet(
|
|||||||
vdc_id = django_filters.ModelMultipleChoiceFilter(
|
vdc_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
field_name='vdcs',
|
field_name='vdcs',
|
||||||
queryset=VirtualDeviceContext.objects.all(),
|
queryset=VirtualDeviceContext.objects.all(),
|
||||||
label='Virtual Device Context',
|
label=_('Virtual Device Context')
|
||||||
)
|
)
|
||||||
vdc_identifier = django_filters.ModelMultipleChoiceFilter(
|
vdc_identifier = django_filters.ModelMultipleChoiceFilter(
|
||||||
field_name='vdcs__identifier',
|
field_name='vdcs__identifier',
|
||||||
queryset=VirtualDeviceContext.objects.all(),
|
queryset=VirtualDeviceContext.objects.all(),
|
||||||
to_field_name='identifier',
|
to_field_name='identifier',
|
||||||
label='Virtual Device Context (Identifier)',
|
label=_('Virtual Device Context (Identifier)')
|
||||||
)
|
)
|
||||||
vdc = django_filters.ModelMultipleChoiceFilter(
|
vdc = django_filters.ModelMultipleChoiceFilter(
|
||||||
field_name='vdcs__name',
|
field_name='vdcs__name',
|
||||||
queryset=VirtualDeviceContext.objects.all(),
|
queryset=VirtualDeviceContext.objects.all(),
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
label='Virtual Device Context',
|
label=_('Virtual Device Context')
|
||||||
|
)
|
||||||
|
wireless_lan_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='wireless_lans',
|
||||||
|
queryset=WirelessLAN.objects.all(),
|
||||||
|
label=_('Wireless LAN')
|
||||||
|
)
|
||||||
|
wireless_link_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
queryset=WirelessLink.objects.all(),
|
||||||
|
label=_('Wireless link')
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Interface
|
model = Interface
|
||||||
fields = [
|
fields = (
|
||||||
'id', 'name', 'label', 'type', 'enabled', 'mtu', 'mgmt_only', 'poe_mode', 'poe_type', 'mode', 'rf_role',
|
'id', 'name', 'label', 'type', 'enabled', 'mtu', 'mgmt_only', 'poe_mode', 'poe_type', 'mode', 'rf_role',
|
||||||
'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'description', 'cable_end',
|
'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'description', 'mark_connected',
|
||||||
]
|
'cable_id', 'cable_end',
|
||||||
|
)
|
||||||
|
|
||||||
def filter_virtual_chassis_member(self, queryset, name, value):
|
def filter_virtual_chassis_member(self, queryset, name, value):
|
||||||
try:
|
try:
|
||||||
@ -1582,10 +1692,15 @@ class FrontPortFilterSet(
|
|||||||
choices=PortTypeChoices,
|
choices=PortTypeChoices,
|
||||||
null_value=None
|
null_value=None
|
||||||
)
|
)
|
||||||
|
rear_port_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
queryset=RearPort.objects.all()
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = FrontPort
|
model = FrontPort
|
||||||
fields = ['id', 'name', 'label', 'type', 'color', 'description', 'cable_end']
|
fields = (
|
||||||
|
'id', 'name', 'label', 'type', 'color', 'rear_port_position', 'description', 'mark_connected', 'cable_end',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class RearPortFilterSet(
|
class RearPortFilterSet(
|
||||||
@ -1600,21 +1715,38 @@ class RearPortFilterSet(
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = RearPort
|
model = RearPort
|
||||||
fields = ['id', 'name', 'label', 'type', 'color', 'positions', 'description', 'cable_end']
|
fields = (
|
||||||
|
'id', 'name', 'label', 'type', 'color', 'positions', 'description', 'mark_connected', 'cable_end',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ModuleBayFilterSet(DeviceComponentFilterSet, NetBoxModelFilterSet):
|
class ModuleBayFilterSet(DeviceComponentFilterSet, NetBoxModelFilterSet):
|
||||||
|
installed_module_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='installed_module',
|
||||||
|
queryset=ModuleBay.objects.all(),
|
||||||
|
label=_('Installed module (ID)'),
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ModuleBay
|
model = ModuleBay
|
||||||
fields = ['id', 'name', 'label', 'description']
|
fields = ('id', 'name', 'label', 'position', 'description')
|
||||||
|
|
||||||
|
|
||||||
class DeviceBayFilterSet(DeviceComponentFilterSet, NetBoxModelFilterSet):
|
class DeviceBayFilterSet(DeviceComponentFilterSet, NetBoxModelFilterSet):
|
||||||
|
installed_device_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
queryset=Device.objects.all(),
|
||||||
|
label=_('Installed device (ID)'),
|
||||||
|
)
|
||||||
|
installed_device = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='installed_device__name',
|
||||||
|
queryset=Device.objects.all(),
|
||||||
|
to_field_name='name',
|
||||||
|
label=_('Installed device (name)'),
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = DeviceBay
|
model = DeviceBay
|
||||||
fields = ['id', 'name', 'label', 'description']
|
fields = ('id', 'name', 'label', 'description')
|
||||||
|
|
||||||
|
|
||||||
class InventoryItemFilterSet(DeviceComponentFilterSet, NetBoxModelFilterSet):
|
class InventoryItemFilterSet(DeviceComponentFilterSet, NetBoxModelFilterSet):
|
||||||
@ -1650,7 +1782,7 @@ class InventoryItemFilterSet(DeviceComponentFilterSet, NetBoxModelFilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = InventoryItem
|
model = InventoryItem
|
||||||
fields = ['id', 'name', 'label', 'part_id', 'asset_tag', 'discovered']
|
fields = ('id', 'name', 'label', 'part_id', 'asset_tag', 'description', 'discovered')
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
@ -1669,7 +1801,7 @@ class InventoryItemRoleFilterSet(OrganizationalModelFilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = InventoryItemRole
|
model = InventoryItemRole
|
||||||
fields = ['id', 'name', 'slug', 'color', 'description']
|
fields = ('id', 'name', 'slug', 'color', 'description')
|
||||||
|
|
||||||
|
|
||||||
class VirtualChassisFilterSet(NetBoxModelFilterSet):
|
class VirtualChassisFilterSet(NetBoxModelFilterSet):
|
||||||
@ -1734,7 +1866,7 @@ class VirtualChassisFilterSet(NetBoxModelFilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = VirtualChassis
|
model = VirtualChassis
|
||||||
fields = ['id', 'domain', 'name', 'description']
|
fields = ('id', 'domain', 'name', 'description', 'member_count')
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
@ -1839,7 +1971,7 @@ class CableFilterSet(TenancyFilterSet, NetBoxModelFilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Cable
|
model = Cable
|
||||||
fields = ['id', 'label', 'length', 'length_unit', 'description']
|
fields = ('id', 'label', 'length', 'length_unit', 'description')
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
@ -1917,12 +2049,12 @@ class CableFilterSet(TenancyFilterSet, NetBoxModelFilterSet):
|
|||||||
return self.filter_by_termination_object(queryset, CircuitTermination, value)
|
return self.filter_by_termination_object(queryset, CircuitTermination, value)
|
||||||
|
|
||||||
|
|
||||||
class CableTerminationFilterSet(BaseFilterSet):
|
class CableTerminationFilterSet(ChangeLoggedModelFilterSet):
|
||||||
termination_type = ContentTypeFilter()
|
termination_type = ContentTypeFilter()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CableTermination
|
model = CableTermination
|
||||||
fields = ['id', 'cable', 'cable_end', 'termination_type', 'termination_id']
|
fields = ('id', 'cable', 'cable_end', 'termination_type', 'termination_id')
|
||||||
|
|
||||||
|
|
||||||
class PowerPanelFilterSet(NetBoxModelFilterSet, ContactModelFilterSet):
|
class PowerPanelFilterSet(NetBoxModelFilterSet, ContactModelFilterSet):
|
||||||
@ -1971,7 +2103,7 @@ class PowerPanelFilterSet(NetBoxModelFilterSet, ContactModelFilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = PowerPanel
|
model = PowerPanel
|
||||||
fields = ['id', 'name', 'description']
|
fields = ('id', 'name', 'description')
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
@ -2037,10 +2169,10 @@ class PowerFeedFilterSet(NetBoxModelFilterSet, CabledObjectFilterSet, PathEndpoi
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = PowerFeed
|
model = PowerFeed
|
||||||
fields = [
|
fields = (
|
||||||
'id', 'name', 'status', 'type', 'supply', 'phase', 'voltage', 'amperage', 'max_utilization', 'cable_end',
|
'id', 'name', 'status', 'type', 'supply', 'phase', 'voltage', 'amperage', 'max_utilization',
|
||||||
'description',
|
'available_power', 'mark_connected', 'cable_end', 'description',
|
||||||
]
|
)
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
@ -2099,18 +2231,18 @@ class ConsoleConnectionFilterSet(ConnectionFilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ConsolePort
|
model = ConsolePort
|
||||||
fields = ['name']
|
fields = ('name',)
|
||||||
|
|
||||||
|
|
||||||
class PowerConnectionFilterSet(ConnectionFilterSet):
|
class PowerConnectionFilterSet(ConnectionFilterSet):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = PowerPort
|
model = PowerPort
|
||||||
fields = ['name']
|
fields = ('name',)
|
||||||
|
|
||||||
|
|
||||||
class InterfaceConnectionFilterSet(ConnectionFilterSet):
|
class InterfaceConnectionFilterSet(ConnectionFilterSet):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Interface
|
model = Interface
|
||||||
fields = []
|
fields = tuple()
|
||||||
|
@ -13,6 +13,7 @@ from netbox.forms import NetBoxModelBulkEditForm
|
|||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
from utilities.forms import BulkEditForm, add_blank_choice, form_from_model
|
from utilities.forms import BulkEditForm, add_blank_choice, form_from_model
|
||||||
from utilities.forms.fields import ColorField, CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField
|
from utilities.forms.fields import ColorField, CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField
|
||||||
|
from utilities.forms.rendering import FieldSet
|
||||||
from utilities.forms.widgets import BulkEditNullBooleanSelect, NumberWithOptions
|
from utilities.forms.widgets import BulkEditNullBooleanSelect, NumberWithOptions
|
||||||
from wireless.models import WirelessLAN, WirelessLANGroup
|
from wireless.models import WirelessLAN, WirelessLANGroup
|
||||||
from wireless.choices import WirelessRoleChoices
|
from wireless.choices import WirelessRoleChoices
|
||||||
@ -75,7 +76,7 @@ class RegionBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
|
|
||||||
model = Region
|
model = Region
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('parent', 'description')),
|
FieldSet('parent', 'description'),
|
||||||
)
|
)
|
||||||
nullable_fields = ('parent', 'description')
|
nullable_fields = ('parent', 'description')
|
||||||
|
|
||||||
@ -94,7 +95,7 @@ class SiteGroupBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
|
|
||||||
model = SiteGroup
|
model = SiteGroup
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('parent', 'description')),
|
FieldSet('parent', 'description'),
|
||||||
)
|
)
|
||||||
nullable_fields = ('parent', 'description')
|
nullable_fields = ('parent', 'description')
|
||||||
|
|
||||||
@ -154,7 +155,7 @@ class SiteBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
|
|
||||||
model = Site
|
model = Site
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('status', 'region', 'group', 'tenant', 'asns', 'time_zone', 'description')),
|
FieldSet('status', 'region', 'group', 'tenant', 'asns', 'time_zone', 'description'),
|
||||||
)
|
)
|
||||||
nullable_fields = (
|
nullable_fields = (
|
||||||
'region', 'group', 'tenant', 'asns', 'time_zone', 'description', 'comments',
|
'region', 'group', 'tenant', 'asns', 'time_zone', 'description', 'comments',
|
||||||
@ -194,7 +195,7 @@ class LocationBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
|
|
||||||
model = Location
|
model = Location
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('site', 'parent', 'status', 'tenant', 'description')),
|
FieldSet('site', 'parent', 'status', 'tenant', 'description'),
|
||||||
)
|
)
|
||||||
nullable_fields = ('parent', 'tenant', 'description')
|
nullable_fields = ('parent', 'tenant', 'description')
|
||||||
|
|
||||||
@ -212,7 +213,7 @@ class RackRoleBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
|
|
||||||
model = RackRole
|
model = RackRole
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('color', 'description')),
|
FieldSet('color', 'description'),
|
||||||
)
|
)
|
||||||
nullable_fields = ('color', 'description')
|
nullable_fields = ('color', 'description')
|
||||||
|
|
||||||
@ -341,12 +342,13 @@ class RackBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
|
|
||||||
model = Rack
|
model = Rack
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Rack'), ('status', 'role', 'tenant', 'serial', 'asset_tag', 'description')),
|
FieldSet('status', 'role', 'tenant', 'serial', 'asset_tag', 'description', name=_('Rack')),
|
||||||
(_('Location'), ('region', 'site_group', 'site', 'location')),
|
FieldSet('region', 'site_group', 'site', 'location', name=_('Location')),
|
||||||
(_('Hardware'), (
|
FieldSet(
|
||||||
'type', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit', 'mounting_depth',
|
'type', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit', 'mounting_depth',
|
||||||
)),
|
name=_('Hardware')
|
||||||
(_('Weight'), ('weight', 'max_weight', 'weight_unit')),
|
),
|
||||||
|
FieldSet('weight', 'max_weight', 'weight_unit', name=_('Weight')),
|
||||||
)
|
)
|
||||||
nullable_fields = (
|
nullable_fields = (
|
||||||
'location', 'tenant', 'role', 'serial', 'asset_tag', 'outer_width', 'outer_depth', 'outer_unit', 'weight',
|
'location', 'tenant', 'role', 'serial', 'asset_tag', 'outer_width', 'outer_depth', 'outer_unit', 'weight',
|
||||||
@ -376,7 +378,7 @@ class RackReservationBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
|
|
||||||
model = RackReservation
|
model = RackReservation
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('user', 'tenant', 'description')),
|
FieldSet('user', 'tenant', 'description'),
|
||||||
)
|
)
|
||||||
nullable_fields = ('comments',)
|
nullable_fields = ('comments',)
|
||||||
|
|
||||||
@ -390,7 +392,7 @@ class ManufacturerBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
|
|
||||||
model = Manufacturer
|
model = Manufacturer
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('description',)),
|
FieldSet('description'),
|
||||||
)
|
)
|
||||||
nullable_fields = ('description',)
|
nullable_fields = ('description',)
|
||||||
|
|
||||||
@ -450,11 +452,11 @@ class DeviceTypeBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
|
|
||||||
model = DeviceType
|
model = DeviceType
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Device Type'), (
|
FieldSet(
|
||||||
'manufacturer', 'default_platform', 'part_number', 'u_height', 'exclude_from_utilization', 'is_full_depth',
|
'manufacturer', 'default_platform', 'part_number', 'u_height', 'exclude_from_utilization', 'is_full_depth',
|
||||||
'airflow', 'description',
|
'airflow', 'description', name=_('Device Type')
|
||||||
)),
|
),
|
||||||
(_('Weight'), ('weight', 'weight_unit')),
|
FieldSet('weight', 'weight_unit', name=_('Weight')),
|
||||||
)
|
)
|
||||||
nullable_fields = ('part_number', 'airflow', 'weight', 'weight_unit', 'description', 'comments')
|
nullable_fields = ('part_number', 'airflow', 'weight', 'weight_unit', 'description', 'comments')
|
||||||
|
|
||||||
@ -489,8 +491,8 @@ class ModuleTypeBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
|
|
||||||
model = ModuleType
|
model = ModuleType
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Module Type'), ('manufacturer', 'part_number', 'description')),
|
FieldSet('manufacturer', 'part_number', 'description', name=_('Module Type')),
|
||||||
(_('Weight'), ('weight', 'weight_unit')),
|
FieldSet('weight', 'weight_unit', name=_('Weight')),
|
||||||
)
|
)
|
||||||
nullable_fields = ('part_number', 'weight', 'weight_unit', 'description', 'comments')
|
nullable_fields = ('part_number', 'weight', 'weight_unit', 'description', 'comments')
|
||||||
|
|
||||||
@ -518,7 +520,7 @@ class DeviceRoleBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
|
|
||||||
model = DeviceRole
|
model = DeviceRole
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('color', 'vm_role', 'config_template', 'description')),
|
FieldSet('color', 'vm_role', 'config_template', 'description'),
|
||||||
)
|
)
|
||||||
nullable_fields = ('color', 'config_template', 'description')
|
nullable_fields = ('color', 'config_template', 'description')
|
||||||
|
|
||||||
@ -542,7 +544,7 @@ class PlatformBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
|
|
||||||
model = Platform
|
model = Platform
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('manufacturer', 'config_template', 'description')),
|
FieldSet('manufacturer', 'config_template', 'description'),
|
||||||
)
|
)
|
||||||
nullable_fields = ('manufacturer', 'config_template', 'description')
|
nullable_fields = ('manufacturer', 'config_template', 'description')
|
||||||
|
|
||||||
@ -621,10 +623,10 @@ class DeviceBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
|
|
||||||
model = Device
|
model = Device
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Device'), ('role', 'status', 'tenant', 'platform', 'description')),
|
FieldSet('role', 'status', 'tenant', 'platform', 'description', name=_('Device')),
|
||||||
(_('Location'), ('site', 'location')),
|
FieldSet('site', 'location', name=_('Location')),
|
||||||
(_('Hardware'), ('manufacturer', 'device_type', 'airflow', 'serial')),
|
FieldSet('manufacturer', 'device_type', 'airflow', 'serial', name=_('Hardware')),
|
||||||
(_('Configuration'), ('config_template',)),
|
FieldSet('config_template', name=_('Configuration')),
|
||||||
)
|
)
|
||||||
nullable_fields = (
|
nullable_fields = (
|
||||||
'location', 'tenant', 'platform', 'serial', 'airflow', 'description', 'comments',
|
'location', 'tenant', 'platform', 'serial', 'airflow', 'description', 'comments',
|
||||||
@ -668,7 +670,7 @@ class ModuleBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
|
|
||||||
model = Module
|
model = Module
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('manufacturer', 'module_type', 'status', 'serial', 'description')),
|
FieldSet('manufacturer', 'module_type', 'status', 'serial', 'description'),
|
||||||
)
|
)
|
||||||
nullable_fields = ('serial', 'description', 'comments')
|
nullable_fields = ('serial', 'description', 'comments')
|
||||||
|
|
||||||
@ -720,8 +722,8 @@ class CableBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
|
|
||||||
model = Cable
|
model = Cable
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('type', 'status', 'tenant', 'label', 'description')),
|
FieldSet('type', 'status', 'tenant', 'label', 'description'),
|
||||||
(_('Attributes'), ('color', 'length', 'length_unit')),
|
FieldSet('color', 'length', 'length_unit', name=_('Attributes')),
|
||||||
)
|
)
|
||||||
nullable_fields = (
|
nullable_fields = (
|
||||||
'type', 'status', 'tenant', 'label', 'color', 'length', 'description', 'comments',
|
'type', 'status', 'tenant', 'label', 'color', 'length', 'description', 'comments',
|
||||||
@ -743,7 +745,7 @@ class VirtualChassisBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
|
|
||||||
model = VirtualChassis
|
model = VirtualChassis
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('domain', 'description')),
|
FieldSet('domain', 'description'),
|
||||||
)
|
)
|
||||||
nullable_fields = ('domain', 'description', 'comments')
|
nullable_fields = ('domain', 'description', 'comments')
|
||||||
|
|
||||||
@ -791,7 +793,7 @@ class PowerPanelBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
|
|
||||||
model = PowerPanel
|
model = PowerPanel
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('region', 'site_group', 'site', 'location', 'description')),
|
FieldSet('region', 'site_group', 'site', 'location', 'description'),
|
||||||
)
|
)
|
||||||
nullable_fields = ('location', 'description', 'comments')
|
nullable_fields = ('location', 'description', 'comments')
|
||||||
|
|
||||||
@ -861,8 +863,8 @@ class PowerFeedBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
|
|
||||||
model = PowerFeed
|
model = PowerFeed
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('power_panel', 'rack', 'status', 'type', 'mark_connected', 'description', 'tenant')),
|
FieldSet('power_panel', 'rack', 'status', 'type', 'mark_connected', 'description', 'tenant'),
|
||||||
(_('Power'), ('supply', 'phase', 'voltage', 'amperage', 'max_utilization'))
|
FieldSet('supply', 'phase', 'voltage', 'amperage', 'max_utilization', name=_('Power'))
|
||||||
)
|
)
|
||||||
nullable_fields = ('location', 'tenant', 'description', 'comments')
|
nullable_fields = ('location', 'tenant', 'description', 'comments')
|
||||||
|
|
||||||
@ -1210,7 +1212,7 @@ class ConsolePortBulkEditForm(
|
|||||||
|
|
||||||
model = ConsolePort
|
model = ConsolePort
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('module', 'type', 'label', 'speed', 'description', 'mark_connected')),
|
FieldSet('module', 'type', 'label', 'speed', 'description', 'mark_connected'),
|
||||||
)
|
)
|
||||||
nullable_fields = ('module', 'label', 'description')
|
nullable_fields = ('module', 'label', 'description')
|
||||||
|
|
||||||
@ -1227,7 +1229,7 @@ class ConsoleServerPortBulkEditForm(
|
|||||||
|
|
||||||
model = ConsoleServerPort
|
model = ConsoleServerPort
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('module', 'type', 'label', 'speed', 'description', 'mark_connected')),
|
FieldSet('module', 'type', 'label', 'speed', 'description', 'mark_connected'),
|
||||||
)
|
)
|
||||||
nullable_fields = ('module', 'label', 'description')
|
nullable_fields = ('module', 'label', 'description')
|
||||||
|
|
||||||
@ -1244,8 +1246,8 @@ class PowerPortBulkEditForm(
|
|||||||
|
|
||||||
model = PowerPort
|
model = PowerPort
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('module', 'type', 'label', 'description', 'mark_connected')),
|
FieldSet('module', 'type', 'label', 'description', 'mark_connected'),
|
||||||
(_('Power'), ('maximum_draw', 'allocated_draw')),
|
FieldSet('maximum_draw', 'allocated_draw', name=_('Power')),
|
||||||
)
|
)
|
||||||
nullable_fields = ('module', 'label', 'description', 'maximum_draw', 'allocated_draw')
|
nullable_fields = ('module', 'label', 'description', 'maximum_draw', 'allocated_draw')
|
||||||
|
|
||||||
@ -1262,8 +1264,8 @@ class PowerOutletBulkEditForm(
|
|||||||
|
|
||||||
model = PowerOutlet
|
model = PowerOutlet
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('module', 'type', 'label', 'description', 'mark_connected')),
|
FieldSet('module', 'type', 'label', 'description', 'mark_connected'),
|
||||||
(_('Power'), ('feed_leg', 'power_port')),
|
FieldSet('feed_leg', 'power_port', name=_('Power')),
|
||||||
)
|
)
|
||||||
nullable_fields = ('module', 'label', 'type', 'feed_leg', 'power_port', 'description')
|
nullable_fields = ('module', 'label', 'type', 'feed_leg', 'power_port', 'description')
|
||||||
|
|
||||||
@ -1395,20 +1397,21 @@ class InterfaceBulkEditForm(
|
|||||||
|
|
||||||
model = Interface
|
model = Interface
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('module', 'type', 'label', 'speed', 'duplex', 'description')),
|
FieldSet('module', 'type', 'label', 'speed', 'duplex', 'description'),
|
||||||
(_('Addressing'), ('vrf', 'mac_address', 'wwn')),
|
FieldSet('vrf', 'mac_address', 'wwn', name=_('Addressing')),
|
||||||
(_('Operation'), ('vdcs', 'mtu', 'tx_power', 'enabled', 'mgmt_only', 'mark_connected')),
|
FieldSet('vdcs', 'mtu', 'tx_power', 'enabled', 'mgmt_only', 'mark_connected', name=_('Operation')),
|
||||||
(_('PoE'), ('poe_mode', 'poe_type')),
|
FieldSet('poe_mode', 'poe_type', name=_('PoE')),
|
||||||
(_('Related Interfaces'), ('parent', 'bridge', 'lag')),
|
FieldSet('parent', 'bridge', 'lag', name=_('Related Interfaces')),
|
||||||
(_('802.1Q Switching'), ('mode', 'vlan_group', 'untagged_vlan', 'tagged_vlans')),
|
FieldSet('mode', 'vlan_group', 'untagged_vlan', 'tagged_vlans', name=_('802.1Q Switching')),
|
||||||
(_('Wireless'), (
|
FieldSet(
|
||||||
'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'wireless_lan_group', 'wireless_lans',
|
'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'wireless_lan_group', 'wireless_lans',
|
||||||
)),
|
name=_('Wireless')
|
||||||
|
),
|
||||||
)
|
)
|
||||||
nullable_fields = (
|
nullable_fields = (
|
||||||
'module', 'label', 'parent', 'bridge', 'lag', 'speed', 'duplex', 'mac_address', 'wwn', 'vdcs', 'mtu', 'description',
|
'module', 'label', 'parent', 'bridge', 'lag', 'speed', 'duplex', 'mac_address', 'wwn', 'vdcs', 'mtu',
|
||||||
'poe_mode', 'poe_type', 'mode', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'untagged_vlan',
|
'description', 'poe_mode', 'poe_type', 'mode', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width',
|
||||||
'tagged_vlans', 'vrf', 'wireless_lans'
|
'tx_power', 'untagged_vlan', 'tagged_vlans', 'vrf', 'wireless_lans'
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
@ -1488,7 +1491,7 @@ class FrontPortBulkEditForm(
|
|||||||
|
|
||||||
model = FrontPort
|
model = FrontPort
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('module', 'type', 'label', 'color', 'description', 'mark_connected')),
|
FieldSet('module', 'type', 'label', 'color', 'description', 'mark_connected'),
|
||||||
)
|
)
|
||||||
nullable_fields = ('module', 'label', 'description', 'color')
|
nullable_fields = ('module', 'label', 'description', 'color')
|
||||||
|
|
||||||
@ -1505,7 +1508,7 @@ class RearPortBulkEditForm(
|
|||||||
|
|
||||||
model = RearPort
|
model = RearPort
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('module', 'type', 'label', 'color', 'description', 'mark_connected')),
|
FieldSet('module', 'type', 'label', 'color', 'description', 'mark_connected'),
|
||||||
)
|
)
|
||||||
nullable_fields = ('module', 'label', 'description', 'color')
|
nullable_fields = ('module', 'label', 'description', 'color')
|
||||||
|
|
||||||
@ -1516,7 +1519,7 @@ class ModuleBayBulkEditForm(
|
|||||||
):
|
):
|
||||||
model = ModuleBay
|
model = ModuleBay
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('label', 'position', 'description')),
|
FieldSet('label', 'position', 'description'),
|
||||||
)
|
)
|
||||||
nullable_fields = ('label', 'position', 'description')
|
nullable_fields = ('label', 'position', 'description')
|
||||||
|
|
||||||
@ -1527,7 +1530,7 @@ class DeviceBayBulkEditForm(
|
|||||||
):
|
):
|
||||||
model = DeviceBay
|
model = DeviceBay
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('label', 'description')),
|
FieldSet('label', 'description'),
|
||||||
)
|
)
|
||||||
nullable_fields = ('label', 'description')
|
nullable_fields = ('label', 'description')
|
||||||
|
|
||||||
@ -1554,7 +1557,7 @@ class InventoryItemBulkEditForm(
|
|||||||
|
|
||||||
model = InventoryItem
|
model = InventoryItem
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('device', 'label', 'role', 'manufacturer', 'part_id', 'description')),
|
FieldSet('device', 'label', 'role', 'manufacturer', 'part_id', 'description'),
|
||||||
)
|
)
|
||||||
nullable_fields = ('label', 'role', 'manufacturer', 'part_id', 'description')
|
nullable_fields = ('label', 'role', 'manufacturer', 'part_id', 'description')
|
||||||
|
|
||||||
@ -1576,7 +1579,7 @@ class InventoryItemRoleBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
|
|
||||||
model = InventoryItemRole
|
model = InventoryItemRole
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('color', 'description')),
|
FieldSet('color', 'description'),
|
||||||
)
|
)
|
||||||
nullable_fields = ('color', 'description')
|
nullable_fields = ('color', 'description')
|
||||||
|
|
||||||
@ -1599,6 +1602,6 @@ class VirtualDeviceContextBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
)
|
)
|
||||||
model = VirtualDeviceContext
|
model = VirtualDeviceContext
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('device', 'status', 'tenant')),
|
FieldSet('device', 'status', 'tenant'),
|
||||||
)
|
)
|
||||||
nullable_fields = ('device', 'tenant', )
|
nullable_fields = ('device', 'tenant', )
|
||||||
|
@ -157,7 +157,7 @@ class LocationImportForm(NetBoxModelImportForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Location
|
model = Location
|
||||||
fields = ('site', 'parent', 'name', 'slug', 'status', 'tenant', 'description', 'tags')
|
fields = ('site', 'parent', 'name', 'slug', 'status', 'tenant', 'facility', 'description', 'tags')
|
||||||
|
|
||||||
def __init__(self, data=None, *args, **kwargs):
|
def __init__(self, data=None, *args, **kwargs):
|
||||||
super().__init__(data, *args, **kwargs)
|
super().__init__(data, *args, **kwargs)
|
||||||
|
@ -12,7 +12,8 @@ from netbox.forms import NetBoxModelFilterSetForm
|
|||||||
from tenancy.forms import ContactModelFilterForm, TenancyFilterForm
|
from tenancy.forms import ContactModelFilterForm, TenancyFilterForm
|
||||||
from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES, FilterForm, add_blank_choice
|
from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES, FilterForm, add_blank_choice
|
||||||
from utilities.forms.fields import ColorField, DynamicModelMultipleChoiceField, TagFilterField
|
from utilities.forms.fields import ColorField, DynamicModelMultipleChoiceField, TagFilterField
|
||||||
from utilities.forms.widgets import APISelectMultiple, NumberWithOptions
|
from utilities.forms.rendering import FieldSet
|
||||||
|
from utilities.forms.widgets import NumberWithOptions
|
||||||
from vpn.models import L2VPN
|
from vpn.models import L2VPN
|
||||||
from wireless.choices import *
|
from wireless.choices import *
|
||||||
|
|
||||||
@ -132,8 +133,8 @@ class DeviceComponentFilterForm(NetBoxModelFilterSetForm):
|
|||||||
class RegionFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
|
class RegionFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
|
||||||
model = Region
|
model = Region
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag', 'parent_id')),
|
FieldSet('q', 'filter_id', 'tag', 'parent_id'),
|
||||||
(_('Contacts'), ('contact', 'contact_role', 'contact_group'))
|
FieldSet('contact', 'contact_role', 'contact_group', name=_('Contacts'))
|
||||||
)
|
)
|
||||||
parent_id = DynamicModelMultipleChoiceField(
|
parent_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=Region.objects.all(),
|
queryset=Region.objects.all(),
|
||||||
@ -146,8 +147,8 @@ class RegionFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
|
|||||||
class SiteGroupFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
|
class SiteGroupFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
|
||||||
model = SiteGroup
|
model = SiteGroup
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag', 'parent_id')),
|
FieldSet('q', 'filter_id', 'tag', 'parent_id'),
|
||||||
(_('Contacts'), ('contact', 'contact_role', 'contact_group'))
|
FieldSet('contact', 'contact_role', 'contact_group', name=_('Contacts'))
|
||||||
)
|
)
|
||||||
parent_id = DynamicModelMultipleChoiceField(
|
parent_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=SiteGroup.objects.all(),
|
queryset=SiteGroup.objects.all(),
|
||||||
@ -160,10 +161,10 @@ class SiteGroupFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
|
|||||||
class SiteFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilterSetForm):
|
class SiteFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilterSetForm):
|
||||||
model = Site
|
model = Site
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
FieldSet('q', 'filter_id', 'tag'),
|
||||||
(_('Attributes'), ('status', 'region_id', 'group_id', 'asn_id')),
|
FieldSet('status', 'region_id', 'group_id', 'asn_id', name=_('Attributes')),
|
||||||
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
|
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||||
(_('Contacts'), ('contact', 'contact_role', 'contact_group')),
|
FieldSet('contact', 'contact_role', 'contact_group', name=_('Contacts')),
|
||||||
)
|
)
|
||||||
selector_fields = ('filter_id', 'q', 'region_id', 'group_id')
|
selector_fields = ('filter_id', 'q', 'region_id', 'group_id')
|
||||||
status = forms.MultipleChoiceField(
|
status = forms.MultipleChoiceField(
|
||||||
@ -192,10 +193,10 @@ class SiteFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilte
|
|||||||
class LocationFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilterSetForm):
|
class LocationFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilterSetForm):
|
||||||
model = Location
|
model = Location
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
FieldSet('q', 'filter_id', 'tag'),
|
||||||
(_('Attributes'), ('region_id', 'site_group_id', 'site_id', 'parent_id', 'status')),
|
FieldSet('region_id', 'site_group_id', 'site_id', 'parent_id', 'status', name=_('Attributes')),
|
||||||
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
|
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||||
(_('Contacts'), ('contact', 'contact_role', 'contact_group')),
|
FieldSet('contact', 'contact_role', 'contact_group', name=_('Contacts')),
|
||||||
)
|
)
|
||||||
region_id = DynamicModelMultipleChoiceField(
|
region_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=Region.objects.all(),
|
queryset=Region.objects.all(),
|
||||||
@ -241,13 +242,13 @@ class RackRoleFilterForm(NetBoxModelFilterSetForm):
|
|||||||
class RackFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilterSetForm):
|
class RackFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilterSetForm):
|
||||||
model = Rack
|
model = Rack
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
FieldSet('q', 'filter_id', 'tag'),
|
||||||
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id')),
|
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', name=_('Location')),
|
||||||
(_('Function'), ('status', 'role_id')),
|
FieldSet('status', 'role_id', name=_('Function')),
|
||||||
(_('Hardware'), ('type', 'width', 'serial', 'asset_tag')),
|
FieldSet('type', 'width', 'serial', 'asset_tag', name=_('Hardware')),
|
||||||
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
|
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||||
(_('Contacts'), ('contact', 'contact_role', 'contact_group')),
|
FieldSet('contact', 'contact_role', 'contact_group', name=_('Contacts')),
|
||||||
(_('Weight'), ('weight', 'max_weight', 'weight_unit')),
|
FieldSet('weight', 'max_weight', 'weight_unit', name=_('Weight')),
|
||||||
)
|
)
|
||||||
selector_fields = ('filter_id', 'q', 'region_id', 'site_group_id', 'site_id', 'location_id')
|
selector_fields = ('filter_id', 'q', 'region_id', 'site_group_id', 'site_id', 'location_id')
|
||||||
region_id = DynamicModelMultipleChoiceField(
|
region_id = DynamicModelMultipleChoiceField(
|
||||||
@ -326,13 +327,13 @@ class RackFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilte
|
|||||||
|
|
||||||
class RackElevationFilterForm(RackFilterForm):
|
class RackElevationFilterForm(RackFilterForm):
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
FieldSet('q', 'filter_id', 'tag'),
|
||||||
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'id')),
|
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'id', name=_('Location')),
|
||||||
(_('Function'), ('status', 'role_id')),
|
FieldSet('status', 'role_id', name=_('Function')),
|
||||||
(_('Hardware'), ('type', 'width', 'serial', 'asset_tag')),
|
FieldSet('type', 'width', 'serial', 'asset_tag', name=_('Hardware')),
|
||||||
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
|
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||||
(_('Contacts'), ('contact', 'contact_role', 'contact_group')),
|
FieldSet('contact', 'contact_role', 'contact_group', name=_('Contacts')),
|
||||||
(_('Weight'), ('weight', 'max_weight', 'weight_unit')),
|
FieldSet('weight', 'max_weight', 'weight_unit', name=_('Weight')),
|
||||||
)
|
)
|
||||||
id = DynamicModelMultipleChoiceField(
|
id = DynamicModelMultipleChoiceField(
|
||||||
queryset=Rack.objects.all(),
|
queryset=Rack.objects.all(),
|
||||||
@ -348,10 +349,10 @@ class RackElevationFilterForm(RackFilterForm):
|
|||||||
class RackReservationFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
class RackReservationFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||||
model = RackReservation
|
model = RackReservation
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
FieldSet('q', 'filter_id', 'tag'),
|
||||||
(_('User'), ('user_id',)),
|
FieldSet('user_id', name=_('User')),
|
||||||
(_('Rack'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
|
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Rack')),
|
||||||
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
|
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||||
)
|
)
|
||||||
region_id = DynamicModelMultipleChoiceField(
|
region_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=Region.objects.all(),
|
queryset=Region.objects.all(),
|
||||||
@ -401,8 +402,8 @@ class RackReservationFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
|||||||
class ManufacturerFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
|
class ManufacturerFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
|
||||||
model = Manufacturer
|
model = Manufacturer
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
FieldSet('q', 'filter_id', 'tag'),
|
||||||
(_('Contacts'), ('contact', 'contact_role', 'contact_group'))
|
FieldSet('contact', 'contact_role', 'contact_group', name=_('Contacts'))
|
||||||
)
|
)
|
||||||
tag = TagFilterField(model)
|
tag = TagFilterField(model)
|
||||||
|
|
||||||
@ -410,14 +411,16 @@ class ManufacturerFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
|
|||||||
class DeviceTypeFilterForm(NetBoxModelFilterSetForm):
|
class DeviceTypeFilterForm(NetBoxModelFilterSetForm):
|
||||||
model = DeviceType
|
model = DeviceType
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
FieldSet('q', 'filter_id', 'tag'),
|
||||||
(_('Hardware'), ('manufacturer_id', 'default_platform_id', 'part_number', 'subdevice_role', 'airflow')),
|
FieldSet(
|
||||||
(_('Images'), ('has_front_image', 'has_rear_image')),
|
'manufacturer_id', 'default_platform_id', 'part_number', 'subdevice_role', 'airflow', name=_('Hardware')
|
||||||
(_('Components'), (
|
),
|
||||||
|
FieldSet('has_front_image', 'has_rear_image', name=_('Images')),
|
||||||
|
FieldSet(
|
||||||
'console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces',
|
'console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces',
|
||||||
'pass_through_ports', 'device_bays', 'module_bays', 'inventory_items',
|
'pass_through_ports', 'device_bays', 'module_bays', 'inventory_items', name=_('Components')
|
||||||
)),
|
),
|
||||||
(_('Weight'), ('weight', 'weight_unit')),
|
FieldSet('weight', 'weight_unit', name=_('Weight')),
|
||||||
)
|
)
|
||||||
selector_fields = ('filter_id', 'q', 'manufacturer_id')
|
selector_fields = ('filter_id', 'q', 'manufacturer_id')
|
||||||
manufacturer_id = DynamicModelMultipleChoiceField(
|
manufacturer_id = DynamicModelMultipleChoiceField(
|
||||||
@ -536,13 +539,13 @@ class DeviceTypeFilterForm(NetBoxModelFilterSetForm):
|
|||||||
class ModuleTypeFilterForm(NetBoxModelFilterSetForm):
|
class ModuleTypeFilterForm(NetBoxModelFilterSetForm):
|
||||||
model = ModuleType
|
model = ModuleType
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
FieldSet('q', 'filter_id', 'tag'),
|
||||||
(_('Hardware'), ('manufacturer_id', 'part_number')),
|
FieldSet('manufacturer_id', 'part_number', name=_('Hardware')),
|
||||||
(_('Components'), (
|
FieldSet(
|
||||||
'console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces',
|
'console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces',
|
||||||
'pass_through_ports',
|
'pass_through_ports', name=_('Components')
|
||||||
)),
|
),
|
||||||
(_('Weight'), ('weight', 'weight_unit')),
|
FieldSet('weight', 'weight_unit', name=_('Weight')),
|
||||||
)
|
)
|
||||||
selector_fields = ('filter_id', 'q', 'manufacturer_id')
|
selector_fields = ('filter_id', 'q', 'manufacturer_id')
|
||||||
manufacturer_id = DynamicModelMultipleChoiceField(
|
manufacturer_id = DynamicModelMultipleChoiceField(
|
||||||
@ -642,18 +645,20 @@ class DeviceFilterForm(
|
|||||||
):
|
):
|
||||||
model = Device
|
model = Device
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
FieldSet('q', 'filter_id', 'tag'),
|
||||||
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
|
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
|
||||||
(_('Operation'), ('status', 'role_id', 'airflow', 'serial', 'asset_tag', 'mac_address')),
|
FieldSet('status', 'role_id', 'airflow', 'serial', 'asset_tag', 'mac_address', name=_('Operation')),
|
||||||
(_('Hardware'), ('manufacturer_id', 'device_type_id', 'platform_id')),
|
FieldSet('manufacturer_id', 'device_type_id', 'platform_id', name=_('Hardware')),
|
||||||
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
|
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||||
(_('Contacts'), ('contact', 'contact_role', 'contact_group')),
|
FieldSet('contact', 'contact_role', 'contact_group', name=_('Contacts')),
|
||||||
(_('Components'), (
|
FieldSet(
|
||||||
'console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces', 'pass_through_ports',
|
'console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces', 'pass_through_ports',
|
||||||
)),
|
name=_('Components')
|
||||||
(_('Miscellaneous'), (
|
),
|
||||||
|
FieldSet(
|
||||||
'has_primary_ip', 'has_oob_ip', 'virtual_chassis_member', 'config_template_id', 'local_context_data',
|
'has_primary_ip', 'has_oob_ip', 'virtual_chassis_member', 'config_template_id', 'local_context_data',
|
||||||
))
|
name=_('Miscellaneous')
|
||||||
|
)
|
||||||
)
|
)
|
||||||
selector_fields = ('filter_id', 'q', 'region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')
|
selector_fields = ('filter_id', 'q', 'region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')
|
||||||
region_id = DynamicModelMultipleChoiceField(
|
region_id = DynamicModelMultipleChoiceField(
|
||||||
@ -754,7 +759,7 @@ class DeviceFilterForm(
|
|||||||
)
|
)
|
||||||
has_oob_ip = forms.NullBooleanField(
|
has_oob_ip = forms.NullBooleanField(
|
||||||
required=False,
|
required=False,
|
||||||
label='Has an OOB IP',
|
label=_('Has an OOB IP'),
|
||||||
widget=forms.Select(
|
widget=forms.Select(
|
||||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||||
)
|
)
|
||||||
@ -817,9 +822,9 @@ class VirtualDeviceContextFilterForm(
|
|||||||
):
|
):
|
||||||
model = VirtualDeviceContext
|
model = VirtualDeviceContext
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
FieldSet('q', 'filter_id', 'tag'),
|
||||||
(_('Attributes'), ('device', 'status', 'has_primary_ip')),
|
FieldSet('device', 'status', 'has_primary_ip', name=_('Attributes')),
|
||||||
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
|
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||||
)
|
)
|
||||||
device = DynamicModelMultipleChoiceField(
|
device = DynamicModelMultipleChoiceField(
|
||||||
queryset=Device.objects.all(),
|
queryset=Device.objects.all(),
|
||||||
@ -844,8 +849,8 @@ class VirtualDeviceContextFilterForm(
|
|||||||
class ModuleFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, NetBoxModelFilterSetForm):
|
class ModuleFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||||
model = Module
|
model = Module
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
FieldSet('q', 'filter_id', 'tag'),
|
||||||
(_('Hardware'), ('manufacturer_id', 'module_type_id', 'status', 'serial', 'asset_tag')),
|
FieldSet('manufacturer_id', 'module_type_id', 'status', 'serial', 'asset_tag', name=_('Hardware')),
|
||||||
)
|
)
|
||||||
manufacturer_id = DynamicModelMultipleChoiceField(
|
manufacturer_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=Manufacturer.objects.all(),
|
queryset=Manufacturer.objects.all(),
|
||||||
@ -879,9 +884,9 @@ class ModuleFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, NetBoxMo
|
|||||||
class VirtualChassisFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
class VirtualChassisFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||||
model = VirtualChassis
|
model = VirtualChassis
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
FieldSet('q', 'filter_id', 'tag'),
|
||||||
(_('Location'), ('region_id', 'site_group_id', 'site_id')),
|
FieldSet('region_id', 'site_group_id', 'site_id', name=_('Location')),
|
||||||
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
|
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||||
)
|
)
|
||||||
region_id = DynamicModelMultipleChoiceField(
|
region_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=Region.objects.all(),
|
queryset=Region.objects.all(),
|
||||||
@ -908,10 +913,10 @@ class VirtualChassisFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
|||||||
class CableFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
class CableFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||||
model = Cable
|
model = Cable
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
FieldSet('q', 'filter_id', 'tag'),
|
||||||
(_('Location'), ('site_id', 'location_id', 'rack_id', 'device_id')),
|
FieldSet('site_id', 'location_id', 'rack_id', 'device_id', name=_('Location')),
|
||||||
(_('Attributes'), ('type', 'status', 'color', 'length', 'length_unit', 'unterminated')),
|
FieldSet('type', 'status', 'color', 'length', 'length_unit', 'unterminated', name=_('Attributes')),
|
||||||
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
|
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||||
)
|
)
|
||||||
region_id = DynamicModelMultipleChoiceField(
|
region_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=Region.objects.all(),
|
queryset=Region.objects.all(),
|
||||||
@ -992,9 +997,9 @@ class CableFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
|||||||
class PowerPanelFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
|
class PowerPanelFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
|
||||||
model = PowerPanel
|
model = PowerPanel
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
FieldSet('q', 'filter_id', 'tag'),
|
||||||
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id')),
|
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', name=_('Location')),
|
||||||
(_('Contacts'), ('contact', 'contact_role', 'contact_group')),
|
FieldSet('contact', 'contact_role', 'contact_group', name=_('Contacts')),
|
||||||
)
|
)
|
||||||
selector_fields = ('filter_id', 'q', 'site_id', 'location_id')
|
selector_fields = ('filter_id', 'q', 'site_id', 'location_id')
|
||||||
region_id = DynamicModelMultipleChoiceField(
|
region_id = DynamicModelMultipleChoiceField(
|
||||||
@ -1031,10 +1036,10 @@ class PowerPanelFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
|
|||||||
class PowerFeedFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
class PowerFeedFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||||
model = PowerFeed
|
model = PowerFeed
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
FieldSet('q', 'filter_id', 'tag'),
|
||||||
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'power_panel_id', 'rack_id')),
|
FieldSet('region_id', 'site_group_id', 'site_id', 'power_panel_id', 'rack_id', name=_('Location')),
|
||||||
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
|
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||||
(_('Attributes'), ('status', 'type', 'supply', 'phase', 'voltage', 'amperage', 'max_utilization')),
|
FieldSet('status', 'type', 'supply', 'phase', 'voltage', 'amperage', 'max_utilization', name=_('Attributes')),
|
||||||
)
|
)
|
||||||
region_id = DynamicModelMultipleChoiceField(
|
region_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=Region.objects.all(),
|
queryset=Region.objects.all(),
|
||||||
@ -1141,11 +1146,11 @@ class PathEndpointFilterForm(CabledFilterForm):
|
|||||||
class ConsolePortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
|
class ConsolePortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
|
||||||
model = ConsolePort
|
model = ConsolePort
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
FieldSet('q', 'filter_id', 'tag'),
|
||||||
(_('Attributes'), ('name', 'label', 'type', 'speed')),
|
FieldSet('name', 'label', 'type', 'speed', name=_('Attributes')),
|
||||||
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
|
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
|
||||||
(_('Device'), ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')),
|
FieldSet('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id', name=_('Device')),
|
||||||
(_('Connection'), ('cabled', 'connected', 'occupied')),
|
FieldSet('cabled', 'connected', 'occupied', name=_('Connection')),
|
||||||
)
|
)
|
||||||
type = forms.MultipleChoiceField(
|
type = forms.MultipleChoiceField(
|
||||||
label=_('Type'),
|
label=_('Type'),
|
||||||
@ -1163,11 +1168,11 @@ class ConsolePortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
|
|||||||
class ConsoleServerPortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
|
class ConsoleServerPortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
|
||||||
model = ConsoleServerPort
|
model = ConsoleServerPort
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
FieldSet('q', 'filter_id', 'tag'),
|
||||||
(_('Attributes'), ('name', 'label', 'type', 'speed')),
|
FieldSet('name', 'label', 'type', 'speed', name=_('Attributes')),
|
||||||
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
|
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
|
||||||
(_('Device'), ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')),
|
FieldSet('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id', name=_('Device')),
|
||||||
(_('Connection'), ('cabled', 'connected', 'occupied')),
|
FieldSet('cabled', 'connected', 'occupied', name=_('Connection')),
|
||||||
)
|
)
|
||||||
type = forms.MultipleChoiceField(
|
type = forms.MultipleChoiceField(
|
||||||
label=_('Type'),
|
label=_('Type'),
|
||||||
@ -1185,11 +1190,11 @@ class ConsoleServerPortFilterForm(PathEndpointFilterForm, DeviceComponentFilterF
|
|||||||
class PowerPortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
|
class PowerPortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
|
||||||
model = PowerPort
|
model = PowerPort
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
FieldSet('q', 'filter_id', 'tag'),
|
||||||
(_('Attributes'), ('name', 'label', 'type')),
|
FieldSet('name', 'label', 'type', name=_('Attributes')),
|
||||||
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
|
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
|
||||||
(_('Device'), ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')),
|
FieldSet('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id', name=_('Device')),
|
||||||
(_('Connection'), ('cabled', 'connected', 'occupied')),
|
FieldSet('cabled', 'connected', 'occupied', name=_('Connection')),
|
||||||
)
|
)
|
||||||
type = forms.MultipleChoiceField(
|
type = forms.MultipleChoiceField(
|
||||||
label=_('Type'),
|
label=_('Type'),
|
||||||
@ -1202,11 +1207,11 @@ class PowerPortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
|
|||||||
class PowerOutletFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
|
class PowerOutletFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
|
||||||
model = PowerOutlet
|
model = PowerOutlet
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
FieldSet('q', 'filter_id', 'tag'),
|
||||||
(_('Attributes'), ('name', 'label', 'type')),
|
FieldSet('name', 'label', 'type', name=_('Attributes')),
|
||||||
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
|
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
|
||||||
(_('Device'), ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')),
|
FieldSet('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id', name=_('Device')),
|
||||||
(_('Connection'), ('cabled', 'connected', 'occupied')),
|
FieldSet('cabled', 'connected', 'occupied', name=_('Connection')),
|
||||||
)
|
)
|
||||||
type = forms.MultipleChoiceField(
|
type = forms.MultipleChoiceField(
|
||||||
label=_('Type'),
|
label=_('Type'),
|
||||||
@ -1219,14 +1224,14 @@ class PowerOutletFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
|
|||||||
class InterfaceFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
|
class InterfaceFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
|
||||||
model = Interface
|
model = Interface
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
FieldSet('q', 'filter_id', 'tag'),
|
||||||
(_('Attributes'), ('name', 'label', 'kind', 'type', 'speed', 'duplex', 'enabled', 'mgmt_only')),
|
FieldSet('name', 'label', 'kind', 'type', 'speed', 'duplex', 'enabled', 'mgmt_only', name=_('Attributes')),
|
||||||
(_('Addressing'), ('vrf_id', 'l2vpn_id', 'mac_address', 'wwn')),
|
FieldSet('vrf_id', 'l2vpn_id', 'mac_address', 'wwn', name=_('Addressing')),
|
||||||
(_('PoE'), ('poe_mode', 'poe_type')),
|
FieldSet('poe_mode', 'poe_type', name=_('PoE')),
|
||||||
(_('Wireless'), ('rf_role', 'rf_channel', 'rf_channel_width', 'tx_power')),
|
FieldSet('rf_role', 'rf_channel', 'rf_channel_width', 'tx_power', name=_('Wireless')),
|
||||||
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
|
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
|
||||||
(_('Device'), ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id', 'vdc_id')),
|
FieldSet('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id', 'vdc_id', name=_('Device')),
|
||||||
(_('Connection'), ('cabled', 'connected', 'occupied')),
|
FieldSet('cabled', 'connected', 'occupied', name=_('Connection')),
|
||||||
)
|
)
|
||||||
selector_fields = ('filter_id', 'q', 'device_id')
|
selector_fields = ('filter_id', 'q', 'device_id')
|
||||||
vdc_id = DynamicModelMultipleChoiceField(
|
vdc_id = DynamicModelMultipleChoiceField(
|
||||||
@ -1330,11 +1335,11 @@ class InterfaceFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
|
|||||||
|
|
||||||
class FrontPortFilterForm(CabledFilterForm, DeviceComponentFilterForm):
|
class FrontPortFilterForm(CabledFilterForm, DeviceComponentFilterForm):
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
FieldSet('q', 'filter_id', 'tag'),
|
||||||
(_('Attributes'), ('name', 'label', 'type', 'color')),
|
FieldSet('name', 'label', 'type', 'color', name=_('Attributes')),
|
||||||
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
|
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
|
||||||
(_('Device'), ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')),
|
FieldSet('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id', name=_('Device')),
|
||||||
(_('Cable'), ('cabled', 'occupied')),
|
FieldSet('cabled', 'occupied', name=_('Cable')),
|
||||||
)
|
)
|
||||||
model = FrontPort
|
model = FrontPort
|
||||||
type = forms.MultipleChoiceField(
|
type = forms.MultipleChoiceField(
|
||||||
@ -1352,11 +1357,11 @@ class FrontPortFilterForm(CabledFilterForm, DeviceComponentFilterForm):
|
|||||||
class RearPortFilterForm(CabledFilterForm, DeviceComponentFilterForm):
|
class RearPortFilterForm(CabledFilterForm, DeviceComponentFilterForm):
|
||||||
model = RearPort
|
model = RearPort
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
FieldSet('q', 'filter_id', 'tag'),
|
||||||
(_('Attributes'), ('name', 'label', 'type', 'color')),
|
FieldSet('name', 'label', 'type', 'color', name=_('Attributes')),
|
||||||
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
|
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
|
||||||
(_('Device'), ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')),
|
FieldSet('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id', name=_('Device')),
|
||||||
(_('Cable'), ('cabled', 'occupied')),
|
FieldSet('cabled', 'occupied', name=_('Cable')),
|
||||||
)
|
)
|
||||||
type = forms.MultipleChoiceField(
|
type = forms.MultipleChoiceField(
|
||||||
label=_('Type'),
|
label=_('Type'),
|
||||||
@ -1373,10 +1378,10 @@ class RearPortFilterForm(CabledFilterForm, DeviceComponentFilterForm):
|
|||||||
class ModuleBayFilterForm(DeviceComponentFilterForm):
|
class ModuleBayFilterForm(DeviceComponentFilterForm):
|
||||||
model = ModuleBay
|
model = ModuleBay
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
FieldSet('q', 'filter_id', 'tag'),
|
||||||
(_('Attributes'), ('name', 'label', 'position')),
|
FieldSet('name', 'label', 'position', name=_('Attributes')),
|
||||||
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
|
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
|
||||||
(_('Device'), ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')),
|
FieldSet('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id', name=_('Device')),
|
||||||
)
|
)
|
||||||
tag = TagFilterField(model)
|
tag = TagFilterField(model)
|
||||||
position = forms.CharField(
|
position = forms.CharField(
|
||||||
@ -1388,10 +1393,10 @@ class ModuleBayFilterForm(DeviceComponentFilterForm):
|
|||||||
class DeviceBayFilterForm(DeviceComponentFilterForm):
|
class DeviceBayFilterForm(DeviceComponentFilterForm):
|
||||||
model = DeviceBay
|
model = DeviceBay
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
FieldSet('q', 'filter_id', 'tag'),
|
||||||
(_('Attributes'), ('name', 'label')),
|
FieldSet('name', 'label', name=_('Attributes')),
|
||||||
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
|
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
|
||||||
(_('Device'), ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')),
|
FieldSet('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id', name=_('Device')),
|
||||||
)
|
)
|
||||||
tag = TagFilterField(model)
|
tag = TagFilterField(model)
|
||||||
|
|
||||||
@ -1399,10 +1404,13 @@ class DeviceBayFilterForm(DeviceComponentFilterForm):
|
|||||||
class InventoryItemFilterForm(DeviceComponentFilterForm):
|
class InventoryItemFilterForm(DeviceComponentFilterForm):
|
||||||
model = InventoryItem
|
model = InventoryItem
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
FieldSet('q', 'filter_id', 'tag'),
|
||||||
(_('Attributes'), ('name', 'label', 'role_id', 'manufacturer_id', 'serial', 'asset_tag', 'discovered')),
|
FieldSet(
|
||||||
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
|
'name', 'label', 'role_id', 'manufacturer_id', 'serial', 'asset_tag', 'discovered',
|
||||||
(_('Device'), ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')),
|
name=_('Attributes')
|
||||||
|
),
|
||||||
|
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
|
||||||
|
FieldSet('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id', name=_('Device')),
|
||||||
)
|
)
|
||||||
role_id = DynamicModelMultipleChoiceField(
|
role_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=InventoryItemRole.objects.all(),
|
queryset=InventoryItemRole.objects.all(),
|
||||||
|
@ -16,6 +16,7 @@ from utilities.forms.fields import (
|
|||||||
CommentField, ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, JSONField,
|
CommentField, ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, JSONField,
|
||||||
NumericArrayField, SlugField,
|
NumericArrayField, SlugField,
|
||||||
)
|
)
|
||||||
|
from utilities.forms.rendering import FieldSet, InlineFields, TabbedGroups
|
||||||
from utilities.forms.widgets import APISelect, ClearableFileInput, HTMXSelect, NumberWithOptions, SelectWithPK
|
from utilities.forms.widgets import APISelect, ClearableFileInput, HTMXSelect, NumberWithOptions, SelectWithPK
|
||||||
from virtualization.models import Cluster
|
from virtualization.models import Cluster
|
||||||
from wireless.models import WirelessLAN, WirelessLANGroup
|
from wireless.models import WirelessLAN, WirelessLANGroup
|
||||||
@ -77,9 +78,7 @@ class RegionForm(NetBoxModelForm):
|
|||||||
slug = SlugField()
|
slug = SlugField()
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Region'), (
|
FieldSet('parent', 'name', 'slug', 'description', 'tags'),
|
||||||
'parent', 'name', 'slug', 'description', 'tags',
|
|
||||||
)),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -98,9 +97,7 @@ class SiteGroupForm(NetBoxModelForm):
|
|||||||
slug = SlugField()
|
slug = SlugField()
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Site Group'), (
|
FieldSet('parent', 'name', 'slug', 'description', 'tags'),
|
||||||
'parent', 'name', 'slug', 'description', 'tags',
|
|
||||||
)),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -135,11 +132,12 @@ class SiteForm(TenancyForm, NetBoxModelForm):
|
|||||||
comments = CommentField()
|
comments = CommentField()
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Site'), (
|
FieldSet(
|
||||||
'name', 'slug', 'status', 'region', 'group', 'facility', 'asns', 'time_zone', 'description', 'tags',
|
'name', 'slug', 'status', 'region', 'group', 'facility', 'asns', 'time_zone', 'description', 'tags',
|
||||||
)),
|
name=_('Site')
|
||||||
(_('Tenancy'), ('tenant_group', 'tenant')),
|
),
|
||||||
(_('Contact Info'), ('physical_address', 'shipping_address', 'latitude', 'longitude')),
|
FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
|
||||||
|
FieldSet('physical_address', 'shipping_address', 'latitude', 'longitude', name=_('Contact Info')),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -179,14 +177,14 @@ class LocationForm(TenancyForm, NetBoxModelForm):
|
|||||||
slug = SlugField()
|
slug = SlugField()
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Location'), ('site', 'parent', 'name', 'slug', 'status', 'description', 'tags')),
|
FieldSet('site', 'parent', 'name', 'slug', 'status', 'facility', 'description', 'tags', name=_('Location')),
|
||||||
(_('Tenancy'), ('tenant_group', 'tenant')),
|
FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Location
|
model = Location
|
||||||
fields = (
|
fields = (
|
||||||
'site', 'parent', 'name', 'slug', 'status', 'description', 'tenant_group', 'tenant', 'tags',
|
'site', 'parent', 'name', 'slug', 'status', 'description', 'tenant_group', 'tenant', 'facility', 'tags',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -194,9 +192,7 @@ class RackRoleForm(NetBoxModelForm):
|
|||||||
slug = SlugField()
|
slug = SlugField()
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Rack Role'), (
|
FieldSet('name', 'slug', 'color', 'description', 'tags', name=_('Rack Role')),
|
||||||
'name', 'slug', 'color', 'description', 'tags',
|
|
||||||
)),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -227,6 +223,18 @@ class RackForm(TenancyForm, NetBoxModelForm):
|
|||||||
)
|
)
|
||||||
comments = CommentField()
|
comments = CommentField()
|
||||||
|
|
||||||
|
fieldsets = (
|
||||||
|
FieldSet('site', 'location', 'name', 'status', 'role', 'description', 'tags', name=_('Rack')),
|
||||||
|
FieldSet('facility_id', 'serial', 'asset_tag', name=_('Inventory Control')),
|
||||||
|
FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
|
||||||
|
FieldSet(
|
||||||
|
'type', 'width', 'starting_unit', 'u_height',
|
||||||
|
InlineFields('outer_width', 'outer_depth', 'outer_unit', label=_('Outer Dimensions')),
|
||||||
|
InlineFields('weight', 'max_weight', 'weight_unit', label=_('Weight')),
|
||||||
|
'mounting_depth', 'desc_units', name=_('Dimensions')
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Rack
|
model = Rack
|
||||||
fields = [
|
fields = [
|
||||||
@ -256,8 +264,8 @@ class RackReservationForm(TenancyForm, NetBoxModelForm):
|
|||||||
comments = CommentField()
|
comments = CommentField()
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Reservation'), ('rack', 'units', 'user', 'description', 'tags')),
|
FieldSet('rack', 'units', 'user', 'description', 'tags', name=_('Reservation')),
|
||||||
(_('Tenancy'), ('tenant_group', 'tenant')),
|
FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -271,9 +279,7 @@ class ManufacturerForm(NetBoxModelForm):
|
|||||||
slug = SlugField()
|
slug = SlugField()
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Manufacturer'), (
|
FieldSet('name', 'slug', 'description', 'tags', name=_('Manufacturer')),
|
||||||
'name', 'slug', 'description', 'tags',
|
|
||||||
)),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -304,12 +310,12 @@ class DeviceTypeForm(NetBoxModelForm):
|
|||||||
comments = CommentField()
|
comments = CommentField()
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Device Type'), ('manufacturer', 'model', 'slug', 'default_platform', 'description', 'tags')),
|
FieldSet('manufacturer', 'model', 'slug', 'default_platform', 'description', 'tags', name=_('Device Type')),
|
||||||
(_('Chassis'), (
|
FieldSet(
|
||||||
'u_height', 'exclude_from_utilization', 'is_full_depth', 'part_number', 'subdevice_role', 'airflow',
|
'u_height', 'exclude_from_utilization', 'is_full_depth', 'part_number', 'subdevice_role', 'airflow',
|
||||||
'weight', 'weight_unit',
|
'weight', 'weight_unit', name=_('Chassis')
|
||||||
)),
|
),
|
||||||
(_('Images'), ('front_image', 'rear_image')),
|
FieldSet('front_image', 'rear_image', name=_('Images')),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -337,8 +343,8 @@ class ModuleTypeForm(NetBoxModelForm):
|
|||||||
comments = CommentField()
|
comments = CommentField()
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Module Type'), ('manufacturer', 'model', 'part_number', 'description', 'tags')),
|
FieldSet('manufacturer', 'model', 'part_number', 'description', 'tags', name=_('Module Type')),
|
||||||
(_('Weight'), ('weight', 'weight_unit'))
|
FieldSet('weight', 'weight_unit', name=_('Weight'))
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -357,9 +363,9 @@ class DeviceRoleForm(NetBoxModelForm):
|
|||||||
slug = SlugField()
|
slug = SlugField()
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Device Role'), (
|
FieldSet(
|
||||||
'name', 'slug', 'color', 'vm_role', 'config_template', 'description', 'tags',
|
'name', 'slug', 'color', 'vm_role', 'config_template', 'description', 'tags', name=_('Device Role')
|
||||||
)),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -386,7 +392,7 @@ class PlatformForm(NetBoxModelForm):
|
|||||||
)
|
)
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Platform'), ('name', 'slug', 'manufacturer', 'config_template', 'description', 'tags')),
|
FieldSet('name', 'slug', 'manufacturer', 'config_template', 'description', 'tags', name=_('Platform')),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -601,10 +607,8 @@ class ModuleForm(ModuleCommonForm, NetBoxModelForm):
|
|||||||
)
|
)
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Module'), ('device', 'module_bay', 'module_type', 'status', 'description', 'tags')),
|
FieldSet('device', 'module_bay', 'module_type', 'status', 'description', 'tags', name=_('Module')),
|
||||||
(_('Hardware'), (
|
FieldSet('serial', 'asset_tag', 'replicate_components', 'adopt_components', name=_('Hardware')),
|
||||||
'serial', 'asset_tag', 'replicate_components', 'adopt_components',
|
|
||||||
)),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -658,7 +662,7 @@ class PowerPanelForm(NetBoxModelForm):
|
|||||||
comments = CommentField()
|
comments = CommentField()
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
('Power Panel', ('site', 'location', 'name', 'description', 'tags')),
|
FieldSet('site', 'location', 'name', 'description', 'tags', name=_('Power Panel')),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -683,9 +687,12 @@ class PowerFeedForm(TenancyForm, NetBoxModelForm):
|
|||||||
comments = CommentField()
|
comments = CommentField()
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Power Feed'), ('power_panel', 'rack', 'name', 'status', 'type', 'description', 'mark_connected', 'tags')),
|
FieldSet(
|
||||||
(_('Characteristics'), ('supply', 'voltage', 'amperage', 'phase', 'max_utilization')),
|
'power_panel', 'rack', 'name', 'status', 'type', 'description', 'mark_connected', 'tags',
|
||||||
(_('Tenancy'), ('tenant_group', 'tenant')),
|
name=_('Power Feed')
|
||||||
|
),
|
||||||
|
FieldSet('supply', 'voltage', 'amperage', 'phase', 'max_utilization', name=_('Characteristics')),
|
||||||
|
FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -832,7 +839,7 @@ class ModularComponentTemplateForm(ComponentTemplateForm):
|
|||||||
|
|
||||||
class ConsolePortTemplateForm(ModularComponentTemplateForm):
|
class ConsolePortTemplateForm(ModularComponentTemplateForm):
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('device_type', 'module_type', 'name', 'label', 'type', 'description')),
|
FieldSet('device_type', 'module_type', 'name', 'label', 'type', 'description'),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -844,7 +851,7 @@ class ConsolePortTemplateForm(ModularComponentTemplateForm):
|
|||||||
|
|
||||||
class ConsoleServerPortTemplateForm(ModularComponentTemplateForm):
|
class ConsoleServerPortTemplateForm(ModularComponentTemplateForm):
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('device_type', 'module_type', 'name', 'label', 'type', 'description')),
|
FieldSet('device_type', 'module_type', 'name', 'label', 'type', 'description'),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -856,9 +863,9 @@ class ConsoleServerPortTemplateForm(ModularComponentTemplateForm):
|
|||||||
|
|
||||||
class PowerPortTemplateForm(ModularComponentTemplateForm):
|
class PowerPortTemplateForm(ModularComponentTemplateForm):
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, (
|
FieldSet(
|
||||||
'device_type', 'module_type', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description',
|
'device_type', 'module_type', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description',
|
||||||
)),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -879,7 +886,7 @@ class PowerOutletTemplateForm(ModularComponentTemplateForm):
|
|||||||
)
|
)
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('device_type', 'module_type', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description')),
|
FieldSet('device_type', 'module_type', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description'),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -901,9 +908,11 @@ class InterfaceTemplateForm(ModularComponentTemplateForm):
|
|||||||
)
|
)
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('device_type', 'module_type', 'name', 'label', 'type', 'enabled', 'mgmt_only', 'description', 'bridge')),
|
FieldSet(
|
||||||
(_('PoE'), ('poe_mode', 'poe_type')),
|
'device_type', 'module_type', 'name', 'label', 'type', 'enabled', 'mgmt_only', 'description', 'bridge',
|
||||||
(_('Wireless'), ('rf_role',)),
|
),
|
||||||
|
FieldSet('poe_mode', 'poe_type', name=_('PoE')),
|
||||||
|
FieldSet('rf_role', name=_('Wireless')),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -925,10 +934,10 @@ class FrontPortTemplateForm(ModularComponentTemplateForm):
|
|||||||
)
|
)
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, (
|
FieldSet(
|
||||||
'device_type', 'module_type', 'name', 'label', 'type', 'color', 'rear_port', 'rear_port_position',
|
'device_type', 'module_type', 'name', 'label', 'type', 'color', 'rear_port', 'rear_port_position',
|
||||||
'description',
|
'description',
|
||||||
)),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -941,7 +950,7 @@ class FrontPortTemplateForm(ModularComponentTemplateForm):
|
|||||||
|
|
||||||
class RearPortTemplateForm(ModularComponentTemplateForm):
|
class RearPortTemplateForm(ModularComponentTemplateForm):
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('device_type', 'module_type', 'name', 'label', 'type', 'color', 'positions', 'description')),
|
FieldSet('device_type', 'module_type', 'name', 'label', 'type', 'color', 'positions', 'description'),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -953,7 +962,7 @@ class RearPortTemplateForm(ModularComponentTemplateForm):
|
|||||||
|
|
||||||
class ModuleBayTemplateForm(ComponentTemplateForm):
|
class ModuleBayTemplateForm(ComponentTemplateForm):
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('device_type', 'name', 'label', 'position', 'description')),
|
FieldSet('device_type', 'name', 'label', 'position', 'description'),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -965,7 +974,7 @@ class ModuleBayTemplateForm(ComponentTemplateForm):
|
|||||||
|
|
||||||
class DeviceBayTemplateForm(ComponentTemplateForm):
|
class DeviceBayTemplateForm(ComponentTemplateForm):
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('device_type', 'name', 'label', 'description')),
|
FieldSet('device_type', 'name', 'label', 'description'),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -1006,10 +1015,10 @@ class InventoryItemTemplateForm(ComponentTemplateForm):
|
|||||||
)
|
)
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, (
|
FieldSet(
|
||||||
'device_type', 'parent', 'name', 'label', 'role', 'manufacturer', 'part_id', 'description',
|
'device_type', 'parent', 'name', 'label', 'role', 'manufacturer', 'part_id', 'description',
|
||||||
'component_type', 'component_id',
|
'component_type', 'component_id',
|
||||||
)),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -1052,9 +1061,9 @@ class ModularDeviceComponentForm(DeviceComponentForm):
|
|||||||
|
|
||||||
class ConsolePortForm(ModularDeviceComponentForm):
|
class ConsolePortForm(ModularDeviceComponentForm):
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, (
|
FieldSet(
|
||||||
'device', 'module', 'name', 'label', 'type', 'speed', 'mark_connected', 'description', 'tags',
|
'device', 'module', 'name', 'label', 'type', 'speed', 'mark_connected', 'description', 'tags',
|
||||||
)),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -1065,11 +1074,10 @@ class ConsolePortForm(ModularDeviceComponentForm):
|
|||||||
|
|
||||||
|
|
||||||
class ConsoleServerPortForm(ModularDeviceComponentForm):
|
class ConsoleServerPortForm(ModularDeviceComponentForm):
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, (
|
FieldSet(
|
||||||
'device', 'module', 'name', 'label', 'type', 'speed', 'mark_connected', 'description', 'tags',
|
'device', 'module', 'name', 'label', 'type', 'speed', 'mark_connected', 'description', 'tags',
|
||||||
)),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -1080,12 +1088,11 @@ class ConsoleServerPortForm(ModularDeviceComponentForm):
|
|||||||
|
|
||||||
|
|
||||||
class PowerPortForm(ModularDeviceComponentForm):
|
class PowerPortForm(ModularDeviceComponentForm):
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, (
|
FieldSet(
|
||||||
'device', 'module', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'mark_connected',
|
'device', 'module', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'mark_connected',
|
||||||
'description', 'tags',
|
'description', 'tags',
|
||||||
)),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -1107,10 +1114,10 @@ class PowerOutletForm(ModularDeviceComponentForm):
|
|||||||
)
|
)
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, (
|
FieldSet(
|
||||||
'device', 'module', 'name', 'label', 'type', 'power_port', 'feed_leg', 'mark_connected', 'description',
|
'device', 'module', 'name', 'label', 'type', 'power_port', 'feed_leg', 'mark_connected', 'description',
|
||||||
'tags',
|
'tags',
|
||||||
)),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -1206,15 +1213,18 @@ class InterfaceForm(InterfaceCommonForm, ModularDeviceComponentForm):
|
|||||||
)
|
)
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Interface'), ('device', 'module', 'name', 'label', 'type', 'speed', 'duplex', 'description', 'tags')),
|
FieldSet(
|
||||||
(_('Addressing'), ('vrf', 'mac_address', 'wwn')),
|
'device', 'module', 'name', 'label', 'type', 'speed', 'duplex', 'description', 'tags', name=_('Interface')
|
||||||
(_('Operation'), ('vdcs', 'mtu', 'tx_power', 'enabled', 'mgmt_only', 'mark_connected')),
|
),
|
||||||
(_('Related Interfaces'), ('parent', 'bridge', 'lag')),
|
FieldSet('vrf', 'mac_address', 'wwn', name=_('Addressing')),
|
||||||
(_('PoE'), ('poe_mode', 'poe_type')),
|
FieldSet('vdcs', 'mtu', 'tx_power', 'enabled', 'mgmt_only', 'mark_connected', name=_('Operation')),
|
||||||
(_('802.1Q Switching'), ('mode', 'vlan_group', 'untagged_vlan', 'tagged_vlans')),
|
FieldSet('parent', 'bridge', 'lag', name=_('Related Interfaces')),
|
||||||
(_('Wireless'), (
|
FieldSet('poe_mode', 'poe_type', name=_('PoE')),
|
||||||
|
FieldSet('mode', 'vlan_group', 'untagged_vlan', 'tagged_vlans', name=_('802.1Q Switching')),
|
||||||
|
FieldSet(
|
||||||
'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'wireless_lan_group', 'wireless_lans',
|
'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'wireless_lan_group', 'wireless_lans',
|
||||||
)),
|
name=_('Wireless')
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -1245,10 +1255,10 @@ class FrontPortForm(ModularDeviceComponentForm):
|
|||||||
)
|
)
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, (
|
FieldSet(
|
||||||
'device', 'module', 'name', 'label', 'type', 'color', 'rear_port', 'rear_port_position', 'mark_connected',
|
'device', 'module', 'name', 'label', 'type', 'color', 'rear_port', 'rear_port_position', 'mark_connected',
|
||||||
'description', 'tags',
|
'description', 'tags',
|
||||||
)),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -1261,9 +1271,9 @@ class FrontPortForm(ModularDeviceComponentForm):
|
|||||||
|
|
||||||
class RearPortForm(ModularDeviceComponentForm):
|
class RearPortForm(ModularDeviceComponentForm):
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, (
|
FieldSet(
|
||||||
'device', 'module', 'name', 'label', 'type', 'color', 'positions', 'mark_connected', 'description', 'tags',
|
'device', 'module', 'name', 'label', 'type', 'color', 'positions', 'mark_connected', 'description', 'tags',
|
||||||
)),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -1275,7 +1285,7 @@ class RearPortForm(ModularDeviceComponentForm):
|
|||||||
|
|
||||||
class ModuleBayForm(DeviceComponentForm):
|
class ModuleBayForm(DeviceComponentForm):
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('device', 'name', 'label', 'position', 'description', 'tags',)),
|
FieldSet('device', 'name', 'label', 'position', 'description', 'tags',),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -1287,7 +1297,7 @@ class ModuleBayForm(DeviceComponentForm):
|
|||||||
|
|
||||||
class DeviceBayForm(DeviceComponentForm):
|
class DeviceBayForm(DeviceComponentForm):
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('device', 'name', 'label', 'description', 'tags',)),
|
FieldSet('device', 'name', 'label', 'description', 'tags',),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -1395,8 +1405,20 @@ class InventoryItemForm(DeviceComponentForm):
|
|||||||
)
|
)
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Inventory Item'), ('device', 'parent', 'name', 'label', 'role', 'description', 'tags')),
|
FieldSet('device', 'parent', 'name', 'label', 'role', 'description', 'tags', name=_('Inventory Item')),
|
||||||
(_('Hardware'), ('manufacturer', 'part_id', 'serial', 'asset_tag')),
|
FieldSet('manufacturer', 'part_id', 'serial', 'asset_tag', name=_('Hardware')),
|
||||||
|
FieldSet(
|
||||||
|
TabbedGroups(
|
||||||
|
FieldSet('interface', name=_('Interface')),
|
||||||
|
FieldSet('consoleport', name=_('Console Port')),
|
||||||
|
FieldSet('consoleserverport', name=_('Console Server Port')),
|
||||||
|
FieldSet('frontport', name=_('Front Port')),
|
||||||
|
FieldSet('rearport', name=_('Rear Port')),
|
||||||
|
FieldSet('powerport', name=_('Power Port')),
|
||||||
|
FieldSet('poweroutlet', name=_('Power Outlet')),
|
||||||
|
),
|
||||||
|
name=_('Component Assignment')
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -1412,22 +1434,17 @@ class InventoryItemForm(DeviceComponentForm):
|
|||||||
component_type = initial.get('component_type')
|
component_type = initial.get('component_type')
|
||||||
component_id = initial.get('component_id')
|
component_id = initial.get('component_id')
|
||||||
|
|
||||||
# Used for picking the default active tab for component selection
|
|
||||||
self.no_component = True
|
|
||||||
|
|
||||||
if instance:
|
if instance:
|
||||||
# When editing set the initial value for component selectin
|
# When editing set the initial value for component selection
|
||||||
for component_model in ContentType.objects.filter(MODULAR_COMPONENT_MODELS):
|
for component_model in ContentType.objects.filter(MODULAR_COMPONENT_MODELS):
|
||||||
if type(instance.component) is component_model.model_class():
|
if type(instance.component) is component_model.model_class():
|
||||||
initial[component_model.model] = instance.component
|
initial[component_model.model] = instance.component
|
||||||
self.no_component = False
|
|
||||||
break
|
break
|
||||||
elif component_type and component_id:
|
elif component_type and component_id:
|
||||||
# When adding the InventoryItem from a component page
|
# When adding the InventoryItem from a component page
|
||||||
if content_type := ContentType.objects.filter(MODULAR_COMPONENT_MODELS).filter(pk=component_type).first():
|
if content_type := ContentType.objects.filter(MODULAR_COMPONENT_MODELS).filter(pk=component_type).first():
|
||||||
if component := content_type.model_class().objects.filter(pk=component_id).first():
|
if component := content_type.model_class().objects.filter(pk=component_id).first():
|
||||||
initial[content_type.model] = component
|
initial[content_type.model] = component
|
||||||
self.no_component = False
|
|
||||||
|
|
||||||
kwargs['initial'] = initial
|
kwargs['initial'] = initial
|
||||||
|
|
||||||
@ -1461,9 +1478,7 @@ class InventoryItemRoleForm(NetBoxModelForm):
|
|||||||
slug = SlugField()
|
slug = SlugField()
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Inventory Item Role'), (
|
FieldSet('name', 'slug', 'color', 'description', 'tags', name=_('Inventory Item Role')),
|
||||||
'name', 'slug', 'color', 'description', 'tags',
|
|
||||||
)),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -1499,8 +1514,11 @@ class VirtualDeviceContextForm(TenancyForm, NetBoxModelForm):
|
|||||||
)
|
)
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Virtual Device Context'), ('device', 'name', 'status', 'identifier', 'primary_ip4', 'primary_ip6', 'tags')),
|
FieldSet(
|
||||||
(_('Tenancy'), ('tenant_group', 'tenant'))
|
'device', 'name', 'status', 'identifier', 'primary_ip4', 'primary_ip6', 'tags',
|
||||||
|
name=_('Virtual Device Context')
|
||||||
|
),
|
||||||
|
FieldSet('tenant_group', 'tenant', name=_('Tenancy'))
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -4,6 +4,7 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
from dcim.models import *
|
from dcim.models import *
|
||||||
from netbox.forms import NetBoxModelForm
|
from netbox.forms import NetBoxModelForm
|
||||||
from utilities.forms.fields import DynamicModelChoiceField, DynamicModelMultipleChoiceField, ExpandableNameField
|
from utilities.forms.fields import DynamicModelChoiceField, DynamicModelMultipleChoiceField, ExpandableNameField
|
||||||
|
from utilities.forms.rendering import FieldSet
|
||||||
from utilities.forms.widgets import APISelect
|
from utilities.forms.widgets import APISelect
|
||||||
from . import model_forms
|
from . import model_forms
|
||||||
|
|
||||||
@ -113,7 +114,7 @@ class FrontPortTemplateCreateForm(ComponentCreateForm, model_forms.FrontPortTemp
|
|||||||
|
|
||||||
# Override fieldsets from FrontPortTemplateForm to omit rear_port_position
|
# Override fieldsets from FrontPortTemplateForm to omit rear_port_position
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('device_type', 'module_type', 'name', 'label', 'type', 'color', 'rear_port', 'description')),
|
FieldSet('device_type', 'module_type', 'name', 'label', 'type', 'color', 'rear_port', 'description'),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta(model_forms.FrontPortTemplateForm.Meta):
|
class Meta(model_forms.FrontPortTemplateForm.Meta):
|
||||||
@ -274,9 +275,9 @@ class FrontPortCreateForm(ComponentCreateForm, model_forms.FrontPortForm):
|
|||||||
|
|
||||||
# Override fieldsets from FrontPortForm to omit rear_port_position
|
# Override fieldsets from FrontPortForm to omit rear_port_position
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, (
|
FieldSet(
|
||||||
'device', 'module', 'name', 'label', 'type', 'color', 'rear_port', 'mark_connected', 'description', 'tags',
|
'device', 'module', 'name', 'label', 'type', 'color', 'rear_port', 'mark_connected', 'description', 'tags',
|
||||||
)),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta(model_forms.FrontPortForm.Meta):
|
class Meta(model_forms.FrontPortForm.Meta):
|
||||||
|
18
netbox/dcim/migrations/0186_location_facility.py
Normal file
18
netbox/dcim/migrations/0186_location_facility.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 4.2.4 on 2024-03-17 02:21
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('dcim', '0185_gfk_indexes'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='location',
|
||||||
|
name='facility',
|
||||||
|
field=models.CharField(blank=True, max_length=50),
|
||||||
|
),
|
||||||
|
]
|
@ -229,15 +229,16 @@ class DeviceType(ImageAttachmentsMixin, PrimaryModel, WeightMixin):
|
|||||||
'manufacturer': self.manufacturer.name,
|
'manufacturer': self.manufacturer.name,
|
||||||
'model': self.model,
|
'model': self.model,
|
||||||
'slug': self.slug,
|
'slug': self.slug,
|
||||||
|
'description': self.description,
|
||||||
'default_platform': self.default_platform.name if self.default_platform else None,
|
'default_platform': self.default_platform.name if self.default_platform else None,
|
||||||
'part_number': self.part_number,
|
'part_number': self.part_number,
|
||||||
'u_height': float(self.u_height),
|
'u_height': float(self.u_height),
|
||||||
'is_full_depth': self.is_full_depth,
|
'is_full_depth': self.is_full_depth,
|
||||||
'subdevice_role': self.subdevice_role,
|
'subdevice_role': self.subdevice_role,
|
||||||
'airflow': self.airflow,
|
'airflow': self.airflow,
|
||||||
'comments': self.comments,
|
|
||||||
'weight': float(self.weight) if self.weight is not None else None,
|
'weight': float(self.weight) if self.weight is not None else None,
|
||||||
'weight_unit': self.weight_unit,
|
'weight_unit': self.weight_unit,
|
||||||
|
'comments': self.comments,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Component templates
|
# Component templates
|
||||||
@ -415,9 +416,10 @@ class ModuleType(ImageAttachmentsMixin, PrimaryModel, WeightMixin):
|
|||||||
'manufacturer': self.manufacturer.name,
|
'manufacturer': self.manufacturer.name,
|
||||||
'model': self.model,
|
'model': self.model,
|
||||||
'part_number': self.part_number,
|
'part_number': self.part_number,
|
||||||
'comments': self.comments,
|
'description': self.description,
|
||||||
'weight': float(self.weight) if self.weight is not None else None,
|
'weight': float(self.weight) if self.weight is not None else None,
|
||||||
'weight_unit': self.weight_unit,
|
'weight_unit': self.weight_unit,
|
||||||
|
'comments': self.comments,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Component templates
|
# Component templates
|
||||||
|
@ -275,6 +275,12 @@ class Location(ContactsMixin, ImageAttachmentsMixin, NestedGroupModel):
|
|||||||
blank=True,
|
blank=True,
|
||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
|
facility = models.CharField(
|
||||||
|
verbose_name=_('facility'),
|
||||||
|
max_length=50,
|
||||||
|
blank=True,
|
||||||
|
help_text=_('Local facility ID or description')
|
||||||
|
)
|
||||||
|
|
||||||
# Generic relations
|
# Generic relations
|
||||||
vlan_groups = GenericRelation(
|
vlan_groups = GenericRelation(
|
||||||
@ -284,7 +290,7 @@ class Location(ContactsMixin, ImageAttachmentsMixin, NestedGroupModel):
|
|||||||
related_query_name='location'
|
related_query_name='location'
|
||||||
)
|
)
|
||||||
|
|
||||||
clone_fields = ('site', 'parent', 'status', 'tenant', 'description')
|
clone_fields = ('site', 'parent', 'status', 'tenant', 'facility', 'description')
|
||||||
prerequisite_models = (
|
prerequisite_models = (
|
||||||
'dcim.Site',
|
'dcim.Site',
|
||||||
)
|
)
|
||||||
|
@ -132,10 +132,11 @@ class LocationIndex(SearchIndex):
|
|||||||
model = models.Location
|
model = models.Location
|
||||||
fields = (
|
fields = (
|
||||||
('name', 100),
|
('name', 100),
|
||||||
|
('facility', 100),
|
||||||
('slug', 110),
|
('slug', 110),
|
||||||
('description', 500),
|
('description', 500),
|
||||||
)
|
)
|
||||||
display_attrs = ('site', 'status', 'tenant', 'description')
|
display_attrs = ('site', 'status', 'tenant', 'facility', 'description')
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
|
@ -210,6 +210,10 @@ class DeviceTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
|
|||||||
linkify=True,
|
linkify=True,
|
||||||
verbose_name=_('Type')
|
verbose_name=_('Type')
|
||||||
)
|
)
|
||||||
|
platform = tables.Column(
|
||||||
|
linkify=True,
|
||||||
|
verbose_name=_('Platform')
|
||||||
|
)
|
||||||
primary_ip = tables.Column(
|
primary_ip = tables.Column(
|
||||||
linkify=True,
|
linkify=True,
|
||||||
order_by=('primary_ip4', 'primary_ip6'),
|
order_by=('primary_ip4', 'primary_ip6'),
|
||||||
@ -294,7 +298,7 @@ class DeviceTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
|
|||||||
model = models.Device
|
model = models.Device
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'id', 'name', 'status', 'tenant', 'tenant_group', 'role', 'manufacturer', 'device_type',
|
'pk', 'id', 'name', 'status', 'tenant', 'tenant_group', 'role', 'manufacturer', 'device_type',
|
||||||
'platform', 'serial', 'asset_tag', 'region', 'site_group', 'site', 'location', 'rack', 'parent_device',
|
'serial', 'asset_tag', 'region', 'site_group', 'site', 'location', 'rack', 'parent_device',
|
||||||
'device_bay_position', 'position', 'face', 'latitude', 'longitude', 'airflow', 'primary_ip', 'primary_ip4',
|
'device_bay_position', 'position', 'face', 'latitude', 'longitude', 'airflow', 'primary_ip', 'primary_ip4',
|
||||||
'primary_ip6', 'oob_ip', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'description',
|
'primary_ip6', 'oob_ip', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'description',
|
||||||
'config_template', 'comments', 'contacts', 'tags', 'created', 'last_updated',
|
'config_template', 'comments', 'contacts', 'tags', 'created', 'last_updated',
|
||||||
|
@ -152,7 +152,9 @@ class LocationTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
|
|||||||
class Meta(NetBoxTable.Meta):
|
class Meta(NetBoxTable.Meta):
|
||||||
model = Location
|
model = Location
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'id', 'name', 'site', 'status', 'tenant', 'tenant_group', 'rack_count', 'device_count', 'description',
|
'pk', 'id', 'name', 'site', 'status', 'facility', 'tenant', 'tenant_group', 'rack_count', 'device_count',
|
||||||
'slug', 'contacts', 'tags', 'actions', 'created', 'last_updated',
|
'description', 'slug', 'contacts', 'tags', 'actions', 'created', 'last_updated',
|
||||||
|
)
|
||||||
|
default_columns = (
|
||||||
|
'pk', 'name', 'site', 'status', 'facility', 'tenant', 'rack_count', 'device_count', 'description'
|
||||||
)
|
)
|
||||||
default_columns = ('pk', 'name', 'site', 'status', 'tenant', 'rack_count', 'device_count', 'description')
|
|
||||||
|
@ -64,21 +64,32 @@ class RegionTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
|
|
||||||
regions = (
|
parent_regions = (
|
||||||
Region(name='Region 1', slug='region-1', description='foobar1'),
|
Region(name='Region 1', slug='region-1', description='foobar1'),
|
||||||
Region(name='Region 2', slug='region-2', description='foobar2'),
|
Region(name='Region 2', slug='region-2', description='foobar2'),
|
||||||
Region(name='Region 3', slug='region-3', description='foobar3'),
|
Region(name='Region 3', slug='region-3', description='foobar3'),
|
||||||
)
|
)
|
||||||
|
for region in parent_regions:
|
||||||
|
region.save()
|
||||||
|
|
||||||
|
regions = (
|
||||||
|
Region(name='Region 1A', slug='region-1a', parent=parent_regions[0]),
|
||||||
|
Region(name='Region 1B', slug='region-1b', parent=parent_regions[0]),
|
||||||
|
Region(name='Region 2A', slug='region-2a', parent=parent_regions[1]),
|
||||||
|
Region(name='Region 2B', slug='region-2b', parent=parent_regions[1]),
|
||||||
|
Region(name='Region 3A', slug='region-3a', parent=parent_regions[2]),
|
||||||
|
Region(name='Region 3B', slug='region-3b', parent=parent_regions[2]),
|
||||||
|
)
|
||||||
for region in regions:
|
for region in regions:
|
||||||
region.save()
|
region.save()
|
||||||
|
|
||||||
child_regions = (
|
child_regions = (
|
||||||
Region(name='Region 1A', slug='region-1a', parent=regions[0]),
|
Region(name='Region 1A1', slug='region-1a1', parent=regions[0]),
|
||||||
Region(name='Region 1B', slug='region-1b', parent=regions[0]),
|
Region(name='Region 1B1', slug='region-1b1', parent=regions[1]),
|
||||||
Region(name='Region 2A', slug='region-2a', parent=regions[1]),
|
Region(name='Region 2A1', slug='region-2a1', parent=regions[2]),
|
||||||
Region(name='Region 2B', slug='region-2b', parent=regions[1]),
|
Region(name='Region 2B1', slug='region-2b1', parent=regions[3]),
|
||||||
Region(name='Region 3A', slug='region-3a', parent=regions[2]),
|
Region(name='Region 3A1', slug='region-3a1', parent=regions[4]),
|
||||||
Region(name='Region 3B', slug='region-3b', parent=regions[2]),
|
Region(name='Region 3B1', slug='region-3b1', parent=regions[5]),
|
||||||
)
|
)
|
||||||
for region in child_regions:
|
for region in child_regions:
|
||||||
region.save()
|
region.save()
|
||||||
@ -100,12 +111,19 @@ class RegionTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
def test_parent(self):
|
def test_parent(self):
|
||||||
parent_regions = Region.objects.filter(parent__isnull=True)[:2]
|
regions = Region.objects.filter(parent__isnull=True)[:2]
|
||||||
params = {'parent_id': [parent_regions[0].pk, parent_regions[1].pk]}
|
params = {'parent_id': [regions[0].pk, regions[1].pk]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
||||||
params = {'parent': [parent_regions[0].slug, parent_regions[1].slug]}
|
params = {'parent': [regions[0].slug, regions[1].slug]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
||||||
|
|
||||||
|
def test_ancestor(self):
|
||||||
|
regions = Region.objects.filter(parent__isnull=True)[:2]
|
||||||
|
params = {'ancestor_id': [regions[0].pk, regions[1].pk]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 8)
|
||||||
|
params = {'ancestor': [regions[0].slug, regions[1].slug]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 8)
|
||||||
|
|
||||||
|
|
||||||
class SiteGroupTestCase(TestCase, ChangeLoggedFilterSetTests):
|
class SiteGroupTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = SiteGroup.objects.all()
|
queryset = SiteGroup.objects.all()
|
||||||
@ -114,24 +132,35 @@ class SiteGroupTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
|
|
||||||
sitegroups = (
|
parent_groups = (
|
||||||
SiteGroup(name='Site Group 1', slug='site-group-1', description='foobar1'),
|
SiteGroup(name='Site Group 1', slug='site-group-1', description='foobar1'),
|
||||||
SiteGroup(name='Site Group 2', slug='site-group-2', description='foobar2'),
|
SiteGroup(name='Site Group 2', slug='site-group-2', description='foobar2'),
|
||||||
SiteGroup(name='Site Group 3', slug='site-group-3', description='foobar3'),
|
SiteGroup(name='Site Group 3', slug='site-group-3', description='foobar3'),
|
||||||
)
|
)
|
||||||
for sitegroup in sitegroups:
|
for site_group in parent_groups:
|
||||||
sitegroup.save()
|
site_group.save()
|
||||||
|
|
||||||
child_sitegroups = (
|
groups = (
|
||||||
SiteGroup(name='Site Group 1A', slug='site-group-1a', parent=sitegroups[0]),
|
SiteGroup(name='Site Group 1A', slug='site-group-1a', parent=parent_groups[0]),
|
||||||
SiteGroup(name='Site Group 1B', slug='site-group-1b', parent=sitegroups[0]),
|
SiteGroup(name='Site Group 1B', slug='site-group-1b', parent=parent_groups[0]),
|
||||||
SiteGroup(name='Site Group 2A', slug='site-group-2a', parent=sitegroups[1]),
|
SiteGroup(name='Site Group 2A', slug='site-group-2a', parent=parent_groups[1]),
|
||||||
SiteGroup(name='Site Group 2B', slug='site-group-2b', parent=sitegroups[1]),
|
SiteGroup(name='Site Group 2B', slug='site-group-2b', parent=parent_groups[1]),
|
||||||
SiteGroup(name='Site Group 3A', slug='site-group-3a', parent=sitegroups[2]),
|
SiteGroup(name='Site Group 3A', slug='site-group-3a', parent=parent_groups[2]),
|
||||||
SiteGroup(name='Site Group 3B', slug='site-group-3b', parent=sitegroups[2]),
|
SiteGroup(name='Site Group 3B', slug='site-group-3b', parent=parent_groups[2]),
|
||||||
)
|
)
|
||||||
for sitegroup in child_sitegroups:
|
for site_group in groups:
|
||||||
sitegroup.save()
|
site_group.save()
|
||||||
|
|
||||||
|
child_groups = (
|
||||||
|
SiteGroup(name='Site Group 1A1', slug='site-group-1a1', parent=groups[0]),
|
||||||
|
SiteGroup(name='Site Group 1B1', slug='site-group-1b1', parent=groups[1]),
|
||||||
|
SiteGroup(name='Site Group 2A1', slug='site-group-2a1', parent=groups[2]),
|
||||||
|
SiteGroup(name='Site Group 2B1', slug='site-group-2b1', parent=groups[3]),
|
||||||
|
SiteGroup(name='Site Group 3A1', slug='site-group-3a1', parent=groups[4]),
|
||||||
|
SiteGroup(name='Site Group 3B1', slug='site-group-3b1', parent=groups[5]),
|
||||||
|
)
|
||||||
|
for site_group in child_groups:
|
||||||
|
site_group.save()
|
||||||
|
|
||||||
def test_q(self):
|
def test_q(self):
|
||||||
params = {'q': 'foobar1'}
|
params = {'q': 'foobar1'}
|
||||||
@ -150,16 +179,24 @@ class SiteGroupTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
def test_parent(self):
|
def test_parent(self):
|
||||||
parent_sitegroups = SiteGroup.objects.filter(parent__isnull=True)[:2]
|
site_groups = SiteGroup.objects.filter(parent__isnull=True)[:2]
|
||||||
params = {'parent_id': [parent_sitegroups[0].pk, parent_sitegroups[1].pk]}
|
params = {'parent_id': [site_groups[0].pk, site_groups[1].pk]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
||||||
params = {'parent': [parent_sitegroups[0].slug, parent_sitegroups[1].slug]}
|
params = {'parent': [site_groups[0].slug, site_groups[1].slug]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
||||||
|
|
||||||
|
def test_ancestor(self):
|
||||||
|
site_groups = SiteGroup.objects.filter(parent__isnull=True)[:2]
|
||||||
|
params = {'ancestor_id': [site_groups[0].pk, site_groups[1].pk]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 8)
|
||||||
|
params = {'ancestor': [site_groups[0].slug, site_groups[1].slug]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 8)
|
||||||
|
|
||||||
|
|
||||||
class SiteTestCase(TestCase, ChangeLoggedFilterSetTests):
|
class SiteTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = Site.objects.all()
|
queryset = Site.objects.all()
|
||||||
filterset = SiteFilterSet
|
filterset = SiteFilterSet
|
||||||
|
ignore_fields = ('physical_address', 'shipping_address')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -314,21 +351,29 @@ class LocationTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
Site.objects.bulk_create(sites)
|
Site.objects.bulk_create(sites)
|
||||||
|
|
||||||
parent_locations = (
|
parent_locations = (
|
||||||
Location(name='Parent Location 1', slug='parent-location-1', site=sites[0]),
|
Location(name='Location 1', slug='location-1', site=sites[0]),
|
||||||
Location(name='Parent Location 2', slug='parent-location-2', site=sites[1]),
|
Location(name='Location 2', slug='location-2', site=sites[1]),
|
||||||
Location(name='Parent Location 3', slug='parent-location-3', site=sites[2]),
|
Location(name='Location 3', slug='location-3', site=sites[2]),
|
||||||
)
|
)
|
||||||
for location in parent_locations:
|
for location in parent_locations:
|
||||||
location.save()
|
location.save()
|
||||||
|
|
||||||
locations = (
|
locations = (
|
||||||
Location(name='Location 1', slug='location-1', site=sites[0], parent=parent_locations[0], status=LocationStatusChoices.STATUS_PLANNED, description='foobar1'),
|
Location(name='Location 1A', slug='location-1a', site=sites[0], parent=parent_locations[0], status=LocationStatusChoices.STATUS_PLANNED, facility='Facility 1', description='foobar1'),
|
||||||
Location(name='Location 2', slug='location-2', site=sites[1], parent=parent_locations[1], status=LocationStatusChoices.STATUS_STAGING, description='foobar2'),
|
Location(name='Location 2A', slug='location-2a', site=sites[1], parent=parent_locations[1], status=LocationStatusChoices.STATUS_STAGING, facility='Facility 2', description='foobar2'),
|
||||||
Location(name='Location 3', slug='location-3', site=sites[2], parent=parent_locations[2], status=LocationStatusChoices.STATUS_DECOMMISSIONING, description='foobar3'),
|
Location(name='Location 3A', slug='location-3a', site=sites[2], parent=parent_locations[2], status=LocationStatusChoices.STATUS_DECOMMISSIONING, facility='Facility 3', description='foobar3'),
|
||||||
)
|
)
|
||||||
for location in locations:
|
for location in locations:
|
||||||
location.save()
|
location.save()
|
||||||
|
|
||||||
|
child_locations = (
|
||||||
|
Location(name='Location 1A1', slug='location-1a1', site=sites[0], parent=locations[0]),
|
||||||
|
Location(name='Location 2A1', slug='location-2a1', site=sites[1], parent=locations[1]),
|
||||||
|
Location(name='Location 3A1', slug='location-3a1', site=sites[2], parent=locations[2]),
|
||||||
|
)
|
||||||
|
for location in child_locations:
|
||||||
|
location.save()
|
||||||
|
|
||||||
def test_q(self):
|
def test_q(self):
|
||||||
params = {'q': 'foobar1'}
|
params = {'q': 'foobar1'}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
@ -345,6 +390,10 @@ class LocationTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
params = {'status': [LocationStatusChoices.STATUS_PLANNED, LocationStatusChoices.STATUS_STAGING]}
|
params = {'status': [LocationStatusChoices.STATUS_PLANNED, LocationStatusChoices.STATUS_STAGING]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
def test_facility(self):
|
||||||
|
params = {'facility': ['Facility 1', 'Facility 2']}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
def test_description(self):
|
def test_description(self):
|
||||||
params = {'description': ['foobar1', 'foobar2']}
|
params = {'description': ['foobar1', 'foobar2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
@ -352,31 +401,38 @@ class LocationTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
def test_region(self):
|
def test_region(self):
|
||||||
regions = Region.objects.all()[:2]
|
regions = Region.objects.all()[:2]
|
||||||
params = {'region_id': [regions[0].pk, regions[1].pk]}
|
params = {'region_id': [regions[0].pk, regions[1].pk]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
|
||||||
params = {'region': [regions[0].slug, regions[1].slug]}
|
params = {'region': [regions[0].slug, regions[1].slug]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
|
||||||
|
|
||||||
def test_site_group(self):
|
def test_site_group(self):
|
||||||
site_groups = SiteGroup.objects.all()[:2]
|
site_groups = SiteGroup.objects.all()[:2]
|
||||||
params = {'site_group_id': [site_groups[0].pk, site_groups[1].pk]}
|
params = {'site_group_id': [site_groups[0].pk, site_groups[1].pk]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
|
||||||
params = {'site_group': [site_groups[0].slug, site_groups[1].slug]}
|
params = {'site_group': [site_groups[0].slug, site_groups[1].slug]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
|
||||||
|
|
||||||
def test_site(self):
|
def test_site(self):
|
||||||
sites = Site.objects.all()[:2]
|
sites = Site.objects.all()[:2]
|
||||||
params = {'site_id': [sites[0].pk, sites[1].pk]}
|
params = {'site_id': [sites[0].pk, sites[1].pk]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
|
||||||
params = {'site': [sites[0].slug, sites[1].slug]}
|
params = {'site': [sites[0].slug, sites[1].slug]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
|
||||||
|
|
||||||
def test_parent(self):
|
def test_parent(self):
|
||||||
parent_groups = Location.objects.filter(name__startswith='Parent')[:2]
|
locations = Location.objects.filter(parent__isnull=True)[:2]
|
||||||
params = {'parent_id': [parent_groups[0].pk, parent_groups[1].pk]}
|
params = {'parent_id': [locations[0].pk, locations[1].pk]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
params = {'parent': [parent_groups[0].slug, parent_groups[1].slug]}
|
params = {'parent': [locations[0].slug, locations[1].slug]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
def test_ancestor(self):
|
||||||
|
locations = Location.objects.filter(parent__isnull=True)[:2]
|
||||||
|
params = {'ancestor_id': [locations[0].pk, locations[1].pk]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
||||||
|
params = {'ancestor': [locations[0].slug, locations[1].slug]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
||||||
|
|
||||||
|
|
||||||
class RackRoleTestCase(TestCase, ChangeLoggedFilterSetTests):
|
class RackRoleTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = RackRole.objects.all()
|
queryset = RackRole.objects.all()
|
||||||
@ -416,6 +472,7 @@ class RackRoleTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
class RackTestCase(TestCase, ChangeLoggedFilterSetTests):
|
class RackTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = Rack.objects.all()
|
queryset = Rack.objects.all()
|
||||||
filterset = RackFilterSet
|
filterset = RackFilterSet
|
||||||
|
ignore_fields = ('units',)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -675,6 +732,7 @@ class RackTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
class RackReservationTestCase(TestCase, ChangeLoggedFilterSetTests):
|
class RackReservationTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = RackReservation.objects.all()
|
queryset = RackReservation.objects.all()
|
||||||
filterset = RackReservationFilterSet
|
filterset = RackReservationFilterSet
|
||||||
|
ignore_fields = ('units',)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -838,6 +896,7 @@ class ManufacturerTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
class DeviceTypeTestCase(TestCase, ChangeLoggedFilterSetTests):
|
class DeviceTypeTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = DeviceType.objects.all()
|
queryset = DeviceType.objects.all()
|
||||||
filterset = DeviceTypeFilterSet
|
filterset = DeviceTypeFilterSet
|
||||||
|
ignore_fields = ('front_image', 'rear_image')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -1829,6 +1888,7 @@ class PlatformTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
class DeviceTestCase(TestCase, ChangeLoggedFilterSetTests):
|
class DeviceTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = Device.objects.all()
|
queryset = Device.objects.all()
|
||||||
filterset = DeviceFilterSet
|
filterset = DeviceFilterSet
|
||||||
|
ignore_fields = ('local_context_data', 'oob_ip', 'primary_ip4', 'primary_ip6', 'vc_master_for')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -2281,6 +2341,7 @@ class DeviceTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
class ModuleTestCase(TestCase, ChangeLoggedFilterSetTests):
|
class ModuleTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = Module.objects.all()
|
queryset = Module.objects.all()
|
||||||
filterset = ModuleFilterSet
|
filterset = ModuleFilterSet
|
||||||
|
ignore_fields = ('local_context_data',)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -3178,6 +3239,7 @@ class PowerOutletTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedF
|
|||||||
class InterfaceTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFilterSetTests):
|
class InterfaceTestCase(TestCase, DeviceComponentFilterSetTests, ChangeLoggedFilterSetTests):
|
||||||
queryset = Interface.objects.all()
|
queryset = Interface.objects.all()
|
||||||
filterset = InterfaceFilterSet
|
filterset = InterfaceFilterSet
|
||||||
|
ignore_fields = ('tagged_vlans', 'untagged_vlan', 'vdcs')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -5281,6 +5343,7 @@ class PowerFeedTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
class VirtualDeviceContextTestCase(TestCase, ChangeLoggedFilterSetTests):
|
class VirtualDeviceContextTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = VirtualDeviceContext.objects.all()
|
queryset = VirtualDeviceContext.objects.all()
|
||||||
filterset = VirtualDeviceContextFilterSet
|
filterset = VirtualDeviceContextFilterSet
|
||||||
|
ignore_fields = ('primary_ip4', 'primary_ip6')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -5350,15 +5413,22 @@ class VirtualDeviceContextTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
VirtualDeviceContext.objects.bulk_create(vdcs)
|
VirtualDeviceContext.objects.bulk_create(vdcs)
|
||||||
|
|
||||||
interfaces = (
|
interfaces = (
|
||||||
Interface(device=devices[0], name='Interface 1', type='virtual'),
|
Interface(device=devices[0], name='Interface 1', type=InterfaceTypeChoices.TYPE_VIRTUAL),
|
||||||
Interface(device=devices[0], name='Interface 2', type='virtual'),
|
Interface(device=devices[0], name='Interface 2', type=InterfaceTypeChoices.TYPE_VIRTUAL),
|
||||||
|
Interface(device=devices[1], name='Interface 3', type=InterfaceTypeChoices.TYPE_VIRTUAL),
|
||||||
|
Interface(device=devices[1], name='Interface 4', type=InterfaceTypeChoices.TYPE_VIRTUAL),
|
||||||
|
Interface(device=devices[2], name='Interface 5', type=InterfaceTypeChoices.TYPE_VIRTUAL),
|
||||||
|
Interface(device=devices[2], name='Interface 6', type=InterfaceTypeChoices.TYPE_VIRTUAL),
|
||||||
)
|
)
|
||||||
Interface.objects.bulk_create(interfaces)
|
Interface.objects.bulk_create(interfaces)
|
||||||
|
|
||||||
interfaces[0].vdcs.set([vdcs[0]])
|
interfaces[0].vdcs.set([vdcs[0]])
|
||||||
interfaces[1].vdcs.set([vdcs[1]])
|
interfaces[1].vdcs.set([vdcs[1]])
|
||||||
|
interfaces[2].vdcs.set([vdcs[2]])
|
||||||
|
interfaces[3].vdcs.set([vdcs[3]])
|
||||||
|
interfaces[4].vdcs.set([vdcs[4]])
|
||||||
|
interfaces[5].vdcs.set([vdcs[5]])
|
||||||
|
|
||||||
addresses = (
|
ip_addresses = (
|
||||||
IPAddress(assigned_object=interfaces[0], address='10.1.1.1/24'),
|
IPAddress(assigned_object=interfaces[0], address='10.1.1.1/24'),
|
||||||
IPAddress(assigned_object=interfaces[1], address='10.1.1.2/24'),
|
IPAddress(assigned_object=interfaces[1], address='10.1.1.2/24'),
|
||||||
IPAddress(assigned_object=None, address='10.1.1.3/24'),
|
IPAddress(assigned_object=None, address='10.1.1.3/24'),
|
||||||
@ -5366,13 +5436,12 @@ class VirtualDeviceContextTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
IPAddress(assigned_object=interfaces[1], address='2001:db8::2/64'),
|
IPAddress(assigned_object=interfaces[1], address='2001:db8::2/64'),
|
||||||
IPAddress(assigned_object=None, address='2001:db8::3/64'),
|
IPAddress(assigned_object=None, address='2001:db8::3/64'),
|
||||||
)
|
)
|
||||||
IPAddress.objects.bulk_create(addresses)
|
IPAddress.objects.bulk_create(ip_addresses)
|
||||||
|
vdcs[0].primary_ip4 = ip_addresses[0]
|
||||||
vdcs[0].primary_ip4 = addresses[0]
|
vdcs[0].primary_ip6 = ip_addresses[3]
|
||||||
vdcs[0].primary_ip6 = addresses[3]
|
|
||||||
vdcs[0].save()
|
vdcs[0].save()
|
||||||
vdcs[1].primary_ip4 = addresses[1]
|
vdcs[1].primary_ip4 = ip_addresses[1]
|
||||||
vdcs[1].primary_ip6 = addresses[4]
|
vdcs[1].primary_ip6 = ip_addresses[4]
|
||||||
vdcs[1].save()
|
vdcs[1].save()
|
||||||
|
|
||||||
def test_q(self):
|
def test_q(self):
|
||||||
@ -5380,8 +5449,11 @@ class VirtualDeviceContextTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
def test_device(self):
|
def test_device(self):
|
||||||
params = {'device': ['Device 1', 'Device 2']}
|
devices = Device.objects.filter(name__in=['Device 1', 'Device 2'])
|
||||||
|
params = {'device': [devices[0].name, devices[1].name]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
|
||||||
|
params = {'device_id': [devices[0].pk, devices[1].pk]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
||||||
|
|
||||||
def test_status(self):
|
def test_status(self):
|
||||||
params = {'status': ['active']}
|
params = {'status': ['active']}
|
||||||
@ -5391,10 +5463,10 @@ class VirtualDeviceContextTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
params = {'description': ['foobar1', 'foobar2']}
|
params = {'description': ['foobar1', 'foobar2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
def test_device_id(self):
|
def test_interface(self):
|
||||||
devices = Device.objects.filter(name__in=['Device 1', 'Device 2'])
|
interfaces = Interface.objects.filter(name__in=['Interface 1', 'Interface 3'])
|
||||||
params = {'device_id': [devices[0].pk, devices[1].pk]}
|
params = {'interface_id': [interfaces[0].pk, interfaces[1].pk]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
def test_has_primary_ip(self):
|
def test_has_primary_ip(self):
|
||||||
params = {'has_primary_ip': True}
|
params = {'has_primary_ip': True}
|
||||||
|
@ -213,6 +213,7 @@ class LocationTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
|
|||||||
'slug': 'location-x',
|
'slug': 'location-x',
|
||||||
'site': site.pk,
|
'site': site.pk,
|
||||||
'status': LocationStatusChoices.STATUS_PLANNED,
|
'status': LocationStatusChoices.STATUS_PLANNED,
|
||||||
|
'facility': 'Facility X',
|
||||||
'tenant': tenant.pk,
|
'tenant': tenant.pk,
|
||||||
'description': 'A new location',
|
'description': 'A new location',
|
||||||
'tags': [t.pk for t in tags],
|
'tags': [t.pk for t in tags],
|
||||||
|
@ -727,7 +727,6 @@ class RackNonRackedView(generic.ObjectChildrenView):
|
|||||||
class RackEditView(generic.ObjectEditView):
|
class RackEditView(generic.ObjectEditView):
|
||||||
queryset = Rack.objects.all()
|
queryset = Rack.objects.all()
|
||||||
form = forms.RackForm
|
form = forms.RackForm
|
||||||
template_name = 'dcim/rack_edit.html'
|
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(Rack, 'delete')
|
@register_model_view(Rack, 'delete')
|
||||||
@ -1079,7 +1078,7 @@ class DeviceTypeInventoryItemsView(DeviceTypeComponentsView):
|
|||||||
tab = ViewTab(
|
tab = ViewTab(
|
||||||
label=_('Inventory Items'),
|
label=_('Inventory Items'),
|
||||||
badge=lambda obj: obj.inventory_item_template_count,
|
badge=lambda obj: obj.inventory_item_template_count,
|
||||||
permission='dcim.view_invenotryitemtemplate',
|
permission='dcim.view_inventoryitemtemplate',
|
||||||
weight=590,
|
weight=590,
|
||||||
hide_if_empty=True
|
hide_if_empty=True
|
||||||
)
|
)
|
||||||
@ -2925,14 +2924,12 @@ class InventoryItemView(generic.ObjectView):
|
|||||||
class InventoryItemEditView(generic.ObjectEditView):
|
class InventoryItemEditView(generic.ObjectEditView):
|
||||||
queryset = InventoryItem.objects.all()
|
queryset = InventoryItem.objects.all()
|
||||||
form = forms.InventoryItemForm
|
form = forms.InventoryItemForm
|
||||||
template_name = 'dcim/inventoryitem_edit.html'
|
|
||||||
|
|
||||||
|
|
||||||
class InventoryItemCreateView(generic.ComponentCreateView):
|
class InventoryItemCreateView(generic.ComponentCreateView):
|
||||||
queryset = InventoryItem.objects.all()
|
queryset = InventoryItem.objects.all()
|
||||||
form = forms.InventoryItemCreateForm
|
form = forms.InventoryItemCreateForm
|
||||||
model_form = forms.InventoryItemForm
|
model_form = forms.InventoryItemForm
|
||||||
template_name = 'dcim/inventoryitem_edit.html'
|
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(InventoryItem, 'delete')
|
@register_model_view(InventoryItem, 'delete')
|
||||||
|
@ -57,10 +57,10 @@ class CustomFieldsDataField(Field):
|
|||||||
for cf in self._get_custom_fields():
|
for cf in self._get_custom_fields():
|
||||||
value = cf.deserialize(obj.get(cf.name))
|
value = cf.deserialize(obj.get(cf.name))
|
||||||
if value is not None and cf.type == CustomFieldTypeChoices.TYPE_OBJECT:
|
if value is not None and cf.type == CustomFieldTypeChoices.TYPE_OBJECT:
|
||||||
serializer = get_serializer_for_model(cf.object_type.model_class())
|
serializer = get_serializer_for_model(cf.related_object_type.model_class())
|
||||||
value = serializer(value, nested=True, context=self.parent.context).data
|
value = serializer(value, nested=True, context=self.parent.context).data
|
||||||
elif value is not None and cf.type == CustomFieldTypeChoices.TYPE_MULTIOBJECT:
|
elif value is not None and cf.type == CustomFieldTypeChoices.TYPE_MULTIOBJECT:
|
||||||
serializer = get_serializer_for_model(cf.object_type.model_class())
|
serializer = get_serializer_for_model(cf.related_object_type.model_class())
|
||||||
value = serializer(value, nested=True, many=True, context=self.parent.context).data
|
value = serializer(value, nested=True, many=True, context=self.parent.context).data
|
||||||
data[cf.name] = value
|
data[cf.name] = value
|
||||||
|
|
||||||
@ -79,7 +79,7 @@ class CustomFieldsDataField(Field):
|
|||||||
CustomFieldTypeChoices.TYPE_OBJECT,
|
CustomFieldTypeChoices.TYPE_OBJECT,
|
||||||
CustomFieldTypeChoices.TYPE_MULTIOBJECT
|
CustomFieldTypeChoices.TYPE_MULTIOBJECT
|
||||||
):
|
):
|
||||||
serializer_class = get_serializer_for_model(cf.object_type.model_class())
|
serializer_class = get_serializer_for_model(cf.related_object_type.model_class())
|
||||||
many = cf.type == CustomFieldTypeChoices.TYPE_MULTIOBJECT
|
many = cf.type == CustomFieldTypeChoices.TYPE_MULTIOBJECT
|
||||||
serializer = serializer_class(data=data[cf.name], nested=True, many=many, context=self.parent.context)
|
serializer = serializer_class(data=data[cf.name], nested=True, many=many, context=self.parent.context)
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
|
@ -44,7 +44,7 @@ class CustomFieldSerializer(ValidatedModelSerializer):
|
|||||||
many=True
|
many=True
|
||||||
)
|
)
|
||||||
type = ChoiceField(choices=CustomFieldTypeChoices)
|
type = ChoiceField(choices=CustomFieldTypeChoices)
|
||||||
object_type = ContentTypeField(
|
related_object_type = ContentTypeField(
|
||||||
queryset=ObjectType.objects.all(),
|
queryset=ObjectType.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
allow_null=True
|
allow_null=True
|
||||||
@ -62,10 +62,10 @@ class CustomFieldSerializer(ValidatedModelSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = CustomField
|
model = CustomField
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'url', 'display', 'object_types', 'type', 'object_type', 'data_type', 'name', 'label', 'group_name',
|
'id', 'url', 'display', 'object_types', 'type', 'related_object_type', 'data_type', 'name', 'label',
|
||||||
'description', 'required', 'search_weight', 'filter_logic', 'ui_visible', 'ui_editable', 'is_cloneable',
|
'group_name', 'description', 'required', 'search_weight', 'filter_logic', 'ui_visible', 'ui_editable',
|
||||||
'default', 'weight', 'validation_minimum', 'validation_maximum', 'validation_regex', 'choice_set',
|
'is_cloneable', 'default', 'weight', 'validation_minimum', 'validation_maximum', 'validation_regex',
|
||||||
'created', 'last_updated',
|
'choice_set', 'created', 'last_updated',
|
||||||
]
|
]
|
||||||
brief_fields = ('id', 'url', 'display', 'name', 'description')
|
brief_fields = ('id', 'url', 'display', 'name', 'description')
|
||||||
|
|
||||||
|
@ -40,12 +40,14 @@ class ScriptFilterSet(BaseFilterSet):
|
|||||||
method='search',
|
method='search',
|
||||||
label=_('Search'),
|
label=_('Search'),
|
||||||
)
|
)
|
||||||
|
module_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
queryset=ScriptModule.objects.all(),
|
||||||
|
label=_('Script module (ID)'),
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Script
|
model = Script
|
||||||
fields = [
|
fields = ('id', 'name', 'is_executable')
|
||||||
'id', 'name',
|
|
||||||
]
|
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
@ -69,10 +71,10 @@ class WebhookFilterSet(NetBoxModelFilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Webhook
|
model = Webhook
|
||||||
fields = [
|
fields = (
|
||||||
'id', 'name', 'payload_url', 'http_method', 'http_content_type', 'secret', 'ssl_verification',
|
'id', 'name', 'payload_url', 'http_method', 'http_content_type', 'secret', 'ssl_verification',
|
||||||
'ca_file_path', 'description',
|
'ca_file_path', 'description',
|
||||||
]
|
)
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
@ -89,8 +91,9 @@ class EventRuleFilterSet(NetBoxModelFilterSet):
|
|||||||
method='search',
|
method='search',
|
||||||
label=_('Search'),
|
label=_('Search'),
|
||||||
)
|
)
|
||||||
object_type_id = MultiValueNumberFilter(
|
object_type_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
field_name='object_types__id'
|
queryset=ObjectType.objects.all(),
|
||||||
|
field_name='object_types'
|
||||||
)
|
)
|
||||||
object_type = ContentTypeFilter(
|
object_type = ContentTypeFilter(
|
||||||
field_name='object_types'
|
field_name='object_types'
|
||||||
@ -103,10 +106,10 @@ class EventRuleFilterSet(NetBoxModelFilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = EventRule
|
model = EventRule
|
||||||
fields = [
|
fields = (
|
||||||
'id', 'name', 'type_create', 'type_update', 'type_delete', 'type_job_start', 'type_job_end', 'enabled',
|
'id', 'name', 'type_create', 'type_update', 'type_delete', 'type_job_start', 'type_job_end', 'enabled',
|
||||||
'action_type', 'description',
|
'action_type', 'description',
|
||||||
]
|
)
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
@ -118,7 +121,7 @@ class EventRuleFilterSet(NetBoxModelFilterSet):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class CustomFieldFilterSet(BaseFilterSet):
|
class CustomFieldFilterSet(ChangeLoggedModelFilterSet):
|
||||||
q = django_filters.CharFilter(
|
q = django_filters.CharFilter(
|
||||||
method='search',
|
method='search',
|
||||||
label=_('Search'),
|
label=_('Search'),
|
||||||
@ -126,12 +129,18 @@ class CustomFieldFilterSet(BaseFilterSet):
|
|||||||
type = django_filters.MultipleChoiceFilter(
|
type = django_filters.MultipleChoiceFilter(
|
||||||
choices=CustomFieldTypeChoices
|
choices=CustomFieldTypeChoices
|
||||||
)
|
)
|
||||||
object_type_id = MultiValueNumberFilter(
|
object_type_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
field_name='object_types__id'
|
queryset=ObjectType.objects.all(),
|
||||||
|
field_name='object_types'
|
||||||
)
|
)
|
||||||
object_type = ContentTypeFilter(
|
object_type = ContentTypeFilter(
|
||||||
field_name='object_types'
|
field_name='object_types'
|
||||||
)
|
)
|
||||||
|
related_object_type_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
queryset=ObjectType.objects.all(),
|
||||||
|
field_name='related_object_type'
|
||||||
|
)
|
||||||
|
related_object_type = ContentTypeFilter()
|
||||||
choice_set_id = django_filters.ModelMultipleChoiceFilter(
|
choice_set_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
queryset=CustomFieldChoiceSet.objects.all()
|
queryset=CustomFieldChoiceSet.objects.all()
|
||||||
)
|
)
|
||||||
@ -143,10 +152,11 @@ class CustomFieldFilterSet(BaseFilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CustomField
|
model = CustomField
|
||||||
fields = [
|
fields = (
|
||||||
'id', 'name', 'group_name', 'required', 'search_weight', 'filter_logic', 'ui_visible', 'ui_editable',
|
'id', 'name', 'label', 'group_name', 'required', 'search_weight', 'filter_logic', 'ui_visible',
|
||||||
'weight', 'is_cloneable', 'description',
|
'ui_editable', 'weight', 'is_cloneable', 'description', 'validation_minimum', 'validation_maximum',
|
||||||
]
|
'validation_regex',
|
||||||
|
)
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
@ -159,7 +169,7 @@ class CustomFieldFilterSet(BaseFilterSet):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class CustomFieldChoiceSetFilterSet(BaseFilterSet):
|
class CustomFieldChoiceSetFilterSet(ChangeLoggedModelFilterSet):
|
||||||
q = django_filters.CharFilter(
|
q = django_filters.CharFilter(
|
||||||
method='search',
|
method='search',
|
||||||
label=_('Search'),
|
label=_('Search'),
|
||||||
@ -170,9 +180,9 @@ class CustomFieldChoiceSetFilterSet(BaseFilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CustomFieldChoiceSet
|
model = CustomFieldChoiceSet
|
||||||
fields = [
|
fields = (
|
||||||
'id', 'name', 'description', 'base_choices', 'order_alphabetically',
|
'id', 'name', 'description', 'base_choices', 'order_alphabetically',
|
||||||
]
|
)
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
@ -187,13 +197,14 @@ class CustomFieldChoiceSetFilterSet(BaseFilterSet):
|
|||||||
return queryset.filter(extra_choices__overlap=value)
|
return queryset.filter(extra_choices__overlap=value)
|
||||||
|
|
||||||
|
|
||||||
class CustomLinkFilterSet(BaseFilterSet):
|
class CustomLinkFilterSet(ChangeLoggedModelFilterSet):
|
||||||
q = django_filters.CharFilter(
|
q = django_filters.CharFilter(
|
||||||
method='search',
|
method='search',
|
||||||
label=_('Search'),
|
label=_('Search'),
|
||||||
)
|
)
|
||||||
object_type_id = MultiValueNumberFilter(
|
object_type_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
field_name='object_types__id'
|
queryset=ObjectType.objects.all(),
|
||||||
|
field_name='object_types'
|
||||||
)
|
)
|
||||||
object_type = ContentTypeFilter(
|
object_type = ContentTypeFilter(
|
||||||
field_name='object_types'
|
field_name='object_types'
|
||||||
@ -201,9 +212,9 @@ class CustomLinkFilterSet(BaseFilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CustomLink
|
model = CustomLink
|
||||||
fields = [
|
fields = (
|
||||||
'id', 'name', 'enabled', 'link_text', 'link_url', 'weight', 'group_name', 'new_window',
|
'id', 'name', 'enabled', 'link_text', 'link_url', 'weight', 'group_name', 'new_window', 'button_class',
|
||||||
]
|
)
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
@ -216,13 +227,14 @@ class CustomLinkFilterSet(BaseFilterSet):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ExportTemplateFilterSet(BaseFilterSet):
|
class ExportTemplateFilterSet(ChangeLoggedModelFilterSet):
|
||||||
q = django_filters.CharFilter(
|
q = django_filters.CharFilter(
|
||||||
method='search',
|
method='search',
|
||||||
label=_('Search'),
|
label=_('Search'),
|
||||||
)
|
)
|
||||||
object_type_id = MultiValueNumberFilter(
|
object_type_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
field_name='object_types__id'
|
queryset=ObjectType.objects.all(),
|
||||||
|
field_name='object_types'
|
||||||
)
|
)
|
||||||
object_type = ContentTypeFilter(
|
object_type = ContentTypeFilter(
|
||||||
field_name='object_types'
|
field_name='object_types'
|
||||||
@ -238,7 +250,10 @@ class ExportTemplateFilterSet(BaseFilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ExportTemplate
|
model = ExportTemplate
|
||||||
fields = ['id', 'name', 'description', 'data_synced']
|
fields = (
|
||||||
|
'id', 'name', 'description', 'mime_type', 'file_extension', 'as_attachment', 'auto_sync_enabled',
|
||||||
|
'data_synced',
|
||||||
|
)
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
@ -249,13 +264,14 @@ class ExportTemplateFilterSet(BaseFilterSet):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class SavedFilterFilterSet(BaseFilterSet):
|
class SavedFilterFilterSet(ChangeLoggedModelFilterSet):
|
||||||
q = django_filters.CharFilter(
|
q = django_filters.CharFilter(
|
||||||
method='search',
|
method='search',
|
||||||
label=_('Search'),
|
label=_('Search'),
|
||||||
)
|
)
|
||||||
object_type_id = MultiValueNumberFilter(
|
object_type_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
field_name='object_types__id'
|
queryset=ObjectType.objects.all(),
|
||||||
|
field_name='object_types'
|
||||||
)
|
)
|
||||||
object_type = ContentTypeFilter(
|
object_type = ContentTypeFilter(
|
||||||
field_name='object_types'
|
field_name='object_types'
|
||||||
@ -276,7 +292,7 @@ class SavedFilterFilterSet(BaseFilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = SavedFilter
|
model = SavedFilter
|
||||||
fields = ['id', 'name', 'slug', 'description', 'enabled', 'shared', 'weight']
|
fields = ('id', 'name', 'slug', 'description', 'enabled', 'shared', 'weight')
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
@ -317,20 +333,19 @@ class BookmarkFilterSet(BaseFilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Bookmark
|
model = Bookmark
|
||||||
fields = ['id', 'object_id']
|
fields = ('id', 'object_id')
|
||||||
|
|
||||||
|
|
||||||
class ImageAttachmentFilterSet(BaseFilterSet):
|
class ImageAttachmentFilterSet(ChangeLoggedModelFilterSet):
|
||||||
q = django_filters.CharFilter(
|
q = django_filters.CharFilter(
|
||||||
method='search',
|
method='search',
|
||||||
label=_('Search'),
|
label=_('Search'),
|
||||||
)
|
)
|
||||||
created = django_filters.DateTimeFilter()
|
|
||||||
object_type = ContentTypeFilter()
|
object_type = ContentTypeFilter()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ImageAttachment
|
model = ImageAttachment
|
||||||
fields = ['id', 'object_type_id', 'object_id', 'name']
|
fields = ('id', 'object_type_id', 'object_id', 'name', 'image_width', 'image_height')
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
@ -360,7 +375,7 @@ class JournalEntryFilterSet(NetBoxModelFilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = JournalEntry
|
model = JournalEntry
|
||||||
fields = ['id', 'assigned_object_type_id', 'assigned_object_id', 'created', 'kind']
|
fields = ('id', 'assigned_object_type_id', 'assigned_object_id', 'created', 'kind')
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
@ -385,7 +400,7 @@ class TagFilterSet(ChangeLoggedModelFilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Tag
|
model = Tag
|
||||||
fields = ['id', 'name', 'slug', 'color', 'description', 'object_types']
|
fields = ('id', 'name', 'slug', 'color', 'description', 'object_types')
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
@ -482,12 +497,12 @@ class ConfigContextFilterSet(ChangeLoggedModelFilterSet):
|
|||||||
queryset=DeviceType.objects.all(),
|
queryset=DeviceType.objects.all(),
|
||||||
label=_('Device type'),
|
label=_('Device type'),
|
||||||
)
|
)
|
||||||
role_id = django_filters.ModelMultipleChoiceFilter(
|
device_role_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
field_name='roles',
|
field_name='roles',
|
||||||
queryset=DeviceRole.objects.all(),
|
queryset=DeviceRole.objects.all(),
|
||||||
label=_('Role'),
|
label=_('Role'),
|
||||||
)
|
)
|
||||||
role = django_filters.ModelMultipleChoiceFilter(
|
device_role = django_filters.ModelMultipleChoiceFilter(
|
||||||
field_name='roles__slug',
|
field_name='roles__slug',
|
||||||
queryset=DeviceRole.objects.all(),
|
queryset=DeviceRole.objects.all(),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
@ -573,9 +588,13 @@ class ConfigContextFilterSet(ChangeLoggedModelFilterSet):
|
|||||||
label=_('Data file (ID)'),
|
label=_('Data file (ID)'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# TODO: Remove in v4.1
|
||||||
|
role = device_role
|
||||||
|
role_id = device_role_id
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ConfigContext
|
model = ConfigContext
|
||||||
fields = ['id', 'name', 'is_active', 'data_synced', 'description']
|
fields = ('id', 'name', 'is_active', 'description', 'weight', 'auto_sync_enabled', 'data_synced')
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
@ -587,7 +606,7 @@ class ConfigContextFilterSet(ChangeLoggedModelFilterSet):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ConfigTemplateFilterSet(BaseFilterSet):
|
class ConfigTemplateFilterSet(ChangeLoggedModelFilterSet):
|
||||||
q = django_filters.CharFilter(
|
q = django_filters.CharFilter(
|
||||||
method='search',
|
method='search',
|
||||||
label=_('Search'),
|
label=_('Search'),
|
||||||
@ -604,7 +623,7 @@ class ConfigTemplateFilterSet(BaseFilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ConfigTemplate
|
model = ConfigTemplate
|
||||||
fields = ['id', 'name', 'description', 'data_synced']
|
fields = ('id', 'name', 'description', 'auto_sync_enabled', 'data_synced')
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
@ -652,10 +671,10 @@ class ObjectChangeFilterSet(BaseFilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ObjectChange
|
model = ObjectChange
|
||||||
fields = [
|
fields = (
|
||||||
'id', 'user', 'user_name', 'request_id', 'action', 'changed_object_type_id', 'changed_object_id',
|
'id', 'user', 'user_name', 'request_id', 'action', 'changed_object_type_id', 'changed_object_id',
|
||||||
'object_repr',
|
'related_object_type', 'related_object_id', 'object_repr',
|
||||||
]
|
)
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
@ -678,7 +697,7 @@ class ObjectTypeFilterSet(django_filters.FilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ObjectType
|
model = ObjectType
|
||||||
fields = ['id', 'app_label', 'model']
|
fields = ('id', 'app_label', 'model')
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
|
@ -40,7 +40,7 @@ class CustomFieldImportForm(CSVModelForm):
|
|||||||
choices=CustomFieldTypeChoices,
|
choices=CustomFieldTypeChoices,
|
||||||
help_text=_('Field data type (e.g. text, integer, etc.)')
|
help_text=_('Field data type (e.g. text, integer, etc.)')
|
||||||
)
|
)
|
||||||
object_type = CSVContentTypeField(
|
related_object_type = CSVContentTypeField(
|
||||||
label=_('Object type'),
|
label=_('Object type'),
|
||||||
queryset=ObjectType.objects.public(),
|
queryset=ObjectType.objects.public(),
|
||||||
required=False,
|
required=False,
|
||||||
@ -69,7 +69,7 @@ class CustomFieldImportForm(CSVModelForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = CustomField
|
model = CustomField
|
||||||
fields = (
|
fields = (
|
||||||
'name', 'label', 'group_name', 'type', 'object_types', 'object_type', 'required', 'description',
|
'name', 'label', 'group_name', 'type', 'object_types', 'related_object_type', 'required', 'description',
|
||||||
'search_weight', 'filter_logic', 'default', 'choice_set', 'weight', 'validation_minimum',
|
'search_weight', 'filter_logic', 'default', 'choice_set', 'weight', 'validation_minimum',
|
||||||
'validation_maximum', 'validation_regex', 'ui_visible', 'ui_editable', 'is_cloneable',
|
'validation_maximum', 'validation_regex', 'ui_visible', 'ui_editable', 'is_cloneable',
|
||||||
)
|
)
|
||||||
|
@ -13,6 +13,7 @@ from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES, FilterForm, add_blank_ch
|
|||||||
from utilities.forms.fields import (
|
from utilities.forms.fields import (
|
||||||
ContentTypeChoiceField, ContentTypeMultipleChoiceField, DynamicModelMultipleChoiceField, TagFilterField,
|
ContentTypeChoiceField, ContentTypeMultipleChoiceField, DynamicModelMultipleChoiceField, TagFilterField,
|
||||||
)
|
)
|
||||||
|
from utilities.forms.rendering import FieldSet
|
||||||
from utilities.forms.widgets import APISelectMultiple, DateTimePicker
|
from utilities.forms.widgets import APISelectMultiple, DateTimePicker
|
||||||
from virtualization.models import Cluster, ClusterGroup, ClusterType
|
from virtualization.models import Cluster, ClusterGroup, ClusterType
|
||||||
|
|
||||||
@ -36,16 +37,16 @@ __all__ = (
|
|||||||
|
|
||||||
class CustomFieldFilterForm(SavedFiltersMixin, FilterForm):
|
class CustomFieldFilterForm(SavedFiltersMixin, FilterForm):
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id')),
|
FieldSet('q', 'filter_id'),
|
||||||
(_('Attributes'), (
|
FieldSet(
|
||||||
'type', 'object_type_id', 'group_name', 'weight', 'required', 'choice_set_id', 'ui_visible', 'ui_editable',
|
'type', 'related_object_type_id', 'group_name', 'weight', 'required', 'choice_set_id', 'ui_visible',
|
||||||
'is_cloneable',
|
'ui_editable', 'is_cloneable', name=_('Attributes')
|
||||||
)),
|
),
|
||||||
)
|
)
|
||||||
object_type_id = ContentTypeMultipleChoiceField(
|
related_object_type_id = ContentTypeMultipleChoiceField(
|
||||||
queryset=ObjectType.objects.with_feature('custom_fields'),
|
queryset=ObjectType.objects.with_feature('custom_fields'),
|
||||||
required=False,
|
required=False,
|
||||||
label=_('Object type')
|
label=_('Related object type')
|
||||||
)
|
)
|
||||||
type = forms.MultipleChoiceField(
|
type = forms.MultipleChoiceField(
|
||||||
choices=CustomFieldTypeChoices,
|
choices=CustomFieldTypeChoices,
|
||||||
@ -93,8 +94,8 @@ class CustomFieldFilterForm(SavedFiltersMixin, FilterForm):
|
|||||||
|
|
||||||
class CustomFieldChoiceSetFilterForm(SavedFiltersMixin, FilterForm):
|
class CustomFieldChoiceSetFilterForm(SavedFiltersMixin, FilterForm):
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id')),
|
FieldSet('q', 'filter_id'),
|
||||||
(_('Choices'), ('base_choices', 'choice')),
|
FieldSet('base_choices', 'choice', name=_('Choices')),
|
||||||
)
|
)
|
||||||
base_choices = forms.MultipleChoiceField(
|
base_choices = forms.MultipleChoiceField(
|
||||||
choices=CustomFieldChoiceSetBaseChoices,
|
choices=CustomFieldChoiceSetBaseChoices,
|
||||||
@ -107,8 +108,8 @@ class CustomFieldChoiceSetFilterForm(SavedFiltersMixin, FilterForm):
|
|||||||
|
|
||||||
class CustomLinkFilterForm(SavedFiltersMixin, FilterForm):
|
class CustomLinkFilterForm(SavedFiltersMixin, FilterForm):
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id')),
|
FieldSet('q', 'filter_id'),
|
||||||
(_('Attributes'), ('object_type', 'enabled', 'new_window', 'weight')),
|
FieldSet('object_type', 'enabled', 'new_window', 'weight', name=_('Attributes')),
|
||||||
)
|
)
|
||||||
object_type = ContentTypeMultipleChoiceField(
|
object_type = ContentTypeMultipleChoiceField(
|
||||||
label=_('Object types'),
|
label=_('Object types'),
|
||||||
@ -137,9 +138,9 @@ class CustomLinkFilterForm(SavedFiltersMixin, FilterForm):
|
|||||||
|
|
||||||
class ExportTemplateFilterForm(SavedFiltersMixin, FilterForm):
|
class ExportTemplateFilterForm(SavedFiltersMixin, FilterForm):
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id')),
|
FieldSet('q', 'filter_id'),
|
||||||
(_('Data'), ('data_source_id', 'data_file_id')),
|
FieldSet('data_source_id', 'data_file_id', name=_('Data')),
|
||||||
(_('Attributes'), ('object_type_id', 'mime_type', 'file_extension', 'as_attachment')),
|
FieldSet('object_type_id', 'mime_type', 'file_extension', 'as_attachment', name=_('Attributes')),
|
||||||
)
|
)
|
||||||
data_source_id = DynamicModelMultipleChoiceField(
|
data_source_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=DataSource.objects.all(),
|
queryset=DataSource.objects.all(),
|
||||||
@ -178,8 +179,8 @@ class ExportTemplateFilterForm(SavedFiltersMixin, FilterForm):
|
|||||||
|
|
||||||
class ImageAttachmentFilterForm(SavedFiltersMixin, FilterForm):
|
class ImageAttachmentFilterForm(SavedFiltersMixin, FilterForm):
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id')),
|
FieldSet('q', 'filter_id'),
|
||||||
(_('Attributes'), ('object_type_id', 'name',)),
|
FieldSet('object_type_id', 'name', name=_('Attributes')),
|
||||||
)
|
)
|
||||||
object_type_id = ContentTypeChoiceField(
|
object_type_id = ContentTypeChoiceField(
|
||||||
label=_('Object type'),
|
label=_('Object type'),
|
||||||
@ -194,8 +195,8 @@ class ImageAttachmentFilterForm(SavedFiltersMixin, FilterForm):
|
|||||||
|
|
||||||
class SavedFilterFilterForm(SavedFiltersMixin, FilterForm):
|
class SavedFilterFilterForm(SavedFiltersMixin, FilterForm):
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id')),
|
FieldSet('q', 'filter_id'),
|
||||||
(_('Attributes'), ('object_type', 'enabled', 'shared', 'weight')),
|
FieldSet('object_type', 'enabled', 'shared', 'weight', name=_('Attributes')),
|
||||||
)
|
)
|
||||||
object_type = ContentTypeMultipleChoiceField(
|
object_type = ContentTypeMultipleChoiceField(
|
||||||
label=_('Object types'),
|
label=_('Object types'),
|
||||||
@ -225,8 +226,8 @@ class SavedFilterFilterForm(SavedFiltersMixin, FilterForm):
|
|||||||
class WebhookFilterForm(NetBoxModelFilterSetForm):
|
class WebhookFilterForm(NetBoxModelFilterSetForm):
|
||||||
model = Webhook
|
model = Webhook
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
FieldSet('q', 'filter_id', 'tag'),
|
||||||
(_('Attributes'), ('payload_url', 'http_method', 'http_content_type')),
|
FieldSet('payload_url', 'http_method', 'http_content_type', name=_('Attributes')),
|
||||||
)
|
)
|
||||||
http_content_type = forms.CharField(
|
http_content_type = forms.CharField(
|
||||||
label=_('HTTP content type'),
|
label=_('HTTP content type'),
|
||||||
@ -249,9 +250,9 @@ class EventRuleFilterForm(NetBoxModelFilterSetForm):
|
|||||||
tag = TagFilterField(model)
|
tag = TagFilterField(model)
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
FieldSet('q', 'filter_id', 'tag'),
|
||||||
(_('Attributes'), ('object_type_id', 'action_type', 'enabled')),
|
FieldSet('object_type_id', 'action_type', 'enabled', name=_('Attributes')),
|
||||||
(_('Events'), ('type_create', 'type_update', 'type_delete', 'type_job_start', 'type_job_end')),
|
FieldSet('type_create', 'type_update', 'type_delete', 'type_job_start', 'type_job_end', name=_('Events')),
|
||||||
)
|
)
|
||||||
object_type_id = ContentTypeMultipleChoiceField(
|
object_type_id = ContentTypeMultipleChoiceField(
|
||||||
queryset=ObjectType.objects.with_feature('event_rules'),
|
queryset=ObjectType.objects.with_feature('event_rules'),
|
||||||
@ -323,12 +324,12 @@ class TagFilterForm(SavedFiltersMixin, FilterForm):
|
|||||||
|
|
||||||
class ConfigContextFilterForm(SavedFiltersMixin, FilterForm):
|
class ConfigContextFilterForm(SavedFiltersMixin, FilterForm):
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag_id')),
|
FieldSet('q', 'filter_id', 'tag_id'),
|
||||||
(_('Data'), ('data_source_id', 'data_file_id')),
|
FieldSet('data_source_id', 'data_file_id', name=_('Data')),
|
||||||
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id')),
|
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', name=_('Location')),
|
||||||
(_('Device'), ('device_type_id', 'platform_id', 'role_id')),
|
FieldSet('device_type_id', 'platform_id', 'role_id', name=_('Device')),
|
||||||
(_('Cluster'), ('cluster_type_id', 'cluster_group_id', 'cluster_id')),
|
FieldSet('cluster_type_id', 'cluster_group_id', 'cluster_id', name=_('Cluster')),
|
||||||
(_('Tenant'), ('tenant_group_id', 'tenant_id'))
|
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant'))
|
||||||
)
|
)
|
||||||
data_source_id = DynamicModelMultipleChoiceField(
|
data_source_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=DataSource.objects.all(),
|
queryset=DataSource.objects.all(),
|
||||||
@ -412,8 +413,8 @@ class ConfigContextFilterForm(SavedFiltersMixin, FilterForm):
|
|||||||
|
|
||||||
class ConfigTemplateFilterForm(SavedFiltersMixin, FilterForm):
|
class ConfigTemplateFilterForm(SavedFiltersMixin, FilterForm):
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
FieldSet('q', 'filter_id', 'tag'),
|
||||||
(_('Data'), ('data_source_id', 'data_file_id')),
|
FieldSet('data_source_id', 'data_file_id', name=_('Data')),
|
||||||
)
|
)
|
||||||
data_source_id = DynamicModelMultipleChoiceField(
|
data_source_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=DataSource.objects.all(),
|
queryset=DataSource.objects.all(),
|
||||||
@ -444,9 +445,9 @@ class LocalConfigContextFilterForm(forms.Form):
|
|||||||
class JournalEntryFilterForm(NetBoxModelFilterSetForm):
|
class JournalEntryFilterForm(NetBoxModelFilterSetForm):
|
||||||
model = JournalEntry
|
model = JournalEntry
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
FieldSet('q', 'filter_id', 'tag'),
|
||||||
(_('Creation'), ('created_before', 'created_after', 'created_by_id')),
|
FieldSet('created_before', 'created_after', 'created_by_id', name=_('Creation')),
|
||||||
(_('Attributes'), ('assigned_object_type_id', 'kind'))
|
FieldSet('assigned_object_type_id', 'kind', name=_('Attributes')),
|
||||||
)
|
)
|
||||||
created_after = forms.DateTimeField(
|
created_after = forms.DateTimeField(
|
||||||
required=False,
|
required=False,
|
||||||
@ -482,9 +483,9 @@ class JournalEntryFilterForm(NetBoxModelFilterSetForm):
|
|||||||
class ObjectChangeFilterForm(SavedFiltersMixin, FilterForm):
|
class ObjectChangeFilterForm(SavedFiltersMixin, FilterForm):
|
||||||
model = ObjectChange
|
model = ObjectChange
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id')),
|
FieldSet('q', 'filter_id'),
|
||||||
(_('Time'), ('time_before', 'time_after')),
|
FieldSet('time_before', 'time_after', name=_('Time')),
|
||||||
(_('Attributes'), ('action', 'user_id', 'changed_object_type_id')),
|
FieldSet('action', 'user_id', 'changed_object_type_id', name=_('Attributes')),
|
||||||
)
|
)
|
||||||
time_after = forms.DateTimeField(
|
time_after = forms.DateTimeField(
|
||||||
required=False,
|
required=False,
|
||||||
|
@ -17,6 +17,7 @@ from utilities.forms.fields import (
|
|||||||
CommentField, ContentTypeChoiceField, ContentTypeMultipleChoiceField, DynamicModelChoiceField,
|
CommentField, ContentTypeChoiceField, ContentTypeMultipleChoiceField, DynamicModelChoiceField,
|
||||||
DynamicModelMultipleChoiceField, JSONField, SlugField,
|
DynamicModelMultipleChoiceField, JSONField, SlugField,
|
||||||
)
|
)
|
||||||
|
from utilities.forms.rendering import FieldSet, ObjectAttribute
|
||||||
from utilities.forms.widgets import ChoicesWidget, HTMXSelect
|
from utilities.forms.widgets import ChoicesWidget, HTMXSelect
|
||||||
from virtualization.models import Cluster, ClusterGroup, ClusterType
|
from virtualization.models import Cluster, ClusterGroup, ClusterType
|
||||||
|
|
||||||
@ -42,8 +43,8 @@ class CustomFieldForm(forms.ModelForm):
|
|||||||
label=_('Object types'),
|
label=_('Object types'),
|
||||||
queryset=ObjectType.objects.with_feature('custom_fields')
|
queryset=ObjectType.objects.with_feature('custom_fields')
|
||||||
)
|
)
|
||||||
object_type = ContentTypeChoiceField(
|
related_object_type = ContentTypeChoiceField(
|
||||||
label=_('Object type'),
|
label=_('Related object type'),
|
||||||
queryset=ObjectType.objects.public(),
|
queryset=ObjectType.objects.public(),
|
||||||
required=False,
|
required=False,
|
||||||
help_text=_("Type of the related object (for object/multi-object fields only)")
|
help_text=_("Type of the related object (for object/multi-object fields only)")
|
||||||
@ -54,12 +55,15 @@ class CustomFieldForm(forms.ModelForm):
|
|||||||
)
|
)
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Custom Field'), (
|
FieldSet(
|
||||||
'object_types', 'name', 'label', 'group_name', 'type', 'object_type', 'required', 'description',
|
'object_types', 'name', 'label', 'group_name', 'type', 'related_object_type', 'required', 'description',
|
||||||
)),
|
name=_('Custom Field')
|
||||||
(_('Behavior'), ('search_weight', 'filter_logic', 'ui_visible', 'ui_editable', 'weight', 'is_cloneable')),
|
),
|
||||||
(_('Values'), ('default', 'choice_set')),
|
FieldSet(
|
||||||
(_('Validation'), ('validation_minimum', 'validation_maximum', 'validation_regex')),
|
'search_weight', 'filter_logic', 'ui_visible', 'ui_editable', 'weight', 'is_cloneable', name=_('Behavior')
|
||||||
|
),
|
||||||
|
FieldSet('default', 'choice_set', name=_('Values')),
|
||||||
|
FieldSet('validation_minimum', 'validation_maximum', 'validation_regex', name=_('Validation')),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -128,8 +132,11 @@ class CustomLinkForm(forms.ModelForm):
|
|||||||
)
|
)
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Custom Link'), ('name', 'object_types', 'weight', 'group_name', 'button_class', 'enabled', 'new_window')),
|
FieldSet(
|
||||||
(_('Templates'), ('link_text', 'link_url')),
|
'name', 'object_types', 'weight', 'group_name', 'button_class', 'enabled', 'new_window',
|
||||||
|
name=_('Custom Link')
|
||||||
|
),
|
||||||
|
FieldSet('link_text', 'link_url', name=_('Templates')),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -162,9 +169,9 @@ class ExportTemplateForm(SyncedDataMixin, forms.ModelForm):
|
|||||||
)
|
)
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Export Template'), ('name', 'object_types', 'description', 'template_code')),
|
FieldSet('name', 'object_types', 'description', 'template_code', name=_('Export Template')),
|
||||||
(_('Data Source'), ('data_source', 'data_file', 'auto_sync_enabled')),
|
FieldSet('data_source', 'data_file', 'auto_sync_enabled', name=_('Data Source')),
|
||||||
(_('Rendering'), ('mime_type', 'file_extension', 'as_attachment')),
|
FieldSet('mime_type', 'file_extension', 'as_attachment', name=_('Rendering')),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -199,8 +206,8 @@ class SavedFilterForm(forms.ModelForm):
|
|||||||
parameters = JSONField()
|
parameters = JSONField()
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Saved Filter'), ('name', 'slug', 'object_types', 'description', 'weight', 'enabled', 'shared')),
|
FieldSet('name', 'slug', 'object_types', 'description', 'weight', 'enabled', 'shared', name=_('Saved Filter')),
|
||||||
(_('Parameters'), ('parameters',)),
|
FieldSet('parameters', name=_('Parameters')),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -231,11 +238,12 @@ class BookmarkForm(forms.ModelForm):
|
|||||||
class WebhookForm(NetBoxModelForm):
|
class WebhookForm(NetBoxModelForm):
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Webhook'), ('name', 'description', 'tags',)),
|
FieldSet('name', 'description', 'tags', name=_('Webhook')),
|
||||||
(_('HTTP Request'), (
|
FieldSet(
|
||||||
'payload_url', 'http_method', 'http_content_type', 'additional_headers', 'body_template', 'secret',
|
'payload_url', 'http_method', 'http_content_type', 'additional_headers', 'body_template', 'secret',
|
||||||
)),
|
name=_('HTTP Request')
|
||||||
(_('SSL'), ('ssl_verification', 'ca_file_path')),
|
),
|
||||||
|
FieldSet('ssl_verification', 'ca_file_path', name=_('SSL')),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -266,12 +274,13 @@ class EventRuleForm(NetBoxModelForm):
|
|||||||
)
|
)
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Event Rule'), ('name', 'description', 'object_types', 'enabled', 'tags')),
|
FieldSet('name', 'description', 'object_types', 'enabled', 'tags', name=_('Event Rule')),
|
||||||
(_('Events'), ('type_create', 'type_update', 'type_delete', 'type_job_start', 'type_job_end')),
|
FieldSet('type_create', 'type_update', 'type_delete', 'type_job_start', 'type_job_end', name=_('Events')),
|
||||||
(_('Conditions'), ('conditions',)),
|
FieldSet('conditions', name=_('Conditions')),
|
||||||
(_('Action'), (
|
FieldSet(
|
||||||
'action_type', 'action_choice', 'action_object_type', 'action_object_id', 'action_data',
|
'action_type', 'action_choice', 'action_object_type', 'action_object_id', 'action_data',
|
||||||
)),
|
name=_('Action')
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -360,7 +369,7 @@ class TagForm(forms.ModelForm):
|
|||||||
)
|
)
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
('Tag', ('name', 'slug', 'color', 'description', 'object_types')),
|
FieldSet('name', 'slug', 'color', 'description', 'object_types', name=_('Tag')),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -442,12 +451,13 @@ class ConfigContextForm(SyncedDataMixin, forms.ModelForm):
|
|||||||
)
|
)
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Config Context'), ('name', 'weight', 'description', 'data', 'is_active')),
|
FieldSet('name', 'weight', 'description', 'data', 'is_active', name=_('Config Context')),
|
||||||
(_('Data Source'), ('data_source', 'data_file', 'auto_sync_enabled')),
|
FieldSet('data_source', 'data_file', 'auto_sync_enabled', name=_('Data Source')),
|
||||||
(_('Assignment'), (
|
FieldSet(
|
||||||
'regions', 'site_groups', 'sites', 'locations', 'device_types', 'roles', 'platforms', 'cluster_types',
|
'regions', 'site_groups', 'sites', 'locations', 'device_types', 'roles', 'platforms', 'cluster_types',
|
||||||
'cluster_groups', 'clusters', 'tenant_groups', 'tenants', 'tags',
|
'cluster_groups', 'clusters', 'tenant_groups', 'tenants', 'tags',
|
||||||
)),
|
name=_('Assignment')
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -494,9 +504,9 @@ class ConfigTemplateForm(SyncedDataMixin, forms.ModelForm):
|
|||||||
)
|
)
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Config Template'), ('name', 'description', 'environment_params', 'tags')),
|
FieldSet('name', 'description', 'environment_params', 'tags', name=_('Config Template')),
|
||||||
(_('Content'), ('template_code',)),
|
FieldSet('template_code', name=_('Content')),
|
||||||
(_('Data Source'), ('data_source', 'data_file', 'auto_sync_enabled')),
|
FieldSet('data_source', 'data_file', 'auto_sync_enabled', name=_('Data Source')),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -526,6 +536,9 @@ class ConfigTemplateForm(SyncedDataMixin, forms.ModelForm):
|
|||||||
|
|
||||||
|
|
||||||
class ImageAttachmentForm(forms.ModelForm):
|
class ImageAttachmentForm(forms.ModelForm):
|
||||||
|
fieldsets = (
|
||||||
|
FieldSet(ObjectAttribute('parent'), 'name', 'image'),
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ImageAttachment
|
model = ImageAttachment
|
||||||
|
@ -7,6 +7,7 @@ from extras.models import ObjectChange
|
|||||||
__all__ = (
|
__all__ = (
|
||||||
'ChangelogMixin',
|
'ChangelogMixin',
|
||||||
'ConfigContextMixin',
|
'ConfigContextMixin',
|
||||||
|
'ContactsMixin',
|
||||||
'CustomFieldsMixin',
|
'CustomFieldsMixin',
|
||||||
'ImageAttachmentsMixin',
|
'ImageAttachmentsMixin',
|
||||||
'JournalEntriesMixin',
|
'JournalEntriesMixin',
|
||||||
|
@ -25,7 +25,4 @@ class Migration(migrations.Migration):
|
|||||||
migrations.DeleteModel(
|
migrations.DeleteModel(
|
||||||
name='Report',
|
name='Report',
|
||||||
),
|
),
|
||||||
migrations.DeleteModel(
|
|
||||||
name='ReportModule',
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
@ -82,10 +82,12 @@ def update_scripts(apps, schema_editor):
|
|||||||
ContentType = apps.get_model('contenttypes', 'ContentType')
|
ContentType = apps.get_model('contenttypes', 'ContentType')
|
||||||
Script = apps.get_model('extras', 'Script')
|
Script = apps.get_model('extras', 'Script')
|
||||||
ScriptModule = apps.get_model('extras', 'ScriptModule')
|
ScriptModule = apps.get_model('extras', 'ScriptModule')
|
||||||
|
ReportModule = apps.get_model('extras', 'ReportModule')
|
||||||
Job = apps.get_model('core', 'Job')
|
Job = apps.get_model('core', 'Job')
|
||||||
|
|
||||||
script_ct = ContentType.objects.get_for_model(Script)
|
script_ct = ContentType.objects.get_for_model(Script, for_concrete_model=False)
|
||||||
scriptmodule_ct = ContentType.objects.get_for_model(ScriptModule)
|
scriptmodule_ct = ContentType.objects.get_for_model(ScriptModule, for_concrete_model=False)
|
||||||
|
reportmodule_ct = ContentType.objects.get_for_model(ReportModule, for_concrete_model=False)
|
||||||
|
|
||||||
for module in ScriptModule.objects.all():
|
for module in ScriptModule.objects.all():
|
||||||
for script_name in get_module_scripts(module):
|
for script_name in get_module_scripts(module):
|
||||||
@ -96,10 +98,16 @@ def update_scripts(apps, schema_editor):
|
|||||||
|
|
||||||
# Update all Jobs associated with this ScriptModule & script name to point to the new Script object
|
# Update all Jobs associated with this ScriptModule & script name to point to the new Script object
|
||||||
Job.objects.filter(
|
Job.objects.filter(
|
||||||
object_type=scriptmodule_ct,
|
object_type_id=scriptmodule_ct.id,
|
||||||
object_id=module.pk,
|
object_id=module.pk,
|
||||||
name=script_name
|
name=script_name
|
||||||
).update(object_type=script_ct, object_id=script.pk)
|
).update(object_type_id=script_ct.id, object_id=script.pk)
|
||||||
|
# Update all Jobs associated with this ScriptModule & script name to point to the new Script object
|
||||||
|
Job.objects.filter(
|
||||||
|
object_type_id=reportmodule_ct.id,
|
||||||
|
object_id=module.pk,
|
||||||
|
name=script_name
|
||||||
|
).update(object_type_id=script_ct.id, object_id=script.pk)
|
||||||
|
|
||||||
|
|
||||||
def update_event_rules(apps, schema_editor):
|
def update_event_rules(apps, schema_editor):
|
||||||
|
@ -12,4 +12,7 @@ class Migration(migrations.Migration):
|
|||||||
model_name='eventrule',
|
model_name='eventrule',
|
||||||
name='action_parameters',
|
name='action_parameters',
|
||||||
),
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='ReportModule',
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
@ -0,0 +1,16 @@
|
|||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('extras', '0112_tag_update_object_types'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='customfield',
|
||||||
|
old_name='object_type',
|
||||||
|
new_name='related_object_type',
|
||||||
|
),
|
||||||
|
]
|
@ -11,7 +11,7 @@ from extras.querysets import ConfigContextQuerySet
|
|||||||
from netbox.config import get_config
|
from netbox.config import get_config
|
||||||
from netbox.registry import registry
|
from netbox.registry import registry
|
||||||
from netbox.models import ChangeLoggedModel
|
from netbox.models import ChangeLoggedModel
|
||||||
from netbox.models.features import CloningMixin, ExportTemplatesMixin, SyncedDataMixin, TagsMixin
|
from netbox.models.features import CloningMixin, CustomLinksMixin, ExportTemplatesMixin, SyncedDataMixin, TagsMixin
|
||||||
from utilities.jinja2 import ConfigTemplateLoader
|
from utilities.jinja2 import ConfigTemplateLoader
|
||||||
from utilities.utils import deepmerge
|
from utilities.utils import deepmerge
|
||||||
|
|
||||||
@ -26,7 +26,7 @@ __all__ = (
|
|||||||
# Config contexts
|
# Config contexts
|
||||||
#
|
#
|
||||||
|
|
||||||
class ConfigContext(SyncedDataMixin, CloningMixin, ChangeLoggedModel):
|
class ConfigContext(SyncedDataMixin, CloningMixin, CustomLinksMixin, ChangeLoggedModel):
|
||||||
"""
|
"""
|
||||||
A ConfigContext represents a set of arbitrary data available to any Device or VirtualMachine matching its assigned
|
A ConfigContext represents a set of arbitrary data available to any Device or VirtualMachine matching its assigned
|
||||||
qualifiers (region, site, etc.). For example, the data stored in a ConfigContext assigned to site A and tenant B
|
qualifiers (region, site, etc.). For example, the data stored in a ConfigContext assigned to site A and tenant B
|
||||||
@ -210,7 +210,7 @@ class ConfigContextModel(models.Model):
|
|||||||
# Config templates
|
# Config templates
|
||||||
#
|
#
|
||||||
|
|
||||||
class ConfigTemplate(SyncedDataMixin, ExportTemplatesMixin, TagsMixin, ChangeLoggedModel):
|
class ConfigTemplate(SyncedDataMixin, CustomLinksMixin, ExportTemplatesMixin, TagsMixin, ChangeLoggedModel):
|
||||||
name = models.CharField(
|
name = models.CharField(
|
||||||
verbose_name=_('name'),
|
verbose_name=_('name'),
|
||||||
max_length=100
|
max_length=100
|
||||||
|
@ -78,7 +78,7 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
|
|||||||
default=CustomFieldTypeChoices.TYPE_TEXT,
|
default=CustomFieldTypeChoices.TYPE_TEXT,
|
||||||
help_text=_('The type of data this custom field holds')
|
help_text=_('The type of data this custom field holds')
|
||||||
)
|
)
|
||||||
object_type = models.ForeignKey(
|
related_object_type = models.ForeignKey(
|
||||||
to='core.ObjectType',
|
to='core.ObjectType',
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
blank=True,
|
blank=True,
|
||||||
@ -209,7 +209,7 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
|
|||||||
objects = CustomFieldManager()
|
objects = CustomFieldManager()
|
||||||
|
|
||||||
clone_fields = (
|
clone_fields = (
|
||||||
'object_types', 'type', 'object_type', 'group_name', 'description', 'required', 'search_weight',
|
'object_types', 'type', 'related_object_type', 'group_name', 'description', 'required', 'search_weight',
|
||||||
'filter_logic', 'default', 'weight', 'validation_minimum', 'validation_maximum', 'validation_regex',
|
'filter_logic', 'default', 'weight', 'validation_minimum', 'validation_maximum', 'validation_regex',
|
||||||
'choice_set', 'ui_visible', 'ui_editable', 'is_cloneable',
|
'choice_set', 'ui_visible', 'ui_editable', 'is_cloneable',
|
||||||
)
|
)
|
||||||
@ -344,11 +344,11 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
|
|||||||
|
|
||||||
# Object fields must define an object_type; other fields must not
|
# Object fields must define an object_type; other fields must not
|
||||||
if self.type in (CustomFieldTypeChoices.TYPE_OBJECT, CustomFieldTypeChoices.TYPE_MULTIOBJECT):
|
if self.type in (CustomFieldTypeChoices.TYPE_OBJECT, CustomFieldTypeChoices.TYPE_MULTIOBJECT):
|
||||||
if not self.object_type:
|
if not self.related_object_type:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'object_type': _("Object fields must define an object type.")
|
'object_type': _("Object fields must define an object type.")
|
||||||
})
|
})
|
||||||
elif self.object_type:
|
elif self.related_object_type:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'object_type': _(
|
'object_type': _(
|
||||||
"{type} fields may not define an object type.")
|
"{type} fields may not define an object type.")
|
||||||
@ -388,10 +388,10 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
return value
|
return value
|
||||||
if self.type == CustomFieldTypeChoices.TYPE_OBJECT:
|
if self.type == CustomFieldTypeChoices.TYPE_OBJECT:
|
||||||
model = self.object_type.model_class()
|
model = self.related_object_type.model_class()
|
||||||
return model.objects.filter(pk=value).first()
|
return model.objects.filter(pk=value).first()
|
||||||
if self.type == CustomFieldTypeChoices.TYPE_MULTIOBJECT:
|
if self.type == CustomFieldTypeChoices.TYPE_MULTIOBJECT:
|
||||||
model = self.object_type.model_class()
|
model = self.related_object_type.model_class()
|
||||||
return model.objects.filter(pk__in=value)
|
return model.objects.filter(pk__in=value)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
@ -488,7 +488,7 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
|
|||||||
|
|
||||||
# Object
|
# Object
|
||||||
elif self.type == CustomFieldTypeChoices.TYPE_OBJECT:
|
elif self.type == CustomFieldTypeChoices.TYPE_OBJECT:
|
||||||
model = self.object_type.model_class()
|
model = self.related_object_type.model_class()
|
||||||
field_class = CSVModelChoiceField if for_csv_import else DynamicModelChoiceField
|
field_class = CSVModelChoiceField if for_csv_import else DynamicModelChoiceField
|
||||||
field = field_class(
|
field = field_class(
|
||||||
queryset=model.objects.all(),
|
queryset=model.objects.all(),
|
||||||
@ -498,7 +498,7 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
|
|||||||
|
|
||||||
# Multiple objects
|
# Multiple objects
|
||||||
elif self.type == CustomFieldTypeChoices.TYPE_MULTIOBJECT:
|
elif self.type == CustomFieldTypeChoices.TYPE_MULTIOBJECT:
|
||||||
model = self.object_type.model_class()
|
model = self.related_object_type.model_class()
|
||||||
field_class = CSVModelMultipleChoiceField if for_csv_import else DynamicModelMultipleChoiceField
|
field_class = CSVModelMultipleChoiceField if for_csv_import else DynamicModelMultipleChoiceField
|
||||||
field = field_class(
|
field = field_class(
|
||||||
queryset=model.objects.all(),
|
queryset=model.objects.all(),
|
||||||
|
@ -5,7 +5,7 @@ from django.conf import settings
|
|||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from extras.models import *
|
from extras.models import *
|
||||||
from netbox.tables import NetBoxTable, columns
|
from netbox.tables import BaseTable, NetBoxTable, columns
|
||||||
from .template_code import *
|
from .template_code import *
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
@ -21,6 +21,8 @@ __all__ = (
|
|||||||
'JournalEntryTable',
|
'JournalEntryTable',
|
||||||
'ObjectChangeTable',
|
'ObjectChangeTable',
|
||||||
'SavedFilterTable',
|
'SavedFilterTable',
|
||||||
|
'ReportResultsTable',
|
||||||
|
'ScriptResultsTable',
|
||||||
'TaggedItemTable',
|
'TaggedItemTable',
|
||||||
'TagTable',
|
'TagTable',
|
||||||
'WebhookTable',
|
'WebhookTable',
|
||||||
@ -55,6 +57,9 @@ class CustomFieldTable(NetBoxTable):
|
|||||||
description = columns.MarkdownColumn(
|
description = columns.MarkdownColumn(
|
||||||
verbose_name=_('Description')
|
verbose_name=_('Description')
|
||||||
)
|
)
|
||||||
|
related_object_type = columns.ContentTypeColumn(
|
||||||
|
verbose_name=_('Related Object Type')
|
||||||
|
)
|
||||||
choice_set = tables.Column(
|
choice_set = tables.Column(
|
||||||
linkify=True,
|
linkify=True,
|
||||||
verbose_name=_('Choice Set')
|
verbose_name=_('Choice Set')
|
||||||
@ -71,9 +76,9 @@ class CustomFieldTable(NetBoxTable):
|
|||||||
class Meta(NetBoxTable.Meta):
|
class Meta(NetBoxTable.Meta):
|
||||||
model = CustomField
|
model = CustomField
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'id', 'name', 'object_types', 'label', 'type', 'group_name', 'required', 'default', 'description',
|
'pk', 'id', 'name', 'object_types', 'label', 'type', 'related_object_type', 'group_name', 'required',
|
||||||
'search_weight', 'filter_logic', 'ui_visible', 'ui_editable', 'is_cloneable', 'weight', 'choice_set',
|
'default', 'description', 'search_weight', 'filter_logic', 'ui_visible', 'ui_editable', 'is_cloneable',
|
||||||
'choices', 'created', 'last_updated',
|
'weight', 'choice_set', 'choices', 'created', 'last_updated',
|
||||||
)
|
)
|
||||||
default_columns = ('pk', 'name', 'object_types', 'label', 'group_name', 'type', 'required', 'description')
|
default_columns = ('pk', 'name', 'object_types', 'label', 'group_name', 'type', 'required', 'description')
|
||||||
|
|
||||||
@ -507,3 +512,61 @@ class JournalEntryTable(NetBoxTable):
|
|||||||
default_columns = (
|
default_columns = (
|
||||||
'pk', 'created', 'created_by', 'assigned_object_type', 'assigned_object', 'kind', 'comments'
|
'pk', 'created', 'created_by', 'assigned_object_type', 'assigned_object', 'kind', 'comments'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ScriptResultsTable(BaseTable):
|
||||||
|
index = tables.Column(
|
||||||
|
verbose_name=_('Line')
|
||||||
|
)
|
||||||
|
time = tables.Column(
|
||||||
|
verbose_name=_('Time')
|
||||||
|
)
|
||||||
|
status = tables.TemplateColumn(
|
||||||
|
template_code="""{% load log_levels %}{% log_level record.status %}""",
|
||||||
|
verbose_name=_('Level')
|
||||||
|
)
|
||||||
|
message = tables.Column(
|
||||||
|
verbose_name=_('Message')
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta(BaseTable.Meta):
|
||||||
|
empty_text = _('No results found')
|
||||||
|
fields = (
|
||||||
|
'index', 'time', 'status', 'message',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ReportResultsTable(BaseTable):
|
||||||
|
index = tables.Column(
|
||||||
|
verbose_name=_('Line')
|
||||||
|
)
|
||||||
|
method = tables.Column(
|
||||||
|
verbose_name=_('Method')
|
||||||
|
)
|
||||||
|
time = tables.Column(
|
||||||
|
verbose_name=_('Time')
|
||||||
|
)
|
||||||
|
status = tables.Column(
|
||||||
|
empty_values=(),
|
||||||
|
verbose_name=_('Level')
|
||||||
|
)
|
||||||
|
status = tables.TemplateColumn(
|
||||||
|
template_code="""{% load log_levels %}{% log_level record.status %}""",
|
||||||
|
verbose_name=_('Level')
|
||||||
|
)
|
||||||
|
|
||||||
|
object = tables.Column(
|
||||||
|
verbose_name=_('Object')
|
||||||
|
)
|
||||||
|
url = tables.Column(
|
||||||
|
verbose_name=_('URL')
|
||||||
|
)
|
||||||
|
message = tables.Column(
|
||||||
|
verbose_name=_('Message')
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta(BaseTable.Meta):
|
||||||
|
empty_text = _('No results found')
|
||||||
|
fields = (
|
||||||
|
'index', 'method', 'time', 'status', 'object', 'url', 'message',
|
||||||
|
)
|
||||||
|
@ -350,7 +350,7 @@ class CustomFieldTest(TestCase):
|
|||||||
cf = CustomField.objects.create(
|
cf = CustomField.objects.create(
|
||||||
name='object_field',
|
name='object_field',
|
||||||
type=CustomFieldTypeChoices.TYPE_OBJECT,
|
type=CustomFieldTypeChoices.TYPE_OBJECT,
|
||||||
object_type=ObjectType.objects.get_for_model(VLAN),
|
related_object_type=ObjectType.objects.get_for_model(VLAN),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
cf.object_types.set([self.object_type])
|
cf.object_types.set([self.object_type])
|
||||||
@ -382,7 +382,7 @@ class CustomFieldTest(TestCase):
|
|||||||
cf = CustomField.objects.create(
|
cf = CustomField.objects.create(
|
||||||
name='object_field',
|
name='object_field',
|
||||||
type=CustomFieldTypeChoices.TYPE_MULTIOBJECT,
|
type=CustomFieldTypeChoices.TYPE_MULTIOBJECT,
|
||||||
object_type=ObjectType.objects.get_for_model(VLAN),
|
related_object_type=ObjectType.objects.get_for_model(VLAN),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
cf.object_types.set([self.object_type])
|
cf.object_types.set([self.object_type])
|
||||||
@ -498,16 +498,28 @@ class CustomFieldTest(TestCase):
|
|||||||
).full_clean()
|
).full_clean()
|
||||||
|
|
||||||
# Object
|
# Object
|
||||||
CustomField(name='test', type='object', required=True, object_type=object_type, default=site.pk).full_clean()
|
CustomField(
|
||||||
with self.assertRaises(ValidationError):
|
name='test',
|
||||||
CustomField(name='test', type='object', required=True, object_type=object_type, default="xxx").full_clean()
|
type='object',
|
||||||
|
required=True,
|
||||||
|
related_object_type=object_type,
|
||||||
|
default=site.pk
|
||||||
|
).full_clean()
|
||||||
|
with (self.assertRaises(ValidationError)):
|
||||||
|
CustomField(
|
||||||
|
name='test',
|
||||||
|
type='object',
|
||||||
|
required=True,
|
||||||
|
related_object_type=object_type,
|
||||||
|
default="xxx"
|
||||||
|
).full_clean()
|
||||||
|
|
||||||
# Multi-object
|
# Multi-object
|
||||||
CustomField(
|
CustomField(
|
||||||
name='test',
|
name='test',
|
||||||
type='multiobject',
|
type='multiobject',
|
||||||
required=True,
|
required=True,
|
||||||
object_type=object_type,
|
related_object_type=object_type,
|
||||||
default=[site.pk]
|
default=[site.pk]
|
||||||
).full_clean()
|
).full_clean()
|
||||||
with self.assertRaises(ValidationError):
|
with self.assertRaises(ValidationError):
|
||||||
@ -515,7 +527,7 @@ class CustomFieldTest(TestCase):
|
|||||||
name='test',
|
name='test',
|
||||||
type='multiobject',
|
type='multiobject',
|
||||||
required=True,
|
required=True,
|
||||||
object_type=object_type,
|
related_object_type=object_type,
|
||||||
default=["xxx"]
|
default=["xxx"]
|
||||||
).full_clean()
|
).full_clean()
|
||||||
|
|
||||||
@ -581,13 +593,13 @@ class CustomFieldAPITest(APITestCase):
|
|||||||
CustomField(
|
CustomField(
|
||||||
type=CustomFieldTypeChoices.TYPE_OBJECT,
|
type=CustomFieldTypeChoices.TYPE_OBJECT,
|
||||||
name='object_field',
|
name='object_field',
|
||||||
object_type=ObjectType.objects.get_for_model(VLAN),
|
related_object_type=ObjectType.objects.get_for_model(VLAN),
|
||||||
default=vlans[0].pk,
|
default=vlans[0].pk,
|
||||||
),
|
),
|
||||||
CustomField(
|
CustomField(
|
||||||
type=CustomFieldTypeChoices.TYPE_MULTIOBJECT,
|
type=CustomFieldTypeChoices.TYPE_MULTIOBJECT,
|
||||||
name='multiobject_field',
|
name='multiobject_field',
|
||||||
object_type=ObjectType.objects.get_for_model(VLAN),
|
related_object_type=ObjectType.objects.get_for_model(VLAN),
|
||||||
default=[vlans[0].pk, vlans[1].pk],
|
default=[vlans[0].pk, vlans[1].pk],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -1410,7 +1422,7 @@ class CustomFieldModelFilterTest(TestCase):
|
|||||||
cf = CustomField(
|
cf = CustomField(
|
||||||
name='cf11',
|
name='cf11',
|
||||||
type=CustomFieldTypeChoices.TYPE_OBJECT,
|
type=CustomFieldTypeChoices.TYPE_OBJECT,
|
||||||
object_type=ObjectType.objects.get_for_model(Manufacturer)
|
related_object_type=ObjectType.objects.get_for_model(Manufacturer)
|
||||||
)
|
)
|
||||||
cf.save()
|
cf.save()
|
||||||
cf.object_types.set([object_type])
|
cf.object_types.set([object_type])
|
||||||
@ -1419,7 +1431,7 @@ class CustomFieldModelFilterTest(TestCase):
|
|||||||
cf = CustomField(
|
cf = CustomField(
|
||||||
name='cf12',
|
name='cf12',
|
||||||
type=CustomFieldTypeChoices.TYPE_MULTIOBJECT,
|
type=CustomFieldTypeChoices.TYPE_MULTIOBJECT,
|
||||||
object_type=ObjectType.objects.get_for_model(Manufacturer)
|
related_object_type=ObjectType.objects.get_for_model(Manufacturer)
|
||||||
)
|
)
|
||||||
cf.save()
|
cf.save()
|
||||||
cf.object_types.set([object_type])
|
cf.object_types.set([object_type])
|
||||||
|
@ -23,9 +23,10 @@ from virtualization.models import Cluster, ClusterGroup, ClusterType
|
|||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
class CustomFieldTestCase(TestCase, BaseFilterSetTests):
|
class CustomFieldTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = CustomField.objects.all()
|
queryset = CustomField.objects.all()
|
||||||
filterset = CustomFieldFilterSet
|
filterset = CustomFieldFilterSet
|
||||||
|
ignore_fields = ('default',)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -86,6 +87,16 @@ class CustomFieldTestCase(TestCase, BaseFilterSetTests):
|
|||||||
ui_editable=CustomFieldUIEditableChoices.HIDDEN,
|
ui_editable=CustomFieldUIEditableChoices.HIDDEN,
|
||||||
choice_set=choice_sets[1]
|
choice_set=choice_sets[1]
|
||||||
),
|
),
|
||||||
|
CustomField(
|
||||||
|
name='Custom Field 6',
|
||||||
|
type=CustomFieldTypeChoices.TYPE_OBJECT,
|
||||||
|
related_object_type=ObjectType.objects.get_by_natural_key('dcim', 'site'),
|
||||||
|
required=False,
|
||||||
|
weight=600,
|
||||||
|
filter_logic=CustomFieldFilterLogicChoices.FILTER_DISABLED,
|
||||||
|
ui_visible=CustomFieldUIVisibleChoices.HIDDEN,
|
||||||
|
ui_editable=CustomFieldUIEditableChoices.HIDDEN
|
||||||
|
),
|
||||||
)
|
)
|
||||||
CustomField.objects.bulk_create(custom_fields)
|
CustomField.objects.bulk_create(custom_fields)
|
||||||
custom_fields[0].object_types.add(ObjectType.objects.get_by_natural_key('dcim', 'site'))
|
custom_fields[0].object_types.add(ObjectType.objects.get_by_natural_key('dcim', 'site'))
|
||||||
@ -108,6 +119,12 @@ class CustomFieldTestCase(TestCase, BaseFilterSetTests):
|
|||||||
params = {'object_type_id': [ObjectType.objects.get_by_natural_key('dcim', 'site').pk]}
|
params = {'object_type_id': [ObjectType.objects.get_by_natural_key('dcim', 'site').pk]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
|
def test_related_object_type(self):
|
||||||
|
params = {'related_object_type': 'dcim.site'}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
params = {'related_object_type_id': [ObjectType.objects.get_by_natural_key('dcim', 'site').pk]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
def test_required(self):
|
def test_required(self):
|
||||||
params = {'required': True}
|
params = {'required': True}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
@ -139,9 +156,10 @@ class CustomFieldTestCase(TestCase, BaseFilterSetTests):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
|
||||||
class CustomFieldChoiceSetTestCase(TestCase, BaseFilterSetTests):
|
class CustomFieldChoiceSetTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = CustomFieldChoiceSet.objects.all()
|
queryset = CustomFieldChoiceSet.objects.all()
|
||||||
filterset = CustomFieldChoiceSetFilterSet
|
filterset = CustomFieldChoiceSetFilterSet
|
||||||
|
ignore_fields = ('extra_choices',)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -172,6 +190,7 @@ class CustomFieldChoiceSetTestCase(TestCase, BaseFilterSetTests):
|
|||||||
class WebhookTestCase(TestCase, BaseFilterSetTests):
|
class WebhookTestCase(TestCase, BaseFilterSetTests):
|
||||||
queryset = Webhook.objects.all()
|
queryset = Webhook.objects.all()
|
||||||
filterset = WebhookFilterSet
|
filterset = WebhookFilterSet
|
||||||
|
ignore_fields = ('additional_headers', 'body_template')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -236,6 +255,7 @@ class WebhookTestCase(TestCase, BaseFilterSetTests):
|
|||||||
class EventRuleTestCase(TestCase, BaseFilterSetTests):
|
class EventRuleTestCase(TestCase, BaseFilterSetTests):
|
||||||
queryset = EventRule.objects.all()
|
queryset = EventRule.objects.all()
|
||||||
filterset = EventRuleFilterSet
|
filterset = EventRuleFilterSet
|
||||||
|
ignore_fields = ('action_data', 'conditions')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -389,7 +409,7 @@ class EventRuleTestCase(TestCase, BaseFilterSetTests):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
|
|
||||||
class CustomLinkTestCase(TestCase, BaseFilterSetTests):
|
class CustomLinkTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = CustomLink.objects.all()
|
queryset = CustomLink.objects.all()
|
||||||
filterset = CustomLinkFilterSet
|
filterset = CustomLinkFilterSet
|
||||||
|
|
||||||
@ -458,9 +478,10 @@ class CustomLinkTestCase(TestCase, BaseFilterSetTests):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
|
|
||||||
class SavedFilterTestCase(TestCase, BaseFilterSetTests):
|
class SavedFilterTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = SavedFilter.objects.all()
|
queryset = SavedFilter.objects.all()
|
||||||
filterset = SavedFilterFilterSet
|
filterset = SavedFilterFilterSet
|
||||||
|
ignore_fields = ('parameters',)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -631,9 +652,10 @@ class BookmarkTestCase(TestCase, BaseFilterSetTests):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
||||||
|
|
||||||
|
|
||||||
class ExportTemplateTestCase(TestCase, BaseFilterSetTests):
|
class ExportTemplateTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = ExportTemplate.objects.all()
|
queryset = ExportTemplate.objects.all()
|
||||||
filterset = ExportTemplateFilterSet
|
filterset = ExportTemplateFilterSet
|
||||||
|
ignore_fields = ('template_code', 'data_path')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -667,9 +689,10 @@ class ExportTemplateTestCase(TestCase, BaseFilterSetTests):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
|
||||||
class ImageAttachmentTestCase(TestCase, BaseFilterSetTests):
|
class ImageAttachmentTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = ImageAttachment.objects.all()
|
queryset = ImageAttachment.objects.all()
|
||||||
filterset = ImageAttachmentFilterSet
|
filterset = ImageAttachmentFilterSet
|
||||||
|
ignore_fields = ('image',)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -744,12 +767,6 @@ class ImageAttachmentTestCase(TestCase, BaseFilterSetTests):
|
|||||||
}
|
}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
def test_created(self):
|
|
||||||
pk_list = self.queryset.values_list('pk', flat=True)[:2]
|
|
||||||
self.queryset.filter(pk__in=pk_list).update(created=datetime(2021, 1, 1, 0, 0, 0, tzinfo=timezone.utc))
|
|
||||||
params = {'created': '2021-01-01T00:00:00'}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
|
|
||||||
class JournalEntryTestCase(TestCase, ChangeLoggedFilterSetTests):
|
class JournalEntryTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = JournalEntry.objects.all()
|
queryset = JournalEntry.objects.all()
|
||||||
@ -857,6 +874,7 @@ class JournalEntryTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
class ConfigContextTestCase(TestCase, ChangeLoggedFilterSetTests):
|
class ConfigContextTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = ConfigContext.objects.all()
|
queryset = ConfigContext.objects.all()
|
||||||
filterset = ConfigContextFilterSet
|
filterset = ConfigContextFilterSet
|
||||||
|
ignore_fields = ('data', 'data_path')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -1025,11 +1043,11 @@ class ConfigContextTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
params = {'device_type_id': [device_types[0].pk, device_types[1].pk]}
|
params = {'device_type_id': [device_types[0].pk, device_types[1].pk]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
def test_role(self):
|
def test_device_role(self):
|
||||||
device_roles = DeviceRole.objects.all()[:2]
|
device_roles = DeviceRole.objects.all()[:2]
|
||||||
params = {'role_id': [device_roles[0].pk, device_roles[1].pk]}
|
params = {'device_role_id': [device_roles[0].pk, device_roles[1].pk]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
params = {'role': [device_roles[0].slug, device_roles[1].slug]}
|
params = {'device_role': [device_roles[0].slug, device_roles[1].slug]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
def test_platform(self):
|
def test_platform(self):
|
||||||
@ -1080,9 +1098,10 @@ class ConfigContextTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
|
||||||
class ConfigTemplateTestCase(TestCase, BaseFilterSetTests):
|
class ConfigTemplateTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = ConfigTemplate.objects.all()
|
queryset = ConfigTemplate.objects.all()
|
||||||
filterset = ConfigTemplateFilterSet
|
filterset = ConfigTemplateFilterSet
|
||||||
|
ignore_fields = ('template_code', 'environment_params', 'data_path')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -1109,6 +1128,93 @@ class ConfigTemplateTestCase(TestCase, BaseFilterSetTests):
|
|||||||
class TagTestCase(TestCase, ChangeLoggedFilterSetTests):
|
class TagTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = Tag.objects.all()
|
queryset = Tag.objects.all()
|
||||||
filterset = TagFilterSet
|
filterset = TagFilterSet
|
||||||
|
ignore_fields = (
|
||||||
|
'object_types',
|
||||||
|
|
||||||
|
# Reverse relationships (to tagged models) we can ignore
|
||||||
|
'aggregate',
|
||||||
|
'asn',
|
||||||
|
'asnrange',
|
||||||
|
'cable',
|
||||||
|
'circuit',
|
||||||
|
'circuittermination',
|
||||||
|
'circuittype',
|
||||||
|
'cluster',
|
||||||
|
'clustergroup',
|
||||||
|
'clustertype',
|
||||||
|
'configtemplate',
|
||||||
|
'consoleport',
|
||||||
|
'consoleserverport',
|
||||||
|
'contact',
|
||||||
|
'contactassignment',
|
||||||
|
'contactgroup',
|
||||||
|
'contactrole',
|
||||||
|
'datasource',
|
||||||
|
'device',
|
||||||
|
'devicebay',
|
||||||
|
'devicerole',
|
||||||
|
'devicetype',
|
||||||
|
'dummymodel', # From dummy_plugin
|
||||||
|
'eventrule',
|
||||||
|
'fhrpgroup',
|
||||||
|
'frontport',
|
||||||
|
'ikepolicy',
|
||||||
|
'ikeproposal',
|
||||||
|
'interface',
|
||||||
|
'inventoryitem',
|
||||||
|
'inventoryitemrole',
|
||||||
|
'ipaddress',
|
||||||
|
'iprange',
|
||||||
|
'ipsecpolicy',
|
||||||
|
'ipsecprofile',
|
||||||
|
'ipsecproposal',
|
||||||
|
'journalentry',
|
||||||
|
'l2vpn',
|
||||||
|
'l2vpntermination',
|
||||||
|
'location',
|
||||||
|
'manufacturer',
|
||||||
|
'module',
|
||||||
|
'modulebay',
|
||||||
|
'moduletype',
|
||||||
|
'platform',
|
||||||
|
'powerfeed',
|
||||||
|
'poweroutlet',
|
||||||
|
'powerpanel',
|
||||||
|
'powerport',
|
||||||
|
'prefix',
|
||||||
|
'provider',
|
||||||
|
'provideraccount',
|
||||||
|
'providernetwork',
|
||||||
|
'rack',
|
||||||
|
'rackreservation',
|
||||||
|
'rackrole',
|
||||||
|
'rearport',
|
||||||
|
'region',
|
||||||
|
'rir',
|
||||||
|
'role',
|
||||||
|
'routetarget',
|
||||||
|
'service',
|
||||||
|
'servicetemplate',
|
||||||
|
'site',
|
||||||
|
'sitegroup',
|
||||||
|
'tenant',
|
||||||
|
'tenantgroup',
|
||||||
|
'tunnel',
|
||||||
|
'tunnelgroup',
|
||||||
|
'tunneltermination',
|
||||||
|
'virtualchassis',
|
||||||
|
'virtualdevicecontext',
|
||||||
|
'virtualdisk',
|
||||||
|
'virtualmachine',
|
||||||
|
'vlan',
|
||||||
|
'vlangroup',
|
||||||
|
'vminterface',
|
||||||
|
'vrf',
|
||||||
|
'webhook',
|
||||||
|
'wirelesslan',
|
||||||
|
'wirelesslangroup',
|
||||||
|
'wirelesslink',
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -1177,6 +1283,7 @@ class TagTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
class ObjectChangeTestCase(TestCase, BaseFilterSetTests):
|
class ObjectChangeTestCase(TestCase, BaseFilterSetTests):
|
||||||
queryset = ObjectChange.objects.all()
|
queryset = ObjectChange.objects.all()
|
||||||
filterset = ObjectChangeFilterSet
|
filterset = ObjectChangeFilterSet
|
||||||
|
ignore_fields = ('prechange_data', 'postchange_data')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
|
@ -62,14 +62,14 @@ class CustomFieldModelFormTest(TestCase):
|
|||||||
cf_object = CustomField.objects.create(
|
cf_object = CustomField.objects.create(
|
||||||
name='object',
|
name='object',
|
||||||
type=CustomFieldTypeChoices.TYPE_OBJECT,
|
type=CustomFieldTypeChoices.TYPE_OBJECT,
|
||||||
object_type=ObjectType.objects.get_for_model(Site)
|
related_object_type=ObjectType.objects.get_for_model(Site)
|
||||||
)
|
)
|
||||||
cf_object.object_types.set([object_type])
|
cf_object.object_types.set([object_type])
|
||||||
|
|
||||||
cf_multiobject = CustomField.objects.create(
|
cf_multiobject = CustomField.objects.create(
|
||||||
name='multiobject',
|
name='multiobject',
|
||||||
type=CustomFieldTypeChoices.TYPE_MULTIOBJECT,
|
type=CustomFieldTypeChoices.TYPE_MULTIOBJECT,
|
||||||
object_type=ObjectType.objects.get_for_model(Site)
|
related_object_type=ObjectType.objects.get_for_model(Site)
|
||||||
)
|
)
|
||||||
cf_multiobject.object_types.set([object_type])
|
cf_multiobject.object_types.set([object_type])
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ class CustomFieldTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|||||||
}
|
}
|
||||||
|
|
||||||
cls.csv_data = (
|
cls.csv_data = (
|
||||||
'name,label,type,object_types,object_type,weight,search_weight,filter_logic,choice_set,validation_minimum,validation_maximum,validation_regex,ui_visible,ui_editable',
|
'name,label,type,object_types,related_object_type,weight,search_weight,filter_logic,choice_set,validation_minimum,validation_maximum,validation_regex,ui_visible,ui_editable',
|
||||||
'field4,Field 4,text,dcim.site,,100,1000,exact,,,,[a-z]{3},always,yes',
|
'field4,Field 4,text,dcim.site,,100,1000,exact,,,,[a-z]{3},always,yes',
|
||||||
'field5,Field 5,integer,dcim.site,,100,2000,exact,,1,100,,always,yes',
|
'field5,Field 5,integer,dcim.site,,100,2000,exact,,1,100,,always,yes',
|
||||||
'field6,Field 6,select,dcim.site,,100,3000,exact,Choice Set 1,,,,always,yes',
|
'field6,Field 6,select,dcim.site,,100,3000,exact,Choice Set 1,,,,always,yes',
|
||||||
|
@ -17,6 +17,7 @@ from extras.dashboard.forms import DashboardWidgetAddForm, DashboardWidgetForm
|
|||||||
from extras.dashboard.utils import get_widget_class
|
from extras.dashboard.utils import get_widget_class
|
||||||
from netbox.constants import DEFAULT_ACTION_PERMISSIONS
|
from netbox.constants import DEFAULT_ACTION_PERMISSIONS
|
||||||
from netbox.views import generic
|
from netbox.views import generic
|
||||||
|
from netbox.views.generic.mixins import TableMixin
|
||||||
from utilities.forms import ConfirmationForm, get_field_value
|
from utilities.forms import ConfirmationForm, get_field_value
|
||||||
from utilities.paginator import EnhancedPaginator, get_paginate_count
|
from utilities.paginator import EnhancedPaginator, get_paginate_count
|
||||||
from utilities.rqworker import get_workers_for_queue
|
from utilities.rqworker import get_workers_for_queue
|
||||||
@ -26,6 +27,7 @@ from utilities.views import ContentTypePermissionRequiredMixin, register_model_v
|
|||||||
from . import filtersets, forms, tables
|
from . import filtersets, forms, tables
|
||||||
from .models import *
|
from .models import *
|
||||||
from .scripts import run_script
|
from .scripts import run_script
|
||||||
|
from .tables import ReportResultsTable, ScriptResultsTable
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -757,7 +759,6 @@ class ImageAttachmentListView(generic.ObjectListView):
|
|||||||
class ImageAttachmentEditView(generic.ObjectEditView):
|
class ImageAttachmentEditView(generic.ObjectEditView):
|
||||||
queryset = ImageAttachment.objects.all()
|
queryset = ImageAttachment.objects.all()
|
||||||
form = forms.ImageAttachmentForm
|
form = forms.ImageAttachmentForm
|
||||||
template_name = 'extras/imageattachment_edit.html'
|
|
||||||
|
|
||||||
def alter_object(self, instance, request, args, kwargs):
|
def alter_object(self, instance, request, args, kwargs):
|
||||||
if not instance.pk:
|
if not instance.pk:
|
||||||
@ -1143,19 +1144,72 @@ class LegacyScriptRedirectView(ContentTypePermissionRequiredMixin, View):
|
|||||||
return redirect(f'{url}{path}')
|
return redirect(f'{url}{path}')
|
||||||
|
|
||||||
|
|
||||||
class ScriptResultView(generic.ObjectView):
|
class ScriptResultView(TableMixin, generic.ObjectView):
|
||||||
queryset = Job.objects.all()
|
queryset = Job.objects.all()
|
||||||
|
|
||||||
def get_required_permission(self):
|
def get_required_permission(self):
|
||||||
return 'extras.view_script'
|
return 'extras.view_script'
|
||||||
|
|
||||||
|
def get_table(self, job, request, bulk_actions=True):
|
||||||
|
data = []
|
||||||
|
tests = None
|
||||||
|
table = None
|
||||||
|
index = 0
|
||||||
|
if job.data:
|
||||||
|
if 'log' in job.data:
|
||||||
|
if 'tests' in job.data:
|
||||||
|
tests = job.data['tests']
|
||||||
|
|
||||||
|
for log in job.data['log']:
|
||||||
|
index += 1
|
||||||
|
result = {
|
||||||
|
'index': index,
|
||||||
|
'time': log.get('time'),
|
||||||
|
'status': log.get('status'),
|
||||||
|
'message': log.get('message'),
|
||||||
|
}
|
||||||
|
data.append(result)
|
||||||
|
|
||||||
|
table = ScriptResultsTable(data, user=request.user)
|
||||||
|
table.configure(request)
|
||||||
|
else:
|
||||||
|
# for legacy reports
|
||||||
|
tests = job.data
|
||||||
|
|
||||||
|
if tests:
|
||||||
|
for method, test_data in tests.items():
|
||||||
|
if 'log' in test_data:
|
||||||
|
for time, status, obj, url, message in test_data['log']:
|
||||||
|
index += 1
|
||||||
|
result = {
|
||||||
|
'index': index,
|
||||||
|
'method': method,
|
||||||
|
'time': time,
|
||||||
|
'status': status,
|
||||||
|
'object': obj,
|
||||||
|
'url': url,
|
||||||
|
'message': message,
|
||||||
|
}
|
||||||
|
data.append(result)
|
||||||
|
|
||||||
|
table = ReportResultsTable(data, user=request.user)
|
||||||
|
table.configure(request)
|
||||||
|
|
||||||
|
return table
|
||||||
|
|
||||||
def get(self, request, **kwargs):
|
def get(self, request, **kwargs):
|
||||||
|
table = None
|
||||||
job = get_object_or_404(Job.objects.all(), pk=kwargs.get('job_pk'))
|
job = get_object_or_404(Job.objects.all(), pk=kwargs.get('job_pk'))
|
||||||
|
|
||||||
|
if job.completed:
|
||||||
|
table = self.get_table(job, request, bulk_actions=False)
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
'script': job.object,
|
'script': job.object,
|
||||||
'job': job,
|
'job': job,
|
||||||
|
'table': table,
|
||||||
}
|
}
|
||||||
|
|
||||||
if job.data and 'log' in job.data:
|
if job.data and 'log' in job.data:
|
||||||
# Script
|
# Script
|
||||||
context['tests'] = job.data.get('tests', {})
|
context['tests'] = job.data.get('tests', {})
|
||||||
|
@ -8,6 +8,7 @@ from drf_spectacular.types import OpenApiTypes
|
|||||||
from drf_spectacular.utils import extend_schema_field
|
from drf_spectacular.utils import extend_schema_field
|
||||||
from netaddr.core import AddrFormatError
|
from netaddr.core import AddrFormatError
|
||||||
|
|
||||||
|
from circuits.models import Provider
|
||||||
from dcim.models import Device, Interface, Region, Site, SiteGroup
|
from dcim.models import Device, Interface, Region, Site, SiteGroup
|
||||||
from netbox.filtersets import ChangeLoggedModelFilterSet, OrganizationalModelFilterSet, NetBoxModelFilterSet
|
from netbox.filtersets import ChangeLoggedModelFilterSet, OrganizationalModelFilterSet, NetBoxModelFilterSet
|
||||||
from tenancy.filtersets import TenancyFilterSet
|
from tenancy.filtersets import TenancyFilterSet
|
||||||
@ -75,7 +76,7 @@ class VRFFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = VRF
|
model = VRF
|
||||||
fields = ['id', 'name', 'rd', 'enforce_unique', 'description']
|
fields = ('id', 'name', 'rd', 'enforce_unique', 'description')
|
||||||
|
|
||||||
|
|
||||||
class RouteTargetFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
|
class RouteTargetFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
|
||||||
@ -101,6 +102,28 @@ class RouteTargetFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
|
|||||||
to_field_name='rd',
|
to_field_name='rd',
|
||||||
label=_('Export VRF (RD)'),
|
label=_('Export VRF (RD)'),
|
||||||
)
|
)
|
||||||
|
importing_l2vpn_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='importing_l2vpns',
|
||||||
|
queryset=L2VPN.objects.all(),
|
||||||
|
label=_('Importing L2VPN'),
|
||||||
|
)
|
||||||
|
importing_l2vpn = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='importing_l2vpns__identifier',
|
||||||
|
queryset=L2VPN.objects.all(),
|
||||||
|
to_field_name='identifier',
|
||||||
|
label=_('Importing L2VPN (identifier)'),
|
||||||
|
)
|
||||||
|
exporting_l2vpn_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='exporting_l2vpns',
|
||||||
|
queryset=L2VPN.objects.all(),
|
||||||
|
label=_('Exporting L2VPN'),
|
||||||
|
)
|
||||||
|
exporting_l2vpn = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='exporting_l2vpns__identifier',
|
||||||
|
queryset=L2VPN.objects.all(),
|
||||||
|
to_field_name='identifier',
|
||||||
|
label=_('Exporting L2VPN (identifier)'),
|
||||||
|
)
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
@ -112,14 +135,14 @@ class RouteTargetFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = RouteTarget
|
model = RouteTarget
|
||||||
fields = ['id', 'name', 'description']
|
fields = ('id', 'name', 'description')
|
||||||
|
|
||||||
|
|
||||||
class RIRFilterSet(OrganizationalModelFilterSet):
|
class RIRFilterSet(OrganizationalModelFilterSet):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = RIR
|
model = RIR
|
||||||
fields = ['id', 'name', 'slug', 'is_private', 'description']
|
fields = ('id', 'name', 'slug', 'is_private', 'description')
|
||||||
|
|
||||||
|
|
||||||
class AggregateFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
|
class AggregateFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
|
||||||
@ -144,7 +167,7 @@ class AggregateFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Aggregate
|
model = Aggregate
|
||||||
fields = ['id', 'date_added', 'description']
|
fields = ('id', 'date_added', 'description')
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
@ -183,7 +206,7 @@ class ASNRangeFilterSet(OrganizationalModelFilterSet, TenancyFilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ASNRange
|
model = ASNRange
|
||||||
fields = ['id', 'name', 'start', 'end', 'description']
|
fields = ('id', 'name', 'slug', 'start', 'end', 'description')
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
@ -214,10 +237,21 @@ class ASNFilterSet(OrganizationalModelFilterSet, TenancyFilterSet):
|
|||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label=_('Site (slug)'),
|
label=_('Site (slug)'),
|
||||||
)
|
)
|
||||||
|
provider_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='providers',
|
||||||
|
queryset=Provider.objects.all(),
|
||||||
|
label=_('Provider (ID)'),
|
||||||
|
)
|
||||||
|
provider = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='providers__slug',
|
||||||
|
queryset=Provider.objects.all(),
|
||||||
|
to_field_name='slug',
|
||||||
|
label=_('Provider (slug)'),
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ASN
|
model = ASN
|
||||||
fields = ['id', 'asn', 'description']
|
fields = ('id', 'asn', 'description')
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
@ -234,7 +268,7 @@ class RoleFilterSet(OrganizationalModelFilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Role
|
model = Role
|
||||||
fields = ['id', 'name', 'slug', 'description']
|
fields = ('id', 'name', 'slug', 'description', 'weight')
|
||||||
|
|
||||||
|
|
||||||
class PrefixFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
|
class PrefixFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
|
||||||
@ -359,7 +393,7 @@ class PrefixFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Prefix
|
model = Prefix
|
||||||
fields = ['id', 'is_pool', 'mark_utilized', 'description']
|
fields = ('id', 'is_pool', 'mark_utilized', 'description')
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
@ -475,7 +509,7 @@ class IPRangeFilterSet(TenancyFilterSet, NetBoxModelFilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = IPRange
|
model = IPRange
|
||||||
fields = ['id', 'mark_utilized', 'description']
|
fields = ('id', 'mark_utilized', 'size', 'description')
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
@ -628,10 +662,20 @@ class IPAddressFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
|
|||||||
role = django_filters.MultipleChoiceFilter(
|
role = django_filters.MultipleChoiceFilter(
|
||||||
choices=IPAddressRoleChoices
|
choices=IPAddressRoleChoices
|
||||||
)
|
)
|
||||||
|
service_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='services',
|
||||||
|
queryset=Service.objects.all(),
|
||||||
|
label=_('Service (ID)'),
|
||||||
|
)
|
||||||
|
nat_inside_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='nat_inside',
|
||||||
|
queryset=IPAddress.objects.all(),
|
||||||
|
label=_('NAT inside IP address (ID)'),
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = IPAddress
|
model = IPAddress
|
||||||
fields = ['id', 'dns_name', 'description']
|
fields = ('id', 'dns_name', 'description', 'assigned_object_type', 'assigned_object_id')
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
@ -758,7 +802,7 @@ class FHRPGroupFilterSet(NetBoxModelFilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = FHRPGroup
|
model = FHRPGroup
|
||||||
fields = ['id', 'group_id', 'name', 'auth_key', 'description']
|
fields = ('id', 'group_id', 'name', 'auth_key', 'description')
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
@ -819,7 +863,7 @@ class FHRPGroupAssignmentFilterSet(ChangeLoggedModelFilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = FHRPGroupAssignment
|
model = FHRPGroupAssignment
|
||||||
fields = ['id', 'group_id', 'interface_type', 'interface_id', 'priority']
|
fields = ('id', 'group_id', 'interface_type', 'interface_id', 'priority')
|
||||||
|
|
||||||
def filter_device(self, queryset, name, value):
|
def filter_device(self, queryset, name, value):
|
||||||
devices = Device.objects.filter(**{f'{name}__in': value})
|
devices = Device.objects.filter(**{f'{name}__in': value})
|
||||||
@ -849,7 +893,7 @@ class VLANGroupFilterSet(OrganizationalModelFilterSet):
|
|||||||
region = django_filters.NumberFilter(
|
region = django_filters.NumberFilter(
|
||||||
method='filter_scope'
|
method='filter_scope'
|
||||||
)
|
)
|
||||||
sitegroup = django_filters.NumberFilter(
|
site_group = django_filters.NumberFilter(
|
||||||
method='filter_scope'
|
method='filter_scope'
|
||||||
)
|
)
|
||||||
site = django_filters.NumberFilter(
|
site = django_filters.NumberFilter(
|
||||||
@ -861,16 +905,20 @@ class VLANGroupFilterSet(OrganizationalModelFilterSet):
|
|||||||
rack = django_filters.NumberFilter(
|
rack = django_filters.NumberFilter(
|
||||||
method='filter_scope'
|
method='filter_scope'
|
||||||
)
|
)
|
||||||
clustergroup = django_filters.NumberFilter(
|
cluster_group = django_filters.NumberFilter(
|
||||||
method='filter_scope'
|
method='filter_scope'
|
||||||
)
|
)
|
||||||
cluster = django_filters.NumberFilter(
|
cluster = django_filters.NumberFilter(
|
||||||
method='filter_scope'
|
method='filter_scope'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# TODO: Remove in v4.1
|
||||||
|
sitegroup = site_group
|
||||||
|
clustergroup = cluster_group
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = VLANGroup
|
model = VLANGroup
|
||||||
fields = ['id', 'name', 'slug', 'min_vid', 'max_vid', 'description', 'scope_id']
|
fields = ('id', 'name', 'slug', 'min_vid', 'max_vid', 'description', 'scope_id')
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
@ -882,8 +930,9 @@ class VLANGroupFilterSet(OrganizationalModelFilterSet):
|
|||||||
return queryset.filter(qs_filter)
|
return queryset.filter(qs_filter)
|
||||||
|
|
||||||
def filter_scope(self, queryset, name, value):
|
def filter_scope(self, queryset, name, value):
|
||||||
|
model_name = name.replace('_', '')
|
||||||
return queryset.filter(
|
return queryset.filter(
|
||||||
scope_type=ContentType.objects.get(model=name),
|
scope_type=ContentType.objects.get(model=model_name),
|
||||||
scope_id=value
|
scope_id=value
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -975,7 +1024,7 @@ class VLANFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = VLAN
|
model = VLAN
|
||||||
fields = ['id', 'vid', 'name', 'description']
|
fields = ('id', 'vid', 'name', 'description')
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
@ -1008,7 +1057,7 @@ class ServiceTemplateFilterSet(NetBoxModelFilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ServiceTemplate
|
model = ServiceTemplate
|
||||||
fields = ['id', 'name', 'protocol', 'description']
|
fields = ('id', 'name', 'protocol', 'description')
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
@ -1041,26 +1090,29 @@ class ServiceFilterSet(NetBoxModelFilterSet):
|
|||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
label=_('Virtual machine (name)'),
|
label=_('Virtual machine (name)'),
|
||||||
)
|
)
|
||||||
ipaddress_id = django_filters.ModelMultipleChoiceFilter(
|
ip_address_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
field_name='ipaddresses',
|
field_name='ipaddresses',
|
||||||
queryset=IPAddress.objects.all(),
|
queryset=IPAddress.objects.all(),
|
||||||
label=_('IP address (ID)'),
|
label=_('IP address (ID)'),
|
||||||
)
|
)
|
||||||
ipaddress = django_filters.ModelMultipleChoiceFilter(
|
ip_address = django_filters.ModelMultipleChoiceFilter(
|
||||||
field_name='ipaddresses__address',
|
field_name='ipaddresses__address',
|
||||||
queryset=IPAddress.objects.all(),
|
queryset=IPAddress.objects.all(),
|
||||||
to_field_name='address',
|
to_field_name='address',
|
||||||
label=_('IP address'),
|
label=_('IP address'),
|
||||||
)
|
)
|
||||||
|
|
||||||
port = NumericArrayFilter(
|
port = NumericArrayFilter(
|
||||||
field_name='ports',
|
field_name='ports',
|
||||||
lookup_expr='contains'
|
lookup_expr='contains'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# TODO: Remove in v4.1
|
||||||
|
ipaddress = ip_address
|
||||||
|
ipaddress_id = ip_address_id
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Service
|
model = Service
|
||||||
fields = ['id', 'name', 'protocol', 'description']
|
fields = ('id', 'name', 'protocol', 'description')
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
|
@ -13,6 +13,7 @@ from utilities.forms import add_blank_choice
|
|||||||
from utilities.forms.fields import (
|
from utilities.forms.fields import (
|
||||||
CommentField, ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, NumericArrayField,
|
CommentField, ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, NumericArrayField,
|
||||||
)
|
)
|
||||||
|
from utilities.forms.rendering import FieldSet
|
||||||
from utilities.forms.widgets import BulkEditNullBooleanSelect
|
from utilities.forms.widgets import BulkEditNullBooleanSelect
|
||||||
from virtualization.models import Cluster, ClusterGroup
|
from virtualization.models import Cluster, ClusterGroup
|
||||||
|
|
||||||
@ -55,7 +56,7 @@ class VRFBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
|
|
||||||
model = VRF
|
model = VRF
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('tenant', 'enforce_unique', 'description')),
|
FieldSet('tenant', 'enforce_unique', 'description'),
|
||||||
)
|
)
|
||||||
nullable_fields = ('tenant', 'description', 'comments')
|
nullable_fields = ('tenant', 'description', 'comments')
|
||||||
|
|
||||||
@ -75,7 +76,7 @@ class RouteTargetBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
|
|
||||||
model = RouteTarget
|
model = RouteTarget
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('tenant', 'description')),
|
FieldSet('tenant', 'description'),
|
||||||
)
|
)
|
||||||
nullable_fields = ('tenant', 'description', 'comments')
|
nullable_fields = ('tenant', 'description', 'comments')
|
||||||
|
|
||||||
@ -94,7 +95,7 @@ class RIRBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
|
|
||||||
model = RIR
|
model = RIR
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('is_private', 'description')),
|
FieldSet('is_private', 'description'),
|
||||||
)
|
)
|
||||||
nullable_fields = ('is_private', 'description')
|
nullable_fields = ('is_private', 'description')
|
||||||
|
|
||||||
@ -118,7 +119,7 @@ class ASNRangeBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
|
|
||||||
model = ASNRange
|
model = ASNRange
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('rir', 'tenant', 'description')),
|
FieldSet('rir', 'tenant', 'description'),
|
||||||
)
|
)
|
||||||
nullable_fields = ('description',)
|
nullable_fields = ('description',)
|
||||||
|
|
||||||
@ -148,7 +149,7 @@ class ASNBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
|
|
||||||
model = ASN
|
model = ASN
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('sites', 'rir', 'tenant', 'description')),
|
FieldSet('sites', 'rir', 'tenant', 'description'),
|
||||||
)
|
)
|
||||||
nullable_fields = ('tenant', 'description', 'comments')
|
nullable_fields = ('tenant', 'description', 'comments')
|
||||||
|
|
||||||
@ -177,7 +178,7 @@ class AggregateBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
|
|
||||||
model = Aggregate
|
model = Aggregate
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('rir', 'tenant', 'date_added', 'description')),
|
FieldSet('rir', 'tenant', 'date_added', 'description'),
|
||||||
)
|
)
|
||||||
nullable_fields = ('date_added', 'description', 'comments')
|
nullable_fields = ('date_added', 'description', 'comments')
|
||||||
|
|
||||||
@ -195,7 +196,7 @@ class RoleBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
|
|
||||||
model = Role
|
model = Role
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('weight', 'description')),
|
FieldSet('weight', 'description'),
|
||||||
)
|
)
|
||||||
nullable_fields = ('description',)
|
nullable_fields = ('description',)
|
||||||
|
|
||||||
@ -265,9 +266,9 @@ class PrefixBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
|
|
||||||
model = Prefix
|
model = Prefix
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('tenant', 'status', 'role', 'description')),
|
FieldSet('tenant', 'status', 'role', 'description'),
|
||||||
(_('Site'), ('region', 'site_group', 'site')),
|
FieldSet('region', 'site_group', 'site', name=_('Site')),
|
||||||
(_('Addressing'), ('vrf', 'prefix_length', 'is_pool', 'mark_utilized')),
|
FieldSet('vrf', 'prefix_length', 'is_pool', 'mark_utilized', name=_('Addressing')),
|
||||||
)
|
)
|
||||||
nullable_fields = (
|
nullable_fields = (
|
||||||
'site', 'vrf', 'tenant', 'role', 'description', 'comments',
|
'site', 'vrf', 'tenant', 'role', 'description', 'comments',
|
||||||
@ -309,7 +310,7 @@ class IPRangeBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
|
|
||||||
model = IPRange
|
model = IPRange
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('status', 'role', 'vrf', 'tenant', 'mark_utilized', 'description')),
|
FieldSet('status', 'role', 'vrf', 'tenant', 'mark_utilized', 'description'),
|
||||||
)
|
)
|
||||||
nullable_fields = (
|
nullable_fields = (
|
||||||
'vrf', 'tenant', 'role', 'description', 'comments',
|
'vrf', 'tenant', 'role', 'description', 'comments',
|
||||||
@ -357,8 +358,8 @@ class IPAddressBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
|
|
||||||
model = IPAddress
|
model = IPAddress
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('status', 'role', 'tenant', 'description')),
|
FieldSet('status', 'role', 'tenant', 'description'),
|
||||||
(_('Addressing'), ('vrf', 'mask_length', 'dns_name')),
|
FieldSet('vrf', 'mask_length', 'dns_name', name=_('Addressing')),
|
||||||
)
|
)
|
||||||
nullable_fields = (
|
nullable_fields = (
|
||||||
'vrf', 'role', 'tenant', 'dns_name', 'description', 'comments',
|
'vrf', 'role', 'tenant', 'dns_name', 'description', 'comments',
|
||||||
@ -400,8 +401,8 @@ class FHRPGroupBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
|
|
||||||
model = FHRPGroup
|
model = FHRPGroup
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('protocol', 'group_id', 'name', 'description')),
|
FieldSet('protocol', 'group_id', 'name', 'description'),
|
||||||
(_('Authentication'), ('auth_type', 'auth_key')),
|
FieldSet('auth_type', 'auth_key', name=_('Authentication')),
|
||||||
)
|
)
|
||||||
nullable_fields = ('auth_type', 'auth_key', 'name', 'description', 'comments')
|
nullable_fields = ('auth_type', 'auth_key', 'name', 'description', 'comments')
|
||||||
|
|
||||||
@ -485,8 +486,10 @@ class VLANGroupBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
|
|
||||||
model = VLANGroup
|
model = VLANGroup
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('site', 'min_vid', 'max_vid', 'description')),
|
FieldSet('site', 'min_vid', 'max_vid', 'description'),
|
||||||
(_('Scope'), ('scope_type', 'region', 'sitegroup', 'site', 'location', 'rack', 'clustergroup', 'cluster')),
|
FieldSet(
|
||||||
|
'scope_type', 'region', 'sitegroup', 'site', 'location', 'rack', 'clustergroup', 'cluster', name=_('Scope')
|
||||||
|
),
|
||||||
)
|
)
|
||||||
nullable_fields = ('description',)
|
nullable_fields = ('description',)
|
||||||
|
|
||||||
@ -556,8 +559,8 @@ class VLANBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
|
|
||||||
model = VLAN
|
model = VLAN
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('status', 'role', 'tenant', 'description')),
|
FieldSet('status', 'role', 'tenant', 'description'),
|
||||||
(_('Site & Group'), ('region', 'site_group', 'site', 'group')),
|
FieldSet('region', 'site_group', 'site', 'group', name=_('Site & Group')),
|
||||||
)
|
)
|
||||||
nullable_fields = (
|
nullable_fields = (
|
||||||
'site', 'group', 'tenant', 'role', 'description', 'comments',
|
'site', 'group', 'tenant', 'role', 'description', 'comments',
|
||||||
@ -587,7 +590,7 @@ class ServiceTemplateBulkEditForm(NetBoxModelBulkEditForm):
|
|||||||
|
|
||||||
model = ServiceTemplate
|
model = ServiceTemplate
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('protocol', 'ports', 'description')),
|
FieldSet('protocol', 'ports', 'description'),
|
||||||
)
|
)
|
||||||
nullable_fields = ('description', 'comments')
|
nullable_fields = ('description', 'comments')
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ from netbox.forms import NetBoxModelFilterSetForm
|
|||||||
from tenancy.forms import TenancyFilterForm
|
from tenancy.forms import TenancyFilterForm
|
||||||
from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES, add_blank_choice
|
from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES, add_blank_choice
|
||||||
from utilities.forms.fields import DynamicModelChoiceField, DynamicModelMultipleChoiceField, TagFilterField
|
from utilities.forms.fields import DynamicModelChoiceField, DynamicModelMultipleChoiceField, TagFilterField
|
||||||
|
from utilities.forms.rendering import FieldSet
|
||||||
from virtualization.models import VirtualMachine
|
from virtualization.models import VirtualMachine
|
||||||
from vpn.models import L2VPN
|
from vpn.models import L2VPN
|
||||||
|
|
||||||
@ -42,9 +43,9 @@ IPADDRESS_MASK_LENGTH_CHOICES = add_blank_choice([
|
|||||||
class VRFFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
class VRFFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||||
model = VRF
|
model = VRF
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
FieldSet('q', 'filter_id', 'tag'),
|
||||||
(_('Route Targets'), ('import_target_id', 'export_target_id')),
|
FieldSet('import_target_id', 'export_target_id', name=_('Route Targets')),
|
||||||
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
|
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||||
)
|
)
|
||||||
import_target_id = DynamicModelMultipleChoiceField(
|
import_target_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=RouteTarget.objects.all(),
|
queryset=RouteTarget.objects.all(),
|
||||||
@ -62,9 +63,9 @@ class VRFFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
|||||||
class RouteTargetFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
class RouteTargetFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||||
model = RouteTarget
|
model = RouteTarget
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
FieldSet('q', 'filter_id', 'tag'),
|
||||||
(_('VRF'), ('importing_vrf_id', 'exporting_vrf_id')),
|
FieldSet('importing_vrf_id', 'exporting_vrf_id', name=_('VRF')),
|
||||||
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
|
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||||
)
|
)
|
||||||
importing_vrf_id = DynamicModelMultipleChoiceField(
|
importing_vrf_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=VRF.objects.all(),
|
queryset=VRF.objects.all(),
|
||||||
@ -94,9 +95,9 @@ class RIRFilterForm(NetBoxModelFilterSetForm):
|
|||||||
class AggregateFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
class AggregateFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||||
model = Aggregate
|
model = Aggregate
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
FieldSet('q', 'filter_id', 'tag'),
|
||||||
(_('Attributes'), ('family', 'rir_id')),
|
FieldSet('family', 'rir_id', name=_('Attributes')),
|
||||||
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
|
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||||
)
|
)
|
||||||
family = forms.ChoiceField(
|
family = forms.ChoiceField(
|
||||||
required=False,
|
required=False,
|
||||||
@ -114,9 +115,9 @@ class AggregateFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
|||||||
class ASNRangeFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
class ASNRangeFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||||
model = ASNRange
|
model = ASNRange
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
FieldSet('q', 'filter_id', 'tag'),
|
||||||
(_('Range'), ('rir_id', 'start', 'end')),
|
FieldSet('rir_id', 'start', 'end', name=_('Range')),
|
||||||
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
|
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||||
)
|
)
|
||||||
rir_id = DynamicModelMultipleChoiceField(
|
rir_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=RIR.objects.all(),
|
queryset=RIR.objects.all(),
|
||||||
@ -137,9 +138,9 @@ class ASNRangeFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
|||||||
class ASNFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
class ASNFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||||
model = ASN
|
model = ASN
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
FieldSet('q', 'filter_id', 'tag'),
|
||||||
(_('Assignment'), ('rir_id', 'site_id')),
|
FieldSet('rir_id', 'site_id', name=_('Assignment')),
|
||||||
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
|
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||||
)
|
)
|
||||||
rir_id = DynamicModelMultipleChoiceField(
|
rir_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=RIR.objects.all(),
|
queryset=RIR.objects.all(),
|
||||||
@ -162,11 +163,14 @@ class RoleFilterForm(NetBoxModelFilterSetForm):
|
|||||||
class PrefixFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
class PrefixFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||||
model = Prefix
|
model = Prefix
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
FieldSet('q', 'filter_id', 'tag'),
|
||||||
(_('Addressing'), ('within_include', 'family', 'status', 'role_id', 'mask_length', 'is_pool', 'mark_utilized')),
|
FieldSet(
|
||||||
(_('VRF'), ('vrf_id', 'present_in_vrf_id')),
|
'within_include', 'family', 'status', 'role_id', 'mask_length', 'is_pool', 'mark_utilized',
|
||||||
(_('Location'), ('region_id', 'site_group_id', 'site_id')),
|
name=_('Addressing')
|
||||||
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
|
),
|
||||||
|
FieldSet('vrf_id', 'present_in_vrf_id', name=_('VRF')),
|
||||||
|
FieldSet('region_id', 'site_group_id', 'site_id', name=_('Location')),
|
||||||
|
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||||
)
|
)
|
||||||
mask_length__lte = forms.IntegerField(
|
mask_length__lte = forms.IntegerField(
|
||||||
widget=forms.HiddenInput()
|
widget=forms.HiddenInput()
|
||||||
@ -251,9 +255,9 @@ class PrefixFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
|||||||
class IPRangeFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
class IPRangeFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||||
model = IPRange
|
model = IPRange
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
FieldSet('q', 'filter_id', 'tag'),
|
||||||
(_('Attributes'), ('family', 'vrf_id', 'status', 'role_id', 'mark_utilized')),
|
FieldSet('family', 'vrf_id', 'status', 'role_id', 'mark_utilized', name=_('Attributes')),
|
||||||
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
|
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||||
)
|
)
|
||||||
family = forms.ChoiceField(
|
family = forms.ChoiceField(
|
||||||
required=False,
|
required=False,
|
||||||
@ -290,11 +294,14 @@ class IPRangeFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
|||||||
class IPAddressFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
class IPAddressFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||||
model = IPAddress
|
model = IPAddress
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
FieldSet('q', 'filter_id', 'tag'),
|
||||||
(_('Attributes'), ('parent', 'family', 'status', 'role', 'mask_length', 'assigned_to_interface', 'dns_name')),
|
FieldSet(
|
||||||
(_('VRF'), ('vrf_id', 'present_in_vrf_id')),
|
'parent', 'family', 'status', 'role', 'mask_length', 'assigned_to_interface', 'dns_name',
|
||||||
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
|
name=_('Attributes')
|
||||||
(_('Device/VM'), ('device_id', 'virtual_machine_id')),
|
),
|
||||||
|
FieldSet('vrf_id', 'present_in_vrf_id', name=_('VRF')),
|
||||||
|
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||||
|
FieldSet('device_id', 'virtual_machine_id', name=_('Device/VM')),
|
||||||
)
|
)
|
||||||
selector_fields = ('filter_id', 'q', 'region_id', 'group_id', 'parent', 'status', 'role')
|
selector_fields = ('filter_id', 'q', 'region_id', 'group_id', 'parent', 'status', 'role')
|
||||||
parent = forms.CharField(
|
parent = forms.CharField(
|
||||||
@ -304,7 +311,7 @@ class IPAddressFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
|||||||
'placeholder': 'Prefix',
|
'placeholder': 'Prefix',
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
label='Parent Prefix'
|
label=_('Parent Prefix')
|
||||||
)
|
)
|
||||||
family = forms.ChoiceField(
|
family = forms.ChoiceField(
|
||||||
required=False,
|
required=False,
|
||||||
@ -364,9 +371,9 @@ class IPAddressFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
|||||||
class FHRPGroupFilterForm(NetBoxModelFilterSetForm):
|
class FHRPGroupFilterForm(NetBoxModelFilterSetForm):
|
||||||
model = FHRPGroup
|
model = FHRPGroup
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
FieldSet('q', 'filter_id', 'tag'),
|
||||||
(_('Attributes'), ('name', 'protocol', 'group_id')),
|
FieldSet('name', 'protocol', 'group_id', name=_('Attributes')),
|
||||||
(_('Authentication'), ('auth_type', 'auth_key')),
|
FieldSet('auth_type', 'auth_key', name=_('Authentication')),
|
||||||
)
|
)
|
||||||
name = forms.CharField(
|
name = forms.CharField(
|
||||||
label=_('Name'),
|
label=_('Name'),
|
||||||
@ -396,9 +403,9 @@ class FHRPGroupFilterForm(NetBoxModelFilterSetForm):
|
|||||||
|
|
||||||
class VLANGroupFilterForm(NetBoxModelFilterSetForm):
|
class VLANGroupFilterForm(NetBoxModelFilterSetForm):
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
FieldSet('q', 'filter_id', 'tag'),
|
||||||
(_('Location'), ('region', 'sitegroup', 'site', 'location', 'rack')),
|
FieldSet('region', 'sitegroup', 'site', 'location', 'rack', name=_('Location')),
|
||||||
(_('VLAN ID'), ('min_vid', 'max_vid')),
|
FieldSet('min_vid', 'max_vid', name=_('VLAN ID')),
|
||||||
)
|
)
|
||||||
model = VLANGroup
|
model = VLANGroup
|
||||||
region = DynamicModelMultipleChoiceField(
|
region = DynamicModelMultipleChoiceField(
|
||||||
@ -444,10 +451,10 @@ class VLANGroupFilterForm(NetBoxModelFilterSetForm):
|
|||||||
class VLANFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
class VLANFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||||
model = VLAN
|
model = VLAN
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
FieldSet('q', 'filter_id', 'tag'),
|
||||||
(_('Location'), ('region_id', 'site_group_id', 'site_id')),
|
FieldSet('region_id', 'site_group_id', 'site_id', name=_('Location')),
|
||||||
(_('Attributes'), ('group_id', 'status', 'role_id', 'vid', 'l2vpn_id')),
|
FieldSet('group_id', 'status', 'role_id', 'vid', 'l2vpn_id', name=_('Attributes')),
|
||||||
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
|
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||||
)
|
)
|
||||||
selector_fields = ('filter_id', 'q', 'site_id')
|
selector_fields = ('filter_id', 'q', 'site_id')
|
||||||
region_id = DynamicModelMultipleChoiceField(
|
region_id = DynamicModelMultipleChoiceField(
|
||||||
@ -504,8 +511,8 @@ class VLANFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
|||||||
class ServiceTemplateFilterForm(NetBoxModelFilterSetForm):
|
class ServiceTemplateFilterForm(NetBoxModelFilterSetForm):
|
||||||
model = ServiceTemplate
|
model = ServiceTemplate
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
FieldSet('q', 'filter_id', 'tag'),
|
||||||
(_('Attributes'), ('protocol', 'port')),
|
FieldSet('protocol', 'port', name=_('Attributes')),
|
||||||
)
|
)
|
||||||
protocol = forms.ChoiceField(
|
protocol = forms.ChoiceField(
|
||||||
label=_('Protocol'),
|
label=_('Protocol'),
|
||||||
@ -522,9 +529,9 @@ class ServiceTemplateFilterForm(NetBoxModelFilterSetForm):
|
|||||||
class ServiceFilterForm(ServiceTemplateFilterForm):
|
class ServiceFilterForm(ServiceTemplateFilterForm):
|
||||||
model = Service
|
model = Service
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'filter_id', 'tag')),
|
FieldSet('q', 'filter_id', 'tag'),
|
||||||
(_('Attributes'), ('protocol', 'port')),
|
FieldSet('protocol', 'port', name=_('Attributes')),
|
||||||
(_('Assignment'), ('device_id', 'virtual_machine_id')),
|
FieldSet('device_id', 'virtual_machine_id', name=_('Assignment')),
|
||||||
)
|
)
|
||||||
device_id = DynamicModelMultipleChoiceField(
|
device_id = DynamicModelMultipleChoiceField(
|
||||||
queryset=Device.objects.all(),
|
queryset=Device.objects.all(),
|
||||||
|
@ -16,6 +16,7 @@ from utilities.forms.fields import (
|
|||||||
CommentField, ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, NumericArrayField,
|
CommentField, ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, NumericArrayField,
|
||||||
SlugField,
|
SlugField,
|
||||||
)
|
)
|
||||||
|
from utilities.forms.rendering import FieldSet, InlineFields, ObjectAttribute, TabbedGroups
|
||||||
from utilities.forms.widgets import DatePicker
|
from utilities.forms.widgets import DatePicker
|
||||||
from virtualization.models import Cluster, ClusterGroup, VirtualMachine, VMInterface
|
from virtualization.models import Cluster, ClusterGroup, VirtualMachine, VMInterface
|
||||||
|
|
||||||
@ -56,9 +57,9 @@ class VRFForm(TenancyForm, NetBoxModelForm):
|
|||||||
comments = CommentField()
|
comments = CommentField()
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('VRF'), ('name', 'rd', 'enforce_unique', 'description', 'tags')),
|
FieldSet('name', 'rd', 'enforce_unique', 'description', 'tags', name=_('VRF')),
|
||||||
(_('Route Targets'), ('import_targets', 'export_targets')),
|
FieldSet('import_targets', 'export_targets', name=_('Route Targets')),
|
||||||
(_('Tenancy'), ('tenant_group', 'tenant')),
|
FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -74,8 +75,8 @@ class VRFForm(TenancyForm, NetBoxModelForm):
|
|||||||
|
|
||||||
class RouteTargetForm(TenancyForm, NetBoxModelForm):
|
class RouteTargetForm(TenancyForm, NetBoxModelForm):
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
('Route Target', ('name', 'description', 'tags')),
|
FieldSet('name', 'description', 'tags', name=_('Route Target')),
|
||||||
('Tenancy', ('tenant_group', 'tenant')),
|
FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
|
||||||
)
|
)
|
||||||
comments = CommentField()
|
comments = CommentField()
|
||||||
|
|
||||||
@ -90,9 +91,7 @@ class RIRForm(NetBoxModelForm):
|
|||||||
slug = SlugField()
|
slug = SlugField()
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('RIR'), (
|
FieldSet('name', 'slug', 'is_private', 'description', 'tags', name=_('RIR')),
|
||||||
'name', 'slug', 'is_private', 'description', 'tags',
|
|
||||||
)),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -110,8 +109,8 @@ class AggregateForm(TenancyForm, NetBoxModelForm):
|
|||||||
comments = CommentField()
|
comments = CommentField()
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Aggregate'), ('prefix', 'rir', 'date_added', 'description', 'tags')),
|
FieldSet('prefix', 'rir', 'date_added', 'description', 'tags', name=_('Aggregate')),
|
||||||
(_('Tenancy'), ('tenant_group', 'tenant')),
|
FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -131,8 +130,8 @@ class ASNRangeForm(TenancyForm, NetBoxModelForm):
|
|||||||
)
|
)
|
||||||
slug = SlugField()
|
slug = SlugField()
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('ASN Range'), ('name', 'slug', 'rir', 'start', 'end', 'description', 'tags')),
|
FieldSet('name', 'slug', 'rir', 'start', 'end', 'description', 'tags', name=_('ASN Range')),
|
||||||
(_('Tenancy'), ('tenant_group', 'tenant')),
|
FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -155,8 +154,8 @@ class ASNForm(TenancyForm, NetBoxModelForm):
|
|||||||
comments = CommentField()
|
comments = CommentField()
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('ASN'), ('asn', 'rir', 'sites', 'description', 'tags')),
|
FieldSet('asn', 'rir', 'sites', 'description', 'tags', name=_('ASN')),
|
||||||
(_('Tenancy'), ('tenant_group', 'tenant')),
|
FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -184,9 +183,7 @@ class RoleForm(NetBoxModelForm):
|
|||||||
slug = SlugField()
|
slug = SlugField()
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Role'), (
|
FieldSet('name', 'slug', 'weight', 'description', 'tags', name=_('Role')),
|
||||||
'name', 'slug', 'weight', 'description', 'tags',
|
|
||||||
)),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -226,9 +223,11 @@ class PrefixForm(TenancyForm, NetBoxModelForm):
|
|||||||
comments = CommentField()
|
comments = CommentField()
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Prefix'), ('prefix', 'status', 'vrf', 'role', 'is_pool', 'mark_utilized', 'description', 'tags')),
|
FieldSet(
|
||||||
(_('Site/VLAN Assignment'), ('site', 'vlan')),
|
'prefix', 'status', 'vrf', 'role', 'is_pool', 'mark_utilized', 'description', 'tags', name=_('Prefix')
|
||||||
(_('Tenancy'), ('tenant_group', 'tenant')),
|
),
|
||||||
|
FieldSet('site', 'vlan', name=_('Site/VLAN Assignment')),
|
||||||
|
FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -253,8 +252,11 @@ class IPRangeForm(TenancyForm, NetBoxModelForm):
|
|||||||
comments = CommentField()
|
comments = CommentField()
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('IP Range'), ('vrf', 'start_address', 'end_address', 'role', 'status', 'mark_utilized', 'description', 'tags')),
|
FieldSet(
|
||||||
(_('Tenancy'), ('tenant_group', 'tenant')),
|
'vrf', 'start_address', 'end_address', 'role', 'status', 'mark_utilized', 'description', 'tags',
|
||||||
|
name=_('IP Range')
|
||||||
|
),
|
||||||
|
FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -307,6 +309,20 @@ class IPAddressForm(TenancyForm, NetBoxModelForm):
|
|||||||
)
|
)
|
||||||
comments = CommentField()
|
comments = CommentField()
|
||||||
|
|
||||||
|
fieldsets = (
|
||||||
|
FieldSet('address', 'status', 'role', 'vrf', 'dns_name', 'description', 'tags', name=_('IP Address')),
|
||||||
|
FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
|
||||||
|
FieldSet(
|
||||||
|
TabbedGroups(
|
||||||
|
FieldSet('interface', name=_('Device')),
|
||||||
|
FieldSet('vminterface', name=_('Virtual Machine')),
|
||||||
|
FieldSet('fhrpgroup', name=_('FHRP Group')),
|
||||||
|
),
|
||||||
|
'primary_for_parent', name=_('Assignment')
|
||||||
|
),
|
||||||
|
FieldSet('nat_inside', name=_('NAT IP (Inside)')),
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = IPAddress
|
model = IPAddress
|
||||||
fields = [
|
fields = [
|
||||||
@ -373,20 +389,6 @@ class IPAddressForm(TenancyForm, NetBoxModelForm):
|
|||||||
'primary_for_parent', _("Only IP addresses assigned to an interface can be designated as primary IPs.")
|
'primary_for_parent', _("Only IP addresses assigned to an interface can be designated as primary IPs.")
|
||||||
)
|
)
|
||||||
|
|
||||||
# Do not allow assigning a network ID or broadcast address to an interface.
|
|
||||||
if interface and (address := self.cleaned_data.get('address')):
|
|
||||||
if address.ip == address.network:
|
|
||||||
msg = _("{ip} is a network ID, which may not be assigned to an interface.").format(ip=address.ip)
|
|
||||||
if address.version == 4 and address.prefixlen not in (31, 32):
|
|
||||||
raise ValidationError(msg)
|
|
||||||
if address.version == 6 and address.prefixlen not in (127, 128):
|
|
||||||
raise ValidationError(msg)
|
|
||||||
if address.version == 4 and address.ip == address.broadcast and address.prefixlen not in (31, 32):
|
|
||||||
msg = _("{ip} is a broadcast address, which may not be assigned to an interface.").format(
|
|
||||||
ip=address.ip
|
|
||||||
)
|
|
||||||
raise ValidationError(msg)
|
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
ipaddress = super().save(*args, **kwargs)
|
ipaddress = super().save(*args, **kwargs)
|
||||||
|
|
||||||
@ -457,9 +459,9 @@ class FHRPGroupForm(NetBoxModelForm):
|
|||||||
comments = CommentField()
|
comments = CommentField()
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('FHRP Group'), ('protocol', 'group_id', 'name', 'description', 'tags')),
|
FieldSet('protocol', 'group_id', 'name', 'description', 'tags', name=_('FHRP Group')),
|
||||||
(_('Authentication'), ('auth_type', 'auth_key')),
|
FieldSet('auth_type', 'auth_key', name=_('Authentication')),
|
||||||
(_('Virtual IP Address'), ('ip_vrf', 'ip_address', 'ip_status'))
|
FieldSet('ip_vrf', 'ip_address', 'ip_status', name=_('Virtual IP Address'))
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -516,6 +518,10 @@ class FHRPGroupAssignmentForm(forms.ModelForm):
|
|||||||
queryset=FHRPGroup.objects.all()
|
queryset=FHRPGroup.objects.all()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fieldsets = (
|
||||||
|
FieldSet(ObjectAttribute('interface'), 'group', 'priority'),
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = FHRPGroupAssignment
|
model = FHRPGroupAssignment
|
||||||
fields = ('group', 'priority')
|
fields = ('group', 'priority')
|
||||||
@ -601,9 +607,12 @@ class VLANGroupForm(NetBoxModelForm):
|
|||||||
slug = SlugField()
|
slug = SlugField()
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('VLAN Group'), ('name', 'slug', 'description', 'tags')),
|
FieldSet('name', 'slug', 'description', 'tags', name=_('VLAN Group')),
|
||||||
(_('Child VLANs'), ('min_vid', 'max_vid')),
|
FieldSet('min_vid', 'max_vid', name=_('Child VLANs')),
|
||||||
(_('Scope'), ('scope_type', 'region', 'sitegroup', 'site', 'location', 'rack', 'clustergroup', 'cluster')),
|
FieldSet(
|
||||||
|
'scope_type', 'region', 'sitegroup', 'site', 'location', 'rack', 'clustergroup', 'cluster',
|
||||||
|
name=_('Scope')
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -676,9 +685,7 @@ class ServiceTemplateForm(NetBoxModelForm):
|
|||||||
comments = CommentField()
|
comments = CommentField()
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(_('Service Template'), (
|
FieldSet('name', 'protocol', 'ports', 'description', 'tags', name=_('Service Template')),
|
||||||
'name', 'protocol', 'ports', 'description', 'tags',
|
|
||||||
)),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -718,6 +725,18 @@ class ServiceForm(NetBoxModelForm):
|
|||||||
)
|
)
|
||||||
comments = CommentField()
|
comments = CommentField()
|
||||||
|
|
||||||
|
fieldsets = (
|
||||||
|
FieldSet(
|
||||||
|
TabbedGroups(
|
||||||
|
FieldSet('device', name=_('Device')),
|
||||||
|
FieldSet('virtual_machine', name=_('Virtual Machine')),
|
||||||
|
),
|
||||||
|
'name',
|
||||||
|
InlineFields('protocol', 'ports', label=_('Port(s)')),
|
||||||
|
'ipaddresses', 'description', 'tags', name=_('Service')
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Service
|
model = Service
|
||||||
fields = [
|
fields = [
|
||||||
@ -732,6 +751,20 @@ class ServiceCreateForm(ServiceForm):
|
|||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fieldsets = (
|
||||||
|
FieldSet(
|
||||||
|
TabbedGroups(
|
||||||
|
FieldSet('device', name=_('Device')),
|
||||||
|
FieldSet('virtual_machine', name=_('Virtual Machine')),
|
||||||
|
),
|
||||||
|
TabbedGroups(
|
||||||
|
FieldSet('service_template', name=_('From Template')),
|
||||||
|
FieldSet('name', 'protocol', 'ports', name=_('Custom')),
|
||||||
|
),
|
||||||
|
'ipaddresses', 'description', 'tags', name=_('Service')
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
class Meta(ServiceForm.Meta):
|
class Meta(ServiceForm.Meta):
|
||||||
fields = [
|
fields = [
|
||||||
'device', 'virtual_machine', 'service_template', 'name', 'protocol', 'ports', 'ipaddresses', 'description',
|
'device', 'virtual_machine', 'service_template', 'name', 'protocol', 'ports', 'ipaddresses', 'description',
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import graphene
|
import graphene
|
||||||
|
|
||||||
from ipam import filtersets, models
|
from ipam import filtersets, models
|
||||||
|
from .mixins import IPAddressesMixin
|
||||||
from netbox.graphql.scalars import BigInt
|
from netbox.graphql.scalars import BigInt
|
||||||
from netbox.graphql.types import BaseObjectType, OrganizationalObjectType, NetBoxObjectType
|
from netbox.graphql.types import BaseObjectType, OrganizationalObjectType, NetBoxObjectType
|
||||||
|
|
||||||
@ -71,7 +72,7 @@ class AggregateType(NetBoxObjectType, BaseIPAddressFamilyType):
|
|||||||
filterset_class = filtersets.AggregateFilterSet
|
filterset_class = filtersets.AggregateFilterSet
|
||||||
|
|
||||||
|
|
||||||
class FHRPGroupType(NetBoxObjectType):
|
class FHRPGroupType(NetBoxObjectType, IPAddressesMixin):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.FHRPGroup
|
model = models.FHRPGroup
|
||||||
|
@ -844,6 +844,25 @@ class IPAddress(PrimaryModel):
|
|||||||
'address': _("Cannot create IP address with /0 mask.")
|
'address': _("Cannot create IP address with /0 mask.")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
# Do not allow assigning a network ID or broadcast address to an interface.
|
||||||
|
if self.assigned_object:
|
||||||
|
if self.address.ip == self.address.network:
|
||||||
|
msg = _("{ip} is a network ID, which may not be assigned to an interface.").format(
|
||||||
|
ip=self.address.ip
|
||||||
|
)
|
||||||
|
if self.address.version == 4 and self.address.prefixlen not in (31, 32):
|
||||||
|
raise ValidationError(msg)
|
||||||
|
if self.address.version == 6 and self.address.prefixlen not in (127, 128):
|
||||||
|
raise ValidationError(msg)
|
||||||
|
if (
|
||||||
|
self.address.version == 4 and self.address.ip == self.address.broadcast and
|
||||||
|
self.address.prefixlen not in (31, 32)
|
||||||
|
):
|
||||||
|
msg = _("{ip} is a broadcast address, which may not be assigned to an interface.").format(
|
||||||
|
ip=self.address.ip
|
||||||
|
)
|
||||||
|
raise ValidationError(msg)
|
||||||
|
|
||||||
# Enforce unique IP space (if applicable)
|
# Enforce unique IP space (if applicable)
|
||||||
if (self.vrf is None and get_config().ENFORCE_GLOBAL_UNIQUE) or (self.vrf and self.vrf.enforce_unique):
|
if (self.vrf is None and get_config().ENFORCE_GLOBAL_UNIQUE) or (self.vrf and self.vrf.enforce_unique):
|
||||||
duplicate_ips = self.get_duplicates()
|
duplicate_ips = self.get_duplicates()
|
||||||
|
@ -2,6 +2,7 @@ from django.contrib.contenttypes.models import ContentType
|
|||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from netaddr import IPNetwork
|
from netaddr import IPNetwork
|
||||||
|
|
||||||
|
from circuits.models import Provider
|
||||||
from dcim.choices import InterfaceTypeChoices
|
from dcim.choices import InterfaceTypeChoices
|
||||||
from dcim.models import Device, DeviceRole, DeviceType, Interface, Location, Manufacturer, Rack, Region, Site, SiteGroup
|
from dcim.models import Device, DeviceRole, DeviceType, Interface, Location, Manufacturer, Rack, Region, Site, SiteGroup
|
||||||
from ipam.choices import *
|
from ipam.choices import *
|
||||||
@ -10,6 +11,8 @@ from ipam.models import *
|
|||||||
from tenancy.models import Tenant, TenantGroup
|
from tenancy.models import Tenant, TenantGroup
|
||||||
from utilities.testing import ChangeLoggedFilterSetTests, create_test_device, create_test_virtualmachine
|
from utilities.testing import ChangeLoggedFilterSetTests, create_test_device, create_test_virtualmachine
|
||||||
from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
|
from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
|
||||||
|
from vpn.choices import L2VPNTypeChoices
|
||||||
|
from vpn.models import L2VPN
|
||||||
|
|
||||||
|
|
||||||
class ASNRangeTestCase(TestCase, ChangeLoggedFilterSetTests):
|
class ASNRangeTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
@ -110,13 +113,6 @@ class ASNTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
]
|
]
|
||||||
RIR.objects.bulk_create(rirs)
|
RIR.objects.bulk_create(rirs)
|
||||||
|
|
||||||
sites = [
|
|
||||||
Site(name='Site 1', slug='site-1'),
|
|
||||||
Site(name='Site 2', slug='site-2'),
|
|
||||||
Site(name='Site 3', slug='site-3')
|
|
||||||
]
|
|
||||||
Site.objects.bulk_create(sites)
|
|
||||||
|
|
||||||
tenants = [
|
tenants = [
|
||||||
Tenant(name='Tenant 1', slug='tenant-1'),
|
Tenant(name='Tenant 1', slug='tenant-1'),
|
||||||
Tenant(name='Tenant 2', slug='tenant-2'),
|
Tenant(name='Tenant 2', slug='tenant-2'),
|
||||||
@ -136,6 +132,12 @@ class ASNTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
)
|
)
|
||||||
ASN.objects.bulk_create(asns)
|
ASN.objects.bulk_create(asns)
|
||||||
|
|
||||||
|
sites = [
|
||||||
|
Site(name='Site 1', slug='site-1'),
|
||||||
|
Site(name='Site 2', slug='site-2'),
|
||||||
|
Site(name='Site 3', slug='site-3')
|
||||||
|
]
|
||||||
|
Site.objects.bulk_create(sites)
|
||||||
asns[0].sites.set([sites[0]])
|
asns[0].sites.set([sites[0]])
|
||||||
asns[1].sites.set([sites[1]])
|
asns[1].sites.set([sites[1]])
|
||||||
asns[2].sites.set([sites[2]])
|
asns[2].sites.set([sites[2]])
|
||||||
@ -143,6 +145,16 @@ class ASNTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
asns[4].sites.set([sites[1]])
|
asns[4].sites.set([sites[1]])
|
||||||
asns[5].sites.set([sites[2]])
|
asns[5].sites.set([sites[2]])
|
||||||
|
|
||||||
|
providers = (
|
||||||
|
Provider(name='Provider 1', slug='provider-1'),
|
||||||
|
Provider(name='Provider 2', slug='provider-2'),
|
||||||
|
Provider(name='Provider 3', slug='provider-3'),
|
||||||
|
)
|
||||||
|
Provider.objects.bulk_create(providers)
|
||||||
|
providers[0].asns.add(asns[0])
|
||||||
|
providers[1].asns.add(asns[1])
|
||||||
|
providers[2].asns.add(asns[2])
|
||||||
|
|
||||||
def test_q(self):
|
def test_q(self):
|
||||||
params = {'q': 'foobar1'}
|
params = {'q': 'foobar1'}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
@ -176,11 +188,24 @@ class ASNTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
params = {'description': ['foobar1', 'foobar2']}
|
params = {'description': ['foobar1', 'foobar2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
def test_provider(self):
|
||||||
|
providers = Provider.objects.all()[:2]
|
||||||
|
params = {'provider_id': [providers[0].pk, providers[1].pk]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
|
||||||
class VRFTestCase(TestCase, ChangeLoggedFilterSetTests):
|
class VRFTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = VRF.objects.all()
|
queryset = VRF.objects.all()
|
||||||
filterset = VRFFilterSet
|
filterset = VRFFilterSet
|
||||||
|
|
||||||
|
def get_m2m_filter_name(self, field):
|
||||||
|
# Override filter names for import & export RouteTargets
|
||||||
|
if field.name == 'import_targets':
|
||||||
|
return 'import_target'
|
||||||
|
if field.name == 'export_targets':
|
||||||
|
return 'export_target'
|
||||||
|
return ChangeLoggedFilterSetTests.get_m2m_filter_name(field)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
|
|
||||||
@ -277,6 +302,18 @@ class RouteTargetTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
queryset = RouteTarget.objects.all()
|
queryset = RouteTarget.objects.all()
|
||||||
filterset = RouteTargetFilterSet
|
filterset = RouteTargetFilterSet
|
||||||
|
|
||||||
|
def get_m2m_filter_name(self, field):
|
||||||
|
# Override filter names for import & export VRFs and L2VPNs
|
||||||
|
if field.name == 'importing_vrfs':
|
||||||
|
return 'importing_vrf'
|
||||||
|
if field.name == 'exporting_vrfs':
|
||||||
|
return 'exporting_vrf'
|
||||||
|
if field.name == 'importing_l2vpns':
|
||||||
|
return 'importing_l2vpn'
|
||||||
|
if field.name == 'exporting_l2vpns':
|
||||||
|
return 'exporting_l2vpn'
|
||||||
|
return ChangeLoggedFilterSetTests.get_m2m_filter_name(field)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
|
|
||||||
@ -322,6 +359,17 @@ class RouteTargetTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
vrfs[1].import_targets.add(route_targets[4], route_targets[5])
|
vrfs[1].import_targets.add(route_targets[4], route_targets[5])
|
||||||
vrfs[1].export_targets.add(route_targets[6], route_targets[7])
|
vrfs[1].export_targets.add(route_targets[6], route_targets[7])
|
||||||
|
|
||||||
|
l2vpns = (
|
||||||
|
L2VPN(name='L2VPN 1', slug='l2vpn-1', type=L2VPNTypeChoices.TYPE_VXLAN, identifier=100),
|
||||||
|
L2VPN(name='L2VPN 2', slug='l2vpn-2', type=L2VPNTypeChoices.TYPE_VXLAN, identifier=200),
|
||||||
|
L2VPN(name='L2VPN 3', slug='l2vpn-3', type=L2VPNTypeChoices.TYPE_VXLAN, identifier=300),
|
||||||
|
)
|
||||||
|
L2VPN.objects.bulk_create(l2vpns)
|
||||||
|
l2vpns[0].import_targets.add(route_targets[0], route_targets[1])
|
||||||
|
l2vpns[0].export_targets.add(route_targets[2], route_targets[3])
|
||||||
|
l2vpns[1].import_targets.add(route_targets[4], route_targets[5])
|
||||||
|
l2vpns[1].export_targets.add(route_targets[6], route_targets[7])
|
||||||
|
|
||||||
def test_q(self):
|
def test_q(self):
|
||||||
params = {'q': 'foobar1'}
|
params = {'q': 'foobar1'}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
@ -344,6 +392,20 @@ class RouteTargetTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
params = {'exporting_vrf': [vrfs[0].rd, vrfs[1].rd]}
|
params = {'exporting_vrf': [vrfs[0].rd, vrfs[1].rd]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
||||||
|
|
||||||
|
def test_importing_l2vpn(self):
|
||||||
|
l2vpns = L2VPN.objects.all()[:2]
|
||||||
|
params = {'importing_l2vpn_id': [l2vpns[0].pk, l2vpns[1].pk]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
||||||
|
params = {'importing_l2vpn': [l2vpns[0].identifier, l2vpns[1].identifier]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
||||||
|
|
||||||
|
def test_exporting_l2vpn(self):
|
||||||
|
l2vpns = L2VPN.objects.all()[:2]
|
||||||
|
params = {'exporting_l2vpn_id': [l2vpns[0].pk, l2vpns[1].pk]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
||||||
|
params = {'exporting_l2vpn': [l2vpns[0].identifier, l2vpns[1].identifier]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
||||||
|
|
||||||
def test_tenant(self):
|
def test_tenant(self):
|
||||||
tenants = Tenant.objects.all()[:2]
|
tenants = Tenant.objects.all()[:2]
|
||||||
params = {'tenant_id': [tenants[0].pk, tenants[1].pk]}
|
params = {'tenant_id': [tenants[0].pk, tenants[1].pk]}
|
||||||
@ -922,6 +984,7 @@ class IPRangeTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
class IPAddressTestCase(TestCase, ChangeLoggedFilterSetTests):
|
class IPAddressTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = IPAddress.objects.all()
|
queryset = IPAddress.objects.all()
|
||||||
filterset = IPAddressFilterSet
|
filterset = IPAddressFilterSet
|
||||||
|
ignore_fields = ('fhrpgroup',)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -1092,6 +1155,16 @@ class IPAddressTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
)
|
)
|
||||||
IPAddress.objects.bulk_create(ipaddresses)
|
IPAddress.objects.bulk_create(ipaddresses)
|
||||||
|
|
||||||
|
services = (
|
||||||
|
Service(name='Service 1', protocol=ServiceProtocolChoices.PROTOCOL_TCP, ports=[1]),
|
||||||
|
Service(name='Service 2', protocol=ServiceProtocolChoices.PROTOCOL_TCP, ports=[1]),
|
||||||
|
Service(name='Service 3', protocol=ServiceProtocolChoices.PROTOCOL_TCP, ports=[1]),
|
||||||
|
)
|
||||||
|
Service.objects.bulk_create(services)
|
||||||
|
services[0].ipaddresses.add(ipaddresses[0])
|
||||||
|
services[1].ipaddresses.add(ipaddresses[1])
|
||||||
|
services[2].ipaddresses.add(ipaddresses[2])
|
||||||
|
|
||||||
def test_q(self):
|
def test_q(self):
|
||||||
params = {'q': 'foobar1'}
|
params = {'q': 'foobar1'}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
@ -1231,6 +1304,11 @@ class IPAddressTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
params = {'tenant_group': [tenant_groups[0].slug, tenant_groups[1].slug]}
|
params = {'tenant_group': [tenant_groups[0].slug, tenant_groups[1].slug]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
||||||
|
|
||||||
|
def test_service(self):
|
||||||
|
services = Service.objects.all()[:2]
|
||||||
|
params = {'service_id': [services[0].pk, services[1].pk]}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
|
||||||
class FHRPGroupTestCase(TestCase, ChangeLoggedFilterSetTests):
|
class FHRPGroupTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = FHRPGroup.objects.all()
|
queryset = FHRPGroup.objects.all()
|
||||||
@ -1475,6 +1553,7 @@ class VLANGroupTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
class VLANTestCase(TestCase, ChangeLoggedFilterSetTests):
|
class VLANTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = VLAN.objects.all()
|
queryset = VLAN.objects.all()
|
||||||
filterset = VLANFilterSet
|
filterset = VLANFilterSet
|
||||||
|
ignore_fields = ('interfaces_as_tagged', 'vminterfaces_as_tagged')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -1733,6 +1812,7 @@ class VLANTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
class ServiceTemplateTestCase(TestCase, ChangeLoggedFilterSetTests):
|
class ServiceTemplateTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = ServiceTemplate.objects.all()
|
queryset = ServiceTemplate.objects.all()
|
||||||
filterset = ServiceTemplateFilterSet
|
filterset = ServiceTemplateFilterSet
|
||||||
|
ignore_fields = ('ports',)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -1797,6 +1877,7 @@ class ServiceTemplateTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
class ServiceTestCase(TestCase, ChangeLoggedFilterSetTests):
|
class ServiceTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = Service.objects.all()
|
queryset = Service.objects.all()
|
||||||
filterset = ServiceFilterSet
|
filterset = ServiceFilterSet
|
||||||
|
ignore_fields = ('ports',)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -1883,9 +1964,9 @@ class ServiceTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
params = {'virtual_machine': [vms[0].name, vms[1].name]}
|
params = {'virtual_machine': [vms[0].name, vms[1].name]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
def test_ipaddress(self):
|
def test_ip_address(self):
|
||||||
ips = IPAddress.objects.all()[:2]
|
ips = IPAddress.objects.all()[:2]
|
||||||
params = {'ipaddress_id': [ips[0].pk, ips[1].pk]}
|
params = {'ip_address_id': [ips[0].pk, ips[1].pk]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
params = {'ipaddress': [str(ips[0].address), str(ips[1].address)]}
|
params = {'ip_address': [str(ips[0].address), str(ips[1].address)]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
@ -781,7 +781,6 @@ class IPAddressView(generic.ObjectView):
|
|||||||
class IPAddressEditView(generic.ObjectEditView):
|
class IPAddressEditView(generic.ObjectEditView):
|
||||||
queryset = IPAddress.objects.all()
|
queryset = IPAddress.objects.all()
|
||||||
form = forms.IPAddressForm
|
form = forms.IPAddressForm
|
||||||
template_name = 'ipam/ipaddress_edit.html'
|
|
||||||
|
|
||||||
def alter_object(self, obj, request, url_args, url_kwargs):
|
def alter_object(self, obj, request, url_args, url_kwargs):
|
||||||
|
|
||||||
@ -1059,7 +1058,6 @@ class FHRPGroupBulkDeleteView(generic.BulkDeleteView):
|
|||||||
class FHRPGroupAssignmentEditView(generic.ObjectEditView):
|
class FHRPGroupAssignmentEditView(generic.ObjectEditView):
|
||||||
queryset = FHRPGroupAssignment.objects.all()
|
queryset = FHRPGroupAssignment.objects.all()
|
||||||
form = forms.FHRPGroupAssignmentForm
|
form = forms.FHRPGroupAssignmentForm
|
||||||
template_name = 'ipam/fhrpgroupassignment_edit.html'
|
|
||||||
|
|
||||||
def alter_object(self, instance, request, args, kwargs):
|
def alter_object(self, instance, request, args, kwargs):
|
||||||
if not instance.pk:
|
if not instance.pk:
|
||||||
@ -1236,14 +1234,12 @@ class ServiceView(generic.ObjectView):
|
|||||||
class ServiceCreateView(generic.ObjectEditView):
|
class ServiceCreateView(generic.ObjectEditView):
|
||||||
queryset = Service.objects.all()
|
queryset = Service.objects.all()
|
||||||
form = forms.ServiceCreateForm
|
form = forms.ServiceCreateForm
|
||||||
template_name = 'ipam/service_create.html'
|
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(Service, 'edit')
|
@register_model_view(Service, 'edit')
|
||||||
class ServiceEditView(generic.ObjectEditView):
|
class ServiceEditView(generic.ObjectEditView):
|
||||||
queryset = Service.objects.all()
|
queryset = Service.objects.all()
|
||||||
form = forms.ServiceForm
|
form = forms.ServiceForm
|
||||||
template_name = 'ipam/service_edit.html'
|
|
||||||
|
|
||||||
|
|
||||||
@register_model_view(Service, 'delete')
|
@register_model_view(Service, 'delete')
|
||||||
|
@ -24,7 +24,7 @@ class NetBoxModelForm(CheckLastUpdatedMixin, CustomFieldsMixin, TagsMixin, forms
|
|||||||
Base form for creating & editing NetBox models. Extends Django's ModelForm to add support for custom fields.
|
Base form for creating & editing NetBox models. Extends Django's ModelForm to add support for custom fields.
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
fieldsets: An iterable of two-tuples which define a heading and field set to display per section of
|
fieldsets: An iterable of FieldSets which define a name and set of fields to display per section of
|
||||||
the rendered form (optional). If not defined, the all fields will be rendered as a single section.
|
the rendered form (optional). If not defined, the all fields will be rendered as a single section.
|
||||||
"""
|
"""
|
||||||
fieldsets = ()
|
fieldsets = ()
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
from decimal import Decimal
|
||||||
|
|
||||||
|
from django.core.exceptions import FieldDoesNotExist
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from netaddr import IPAddress, IPNetwork
|
||||||
|
|
||||||
from ipam.fields import IPAddressField, IPNetworkField
|
from ipam.fields import IPAddressField, IPNetworkField
|
||||||
from netbox.registry import registry
|
from netbox.registry import registry
|
||||||
@ -56,6 +59,24 @@ class SearchIndex:
|
|||||||
return FieldTypes.INTEGER
|
return FieldTypes.INTEGER
|
||||||
return FieldTypes.STRING
|
return FieldTypes.STRING
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_attr_type(instance, field_name):
|
||||||
|
"""
|
||||||
|
Return the data type of the specified object attribute.
|
||||||
|
"""
|
||||||
|
value = getattr(instance, field_name)
|
||||||
|
if type(value) is str:
|
||||||
|
return FieldTypes.STRING
|
||||||
|
if type(value) is int:
|
||||||
|
return FieldTypes.INTEGER
|
||||||
|
if type(value) in (float, Decimal):
|
||||||
|
return FieldTypes.FLOAT
|
||||||
|
if type(value) is IPNetwork:
|
||||||
|
return FieldTypes.CIDR
|
||||||
|
if type(value) is IPAddress:
|
||||||
|
return FieldTypes.INET
|
||||||
|
return FieldTypes.STRING
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_field_value(instance, field_name):
|
def get_field_value(instance, field_name):
|
||||||
"""
|
"""
|
||||||
@ -82,7 +103,11 @@ class SearchIndex:
|
|||||||
|
|
||||||
# Capture built-in fields
|
# Capture built-in fields
|
||||||
for name, weight in cls.fields:
|
for name, weight in cls.fields:
|
||||||
type_ = cls.get_field_type(instance, name)
|
try:
|
||||||
|
type_ = cls.get_field_type(instance, name)
|
||||||
|
except FieldDoesNotExist:
|
||||||
|
# Not a concrete field; handle as an object attribute
|
||||||
|
type_ = cls.get_attr_type(instance, name)
|
||||||
value = cls.get_field_value(instance, name)
|
value = cls.get_field_value(instance, name)
|
||||||
if type_ and value:
|
if type_ and value:
|
||||||
values.append(
|
values.append(
|
||||||
|
@ -264,9 +264,11 @@ class SearchTable(tables.Table):
|
|||||||
super().__init__(data, **kwargs)
|
super().__init__(data, **kwargs)
|
||||||
|
|
||||||
def render_field(self, value, record):
|
def render_field(self, value, record):
|
||||||
if hasattr(record.object, value):
|
try:
|
||||||
return title(record.object._meta.get_field(value).verbose_name)
|
model_field = record.object._meta.get_field(value)
|
||||||
return value
|
return title(model_field.verbose_name)
|
||||||
|
except FieldDoesNotExist:
|
||||||
|
return value
|
||||||
|
|
||||||
def render_value(self, value):
|
def render_value(self, value):
|
||||||
if not self.highlight:
|
if not self.highlight:
|
||||||
|
@ -5,10 +5,11 @@
|
|||||||
* @param inferred {boolean} Value is inferred from browser/system preference.
|
* @param inferred {boolean} Value is inferred from browser/system preference.
|
||||||
*/
|
*/
|
||||||
function setMode(mode, inferred) {
|
function setMode(mode, inferred) {
|
||||||
document.documentElement.setAttribute("data-netbox-color-mode", mode);
|
document.documentElement.setAttribute("data-bs-theme", mode);
|
||||||
localStorage.setItem("netbox-color-mode", mode);
|
localStorage.setItem("netbox-color-mode", mode);
|
||||||
localStorage.setItem("netbox-color-mode-inferred", inferred);
|
localStorage.setItem("netbox-color-mode-inferred", inferred);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine the best initial color mode to use prior to rendering.
|
* Determine the best initial color mode to use prior to rendering.
|
||||||
*/
|
*/
|
||||||
@ -69,4 +70,4 @@ function initMode() {
|
|||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
return setMode("light", true);
|
return setMode("light", true);
|
||||||
};
|
}
|
||||||
|
@ -10,15 +10,8 @@
|
|||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
||||||
{# Built-in preferences #}
|
{# Built-in preferences #}
|
||||||
{% for group, fields in form.fieldsets %}
|
{% for fieldset in form.fieldsets %}
|
||||||
<div class="field-group my-5">
|
{% render_fieldset form fieldset %}
|
||||||
<div class="row">
|
|
||||||
<h5 class="col-9 offset-3">{{ group }}</h5>
|
|
||||||
</div>
|
|
||||||
{% for name in fields %}
|
|
||||||
{% render_field form|getfield:name %}
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
{# Plugin preferences #}
|
{# Plugin preferences #}
|
||||||
|
@ -9,13 +9,7 @@
|
|||||||
data-netbox-url-name="{{ request.resolver_match.url_name }}"
|
data-netbox-url-name="{{ request.resolver_match.url_name }}"
|
||||||
data-netbox-base-path="{{ settings.BASE_PATH }}"
|
data-netbox-base-path="{{ settings.BASE_PATH }}"
|
||||||
{% with preferences|get_key:'ui.colormode' as color_mode %}
|
{% with preferences|get_key:'ui.colormode' as color_mode %}
|
||||||
{% if color_mode == 'dark'%}
|
data-netbox-color-mode="{{ color_mode|default:"unset" }}"
|
||||||
data-netbox-color-mode="dark"
|
|
||||||
{% elif color_mode == 'light' %}
|
|
||||||
data-netbox-color-mode="light"
|
|
||||||
{% else %}
|
|
||||||
data-netbox-color-mode="unset"
|
|
||||||
{% endif %}
|
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
>
|
>
|
||||||
<head>
|
<head>
|
||||||
@ -25,7 +19,16 @@
|
|||||||
{# Page title #}
|
{# Page title #}
|
||||||
<title>{% block title %}{% trans "Home" %}{% endblock %} | NetBox</title>
|
<title>{% block title %}{% trans "Home" %}{% endblock %} | NetBox</title>
|
||||||
|
|
||||||
|
{# Initialize color mode #}
|
||||||
|
<script
|
||||||
|
type="text/javascript"
|
||||||
|
src="{% static 'setmode.js' %}"
|
||||||
|
onerror="window.location='{% url 'media_failure' %}?filename=setmode.js'">
|
||||||
|
</script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
(function () {
|
||||||
|
initMode()
|
||||||
|
})();
|
||||||
window.CSRF_TOKEN = "{{ csrf_token }}";
|
window.CSRF_TOKEN = "{{ csrf_token }}";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -53,13 +56,9 @@
|
|||||||
|
|
||||||
{# Additional <head> content #}
|
{# Additional <head> content #}
|
||||||
{% block head %}{% endblock %}
|
{% block head %}{% endblock %}
|
||||||
</head>
|
|
||||||
|
|
||||||
<body
|
</head>
|
||||||
{% if preferences|get_key:'ui.colormode' == 'dark' %}
|
<body>
|
||||||
data-bs-theme="dark"
|
|
||||||
{% endif %}
|
|
||||||
>
|
|
||||||
|
|
||||||
{# Page layout #}
|
{# Page layout #}
|
||||||
{% block layout %}{% endblock %}
|
{% block layout %}{% endblock %}
|
||||||
|
@ -41,7 +41,7 @@ Blocks:
|
|||||||
|
|
||||||
{# Top menu #}
|
{# Top menu #}
|
||||||
<header class="navbar navbar-expand-md d-none d-lg-flex d-print-none">
|
<header class="navbar navbar-expand-md d-none d-lg-flex d-print-none">
|
||||||
<div class="container-xl">
|
<div class="container-fluid">
|
||||||
|
|
||||||
{# Nav menu toggle #}
|
{# Nav menu toggle #}
|
||||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbar-menu" aria-controls="navbar-menu" aria-expanded="false" aria-label="Toggle navigation">
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbar-menu" aria-controls="navbar-menu" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
@ -105,7 +105,7 @@ Blocks:
|
|||||||
{# Page body #}
|
{# Page body #}
|
||||||
{% block page %}
|
{% block page %}
|
||||||
<div class="page-body my-1">
|
<div class="page-body my-1">
|
||||||
<div class="container-xl tab-content py-3">
|
<div class="container-fluid tab-content py-3">
|
||||||
|
|
||||||
{# Page content #}
|
{# Page content #}
|
||||||
{% block content %}{% endblock %}
|
{% block content %}{% endblock %}
|
||||||
@ -124,7 +124,7 @@ Blocks:
|
|||||||
|
|
||||||
{# Page footer #}
|
{# Page footer #}
|
||||||
<footer class="footer footer-transparent d-print-none py-2">
|
<footer class="footer footer-transparent d-print-none py-2">
|
||||||
<div class="container-xl d-flex justify-content-between align-items-center">
|
<div class="container-fluid d-flex justify-content-between align-items-center">
|
||||||
{% block footer %}
|
{% block footer %}
|
||||||
|
|
||||||
{# Footer links #}
|
{# Footer links #}
|
||||||
|
@ -1,58 +0,0 @@
|
|||||||
{% extends 'generic/object_edit.html' %}
|
|
||||||
{% load static %}
|
|
||||||
{% load form_helpers %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block form %}
|
|
||||||
<div class="field-group my-5">
|
|
||||||
<div class="row">
|
|
||||||
<h5 class="col-9 offset-3">{% trans "Circuit Termination" %}</h5>
|
|
||||||
</div>
|
|
||||||
{% render_field form.circuit %}
|
|
||||||
{% render_field form.term_side %}
|
|
||||||
{% render_field form.tags %}
|
|
||||||
{% render_field form.mark_connected %}
|
|
||||||
{% with providernetwork_tab_active=form.initial.provider_network %}
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-9 offset-3">
|
|
||||||
<ul class="nav nav-pills mb-1" role="tablist">
|
|
||||||
<li class="nav-item" role="presentation">
|
|
||||||
<button class="nav-link{% if not providernetwork_tab_active %} active{% endif %}" role="tab" type="button" data-bs-target="#site" data-bs-toggle="tab">{% trans "Site" %}</button>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item" role="presentation">
|
|
||||||
<button class="nav-link{% if providernetwork_tab_active %} active{% endif %}" role="tab" type="button" data-bs-toggle="tab" data-bs-target="#providernetwork">{% trans "Provider Network" %}</button>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="tab-content p-0 border-0">
|
|
||||||
<div class="tab-pane{% if not providernetwork_tab_active %} active{% endif %}" id="site">
|
|
||||||
{% render_field form.site %}
|
|
||||||
</div>
|
|
||||||
<div class="tab-pane{% if providernetwork_tab_active %} active{% endif %}" id="providernetwork">
|
|
||||||
{% render_field form.provider_network %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endwith %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field-group my-5">
|
|
||||||
<div class="row">
|
|
||||||
<h5 class="col-9 offset-3">{% trans "Termination Details" %}</h5>
|
|
||||||
</div>
|
|
||||||
{% render_field form.port_speed %}
|
|
||||||
{% render_field form.upstream_speed %}
|
|
||||||
{% render_field form.xconnect_id %}
|
|
||||||
{% render_field form.pp_info %}
|
|
||||||
{% render_field form.description %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if form.custom_fields %}
|
|
||||||
<div class="field-group mb-5">
|
|
||||||
<div class="row">
|
|
||||||
<h5 class="col-9 offset-3">{% trans "Custom Fields" %}</h5>
|
|
||||||
</div>
|
|
||||||
{% render_custom_fields form %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
@ -56,7 +56,7 @@
|
|||||||
<td>
|
<td>
|
||||||
{{ object.scheduled|annotated_date|placeholder }}
|
{{ object.scheduled|annotated_date|placeholder }}
|
||||||
{% if object.interval %}
|
{% if object.interval %}
|
||||||
({% blocktrans with interval=object.interval %}every {{ interval }} seconds{% endblocktrans %})
|
({% blocktrans with interval=object.interval %}every {{ interval }} minutes{% endblocktrans %})
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
{% load render_table from django_tables2 %}
|
{% load render_table from django_tables2 %}
|
||||||
|
|
||||||
{% block page-header %}
|
{% block page-header %}
|
||||||
<div class="container-xl">
|
<div class="container-fluid">
|
||||||
<div class="d-flex justify-content-between align-items-center mt-2">
|
<div class="d-flex justify-content-between align-items-center mt-2">
|
||||||
{# Breadcrumbs #}
|
{# Breadcrumbs #}
|
||||||
<nav class="breadcrumb-container" aria-label="breadcrumb">
|
<nav class="breadcrumb-container" aria-label="breadcrumb">
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
{% load render_table from django_tables2 %}
|
{% load render_table from django_tables2 %}
|
||||||
|
|
||||||
{% block page-header %}
|
{% block page-header %}
|
||||||
<div class="container-xl">
|
<div class="container-fluid">
|
||||||
<div class="d-flex justify-content-between align-items-center mt-2">
|
<div class="d-flex justify-content-between align-items-center mt-2">
|
||||||
{# Breadcrumbs #}
|
{# Breadcrumbs #}
|
||||||
<nav class="breadcrumb-container" aria-label="breadcrumb">
|
<nav class="breadcrumb-container" aria-label="breadcrumb">
|
||||||
|
@ -1,107 +0,0 @@
|
|||||||
{% extends 'generic/object_edit.html' %}
|
|
||||||
{% load static %}
|
|
||||||
{% load form_helpers %}
|
|
||||||
{% load helpers %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block form %}
|
|
||||||
<div class="field-group my-5">
|
|
||||||
<div class="row">
|
|
||||||
<h5 class="col-9 offset-3">{% trans "Inventory Item" %}</h5>
|
|
||||||
</div>
|
|
||||||
{% render_field form.device %}
|
|
||||||
{% render_field form.parent %}
|
|
||||||
{% render_field form.name %}
|
|
||||||
{% render_field form.label %}
|
|
||||||
{% render_field form.role %}
|
|
||||||
{% render_field form.description %}
|
|
||||||
{% render_field form.tags %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field-group my-5">
|
|
||||||
<div class="row">
|
|
||||||
<h5 class="col-9 offset-3">{% trans "Hardware" %}</h5>
|
|
||||||
</div>
|
|
||||||
{% render_field form.manufacturer %}
|
|
||||||
{% render_field form.part_id %}
|
|
||||||
{% render_field form.serial %}
|
|
||||||
{% render_field form.asset_tag %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field-group my-5">
|
|
||||||
<div class="row">
|
|
||||||
<h5 class="col-9 offset-3">{% trans "Component Assignment" %}</h5>
|
|
||||||
</div>
|
|
||||||
<div class="row offset-sm-3">
|
|
||||||
<ul class="nav nav-pills mb-1" role="tablist">
|
|
||||||
<li role="presentation" class="nav-item">
|
|
||||||
<button role="tab" type="button" id="consoleport_tab" data-bs-toggle="tab" aria-controls="consoleport" data-bs-target="#consoleport" class="nav-link {% if form.initial.consoleport or form.no_component %}active{% endif %}">
|
|
||||||
{% trans "Console Port" %}
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
<li role="presentation" class="nav-item">
|
|
||||||
<button role="tab" type="button" id="consoleserverport_tab" data-bs-toggle="tab" aria-controls="consoleserverport" data-bs-target="#consoleserverport" class="nav-link {% if form.initial.consoleserverport %}active{% endif %}">
|
|
||||||
{% trans "Console Server Port" %}
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
<li role="presentation" class="nav-item">
|
|
||||||
<button role="tab" type="button" id="frontport_tab" data-bs-toggle="tab" aria-controls="frontport" data-bs-target="#frontport" class="nav-link {% if form.initial.frontport %}active{% endif %}">
|
|
||||||
{% trans "Front Port" %}
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
<li role="presentation" class="nav-item">
|
|
||||||
<button role="tab" type="button" id="interface_tab" data-bs-toggle="tab" aria-controls="interface" data-bs-target="#interface" class="nav-link {% if form.initial.interface %}active{% endif %}">
|
|
||||||
{% trans "Interface" %}
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
<li role="presentation" class="nav-item">
|
|
||||||
<button role="tab" type="button" id="poweroutlet_tab" data-bs-toggle="tab" aria-controls="poweroutlet" data-bs-target="#poweroutlet" class="nav-link {% if form.initial.poweroutlet %}active{% endif %}">
|
|
||||||
{% trans "Power Outlet" %}
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
<li role="presentation" class="nav-item">
|
|
||||||
<button role="tab" type="button" id="powerport_tab" data-bs-toggle="tab" aria-controls="powerport" data-bs-target="#powerport" class="nav-link {% if form.initial.powerport %}active{% endif %}">
|
|
||||||
{% trans "Power Port" %}
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
<li role="presentation" class="nav-item">
|
|
||||||
<button role="tab" type="button" id="rearport_tab" data-bs-toggle="tab" aria-controls="rearport" data-bs-target="#rearport" class="nav-link {% if form.initial.rearport %}active{% endif %}">
|
|
||||||
{% trans "Rear Port" %}
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="tab-content p-0 border-0">
|
|
||||||
<div class="tab-pane {% if form.initial.consoleport or form.no_component %}active{% endif %}" id="consoleport" role="tabpanel" aria-labeled-by="consoleport_tab">
|
|
||||||
{% render_field form.consoleport %}
|
|
||||||
</div>
|
|
||||||
<div class="tab-pane {% if form.initial.consoleserverport %}active{% endif %}" id="consoleserverport" role="tabpanel" aria-labeled-by="consoleserverport_tab">
|
|
||||||
{% render_field form.consoleserverport %}
|
|
||||||
</div>
|
|
||||||
<div class="tab-pane {% if form.initial.frontport %}active{% endif %}" id="frontport" role="tabpanel" aria-labeled-by="frontport_tab">
|
|
||||||
{% render_field form.frontport %}
|
|
||||||
</div>
|
|
||||||
<div class="tab-pane {% if form.initial.interface %}active{% endif %}" id="interface" role="tabpanel" aria-labeled-by="interface_tab">
|
|
||||||
{% render_field form.interface %}
|
|
||||||
</div>
|
|
||||||
<div class="tab-pane {% if form.initial.poweroutlet %}active{% endif %}" id="poweroutlet" role="tabpanel" aria-labeled-by="poweroutlet_tab">
|
|
||||||
{% render_field form.poweroutlet %}
|
|
||||||
</div>
|
|
||||||
<div class="tab-pane {% if form.initial.powerport %}active{% endif %}" id="powerport" role="tabpanel" aria-labeled-by="powerport_tab">
|
|
||||||
{% render_field form.powerport %}
|
|
||||||
</div>
|
|
||||||
<div class="tab-pane {% if form.initial.rearport %}active{% endif %}" id="rearport" role="tabpanel" aria-labeled-by="rearport_tab">
|
|
||||||
{% render_field form.rearport %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if form.custom_fields %}
|
|
||||||
<div class="field-group my-5">
|
|
||||||
<div class="row">
|
|
||||||
<h5 class="col-9 offset-3">{% trans "Custom Fields" %}</h5>
|
|
||||||
</div>
|
|
||||||
{% render_custom_fields form %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
@ -54,6 +54,10 @@
|
|||||||
{{ object.tenant|linkify|placeholder }}
|
{{ object.tenant|linkify|placeholder }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">{% trans "Facility" %}</th>
|
||||||
|
<td>{{ object.facility|placeholder }}</td>
|
||||||
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{% include 'inc/panels/tags.html' %}
|
{% include 'inc/panels/tags.html' %}
|
||||||
|
@ -1,90 +0,0 @@
|
|||||||
{% extends 'generic/object_edit.html' %}
|
|
||||||
{% load form_helpers %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block form %}
|
|
||||||
<div class="field-group my-5">
|
|
||||||
<div class="row">
|
|
||||||
<h5 class="col-9 offset-3">{% trans "Rack" %}</h5>
|
|
||||||
</div>
|
|
||||||
{% render_field form.site %}
|
|
||||||
{% render_field form.location %}
|
|
||||||
{% render_field form.name %}
|
|
||||||
{% render_field form.status %}
|
|
||||||
{% render_field form.role %}
|
|
||||||
{% render_field form.description %}
|
|
||||||
{% render_field form.tags %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field-group my-5">
|
|
||||||
<div class="row">
|
|
||||||
<h5 class="col-9 offset-3">{% trans "Inventory Control" %}</h5>
|
|
||||||
</div>
|
|
||||||
{% render_field form.facility_id %}
|
|
||||||
{% render_field form.serial %}
|
|
||||||
{% render_field form.asset_tag %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field-group my-5">
|
|
||||||
<div class="row">
|
|
||||||
<h5 class="col-9 offset-3">{% trans "Tenancy" %}</h5>
|
|
||||||
</div>
|
|
||||||
{% render_field form.tenant_group %}
|
|
||||||
{% render_field form.tenant %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field-group my-5">
|
|
||||||
<div class="row">
|
|
||||||
<h5 class="col-9 offset-3">{% trans "Dimensions" %}</h5>
|
|
||||||
</div>
|
|
||||||
{% render_field form.type %}
|
|
||||||
{% render_field form.width %}
|
|
||||||
{% render_field form.starting_unit %}
|
|
||||||
{% render_field form.u_height %}
|
|
||||||
<div class="row mb-3">
|
|
||||||
<label class="col col-md-3 col-form-label text-lg-end">{% trans "Outer Dimensions" %}</label>
|
|
||||||
<div class="col col-md-3 mb-1">
|
|
||||||
{{ form.outer_width }}
|
|
||||||
<div class="form-text">{% trans "Width" %}</div>
|
|
||||||
</div>
|
|
||||||
<div class="col col-md-3 mb-1">
|
|
||||||
{{ form.outer_depth }}
|
|
||||||
<div class="form-text">{% trans "Depth" %}</div>
|
|
||||||
</div>
|
|
||||||
<div class="col col-md-3 mb-1">
|
|
||||||
{{ form.outer_unit }}
|
|
||||||
<div class="form-text">{% trans "Unit" %}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-3">
|
|
||||||
<label class="col col-md-3 col-form-label text-lg-end">{% trans "Weight" %}</label>
|
|
||||||
<div class="col col-md-3 mb-1">
|
|
||||||
{{ form.weight }}
|
|
||||||
<div class="form-text">{% trans "Weight" %}</div>
|
|
||||||
</div>
|
|
||||||
<div class="col col-md-3 mb-1">
|
|
||||||
{{ form.max_weight }}
|
|
||||||
<div class="form-text">{% trans "Maximum Weight" %}</div>
|
|
||||||
</div>
|
|
||||||
<div class="col col-md-3 mb-1">
|
|
||||||
{{ form.weight_unit }}
|
|
||||||
<div class="form-text">{% trans "Unit" %}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% render_field form.mounting_depth %}
|
|
||||||
{% render_field form.desc_units %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if form.custom_fields %}
|
|
||||||
<div class="field-group my-5">
|
|
||||||
<div class="row">
|
|
||||||
<h5 class="col-9 offset-3">{% trans "Custom Fields" %}</h5>
|
|
||||||
</div>
|
|
||||||
{% render_custom_fields form %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div class="field-group my-5">
|
|
||||||
{% render_field form.comments %}
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
@ -17,7 +17,9 @@
|
|||||||
<th scope="row">Type</th>
|
<th scope="row">Type</th>
|
||||||
<td>
|
<td>
|
||||||
{{ object.get_type_display }}
|
{{ object.get_type_display }}
|
||||||
{% if object.object_type %}({{ object.object_type.model|bettertitle }}){% endif %}
|
{% if object.related_object_type %}
|
||||||
|
({{ object.related_object_type.model|bettertitle }})
|
||||||
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -3,124 +3,63 @@
|
|||||||
{% load log_levels %}
|
{% load log_levels %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
<p>
|
<div class="htmx-container">
|
||||||
{% if job.started %}
|
<p>
|
||||||
{% trans "Started" %}: <strong>{{ job.started|annotated_date }}</strong>
|
{% if job.started %}
|
||||||
{% elif job.scheduled %}
|
{% trans "Started" %}: <strong>{{ job.started|annotated_date }}</strong>
|
||||||
{% trans "Scheduled for" %}: <strong>{{ job.scheduled|annotated_date }}</strong> ({{ job.scheduled|naturaltime }})
|
{% elif job.scheduled %}
|
||||||
{% else %}
|
{% trans "Scheduled for" %}: <strong>{{ job.scheduled|annotated_date }}</strong> ({{ job.scheduled|naturaltime }})
|
||||||
{% trans "Created" %}: <strong>{{ job.created|annotated_date }}</strong>
|
{% else %}
|
||||||
{% endif %}
|
{% trans "Created" %}: <strong>{{ job.created|annotated_date }}</strong>
|
||||||
|
{% endif %}
|
||||||
|
{% if job.completed %}
|
||||||
|
{% trans "Duration" %}: <strong>{{ job.duration }}</strong>
|
||||||
|
{% endif %}
|
||||||
|
<span id="pending-result-label">{% badge job.get_status_display job.get_status_color %}</span>
|
||||||
|
</p>
|
||||||
{% if job.completed %}
|
{% if job.completed %}
|
||||||
{% trans "Duration" %}: <strong>{{ job.duration }}</strong>
|
{% if tests %}
|
||||||
{% endif %}
|
{# Summary of test methods #}
|
||||||
<span id="pending-result-label">{% badge job.get_status_display job.get_status_color %}</span>
|
<div class="card">
|
||||||
</p>
|
<h5 class="card-header">{% trans "Test Summary" %}</h5>
|
||||||
{% if job.completed %}
|
<table class="table table-hover">
|
||||||
|
{% for test, data in tests.items %}
|
||||||
{# Script log. Legacy reports will not have this. #}
|
|
||||||
{% if 'log' in job.data %}
|
|
||||||
<div class="card mb-3">
|
|
||||||
<h5 class="card-header">{% trans "Log" %}</h5>
|
|
||||||
{% if job.data.log %}
|
|
||||||
<table class="table table-hover panel-body">
|
|
||||||
<tr>
|
|
||||||
<th>{% trans "Line" %}</th>
|
|
||||||
<th>{% trans "Time" %}</th>
|
|
||||||
<th>{% trans "Level" %}</th>
|
|
||||||
<th>{% trans "Message" %}</th>
|
|
||||||
</tr>
|
|
||||||
{% for log in job.data.log %}
|
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ forloop.counter }}</td>
|
<td class="font-monospace"><a href="#{{ test }}">{{ test }}</a></td>
|
||||||
<td>{{ log.time|placeholder }}</td>
|
<td class="text-end report-stats">
|
||||||
<td>{% log_level log.status %}</td>
|
<span class="badge text-bg-success">{{ data.success }}</span>
|
||||||
<td>{{ log.message|markdown }}</td>
|
<span class="badge text-bg-info">{{ data.info }}</span>
|
||||||
|
<span class="badge text-bg-warning">{{ data.warning }}</span>
|
||||||
|
<span class="badge text-bg-danger">{{ data.failure }}</span>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
{% else %}
|
</div>
|
||||||
<div class="card-body text-muted">{% trans "None" %}</div>
|
{% endif %}
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{# Script output. Legacy reports will not have this. #}
|
{% if table %}
|
||||||
{% if 'output' in job.data %}
|
|
||||||
<div class="card mb-3">
|
|
||||||
<h5 class="card-header">{% trans "Output" %}</h5>
|
|
||||||
{% if job.data.output %}
|
|
||||||
<pre class="card-body font-monospace">{{ job.data.output }}</pre>
|
|
||||||
{% else %}
|
|
||||||
<div class="card-body text-muted">{% trans "None" %}</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{# Test method logs (for legacy Reports) #}
|
|
||||||
{% if tests %}
|
|
||||||
|
|
||||||
{# Summary of test methods #}
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h5 class="card-header">{% trans "Test Summary" %}</h5>
|
<div class="table-responsive" id="object_list">
|
||||||
<table class="table table-hover">
|
<h5 class="card-header">{% trans "Log" %}</h5>
|
||||||
{% for test, data in tests.items %}
|
{% include 'htmx/table.html' %}
|
||||||
<tr>
|
</div>
|
||||||
<td class="font-monospace"><a href="#{{ test }}">{{ test }}</a></td>
|
|
||||||
<td class="text-end report-stats">
|
|
||||||
<span class="badge text-bg-success">{{ data.success }}</span>
|
|
||||||
<span class="badge text-bg-info">{{ data.info }}</span>
|
|
||||||
<span class="badge text-bg-warning">{{ data.warning }}</span>
|
|
||||||
<span class="badge text-bg-danger">{{ data.failure }}</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{# Detailed results for individual tests #}
|
{# Script output. Legacy reports will not have this. #}
|
||||||
<div class="card">
|
{% if 'output' in job.data %}
|
||||||
<h5 class="card-header">{% trans "Test Details" %}</h5>
|
<div class="card mb-3">
|
||||||
<table class="table table-hover report">
|
<h5 class="card-header">{% trans "Output" %}</h5>
|
||||||
<thead>
|
{% if job.data.output %}
|
||||||
<tr class="table-headings">
|
<pre class="card-body font-monospace">{{ job.data.output }}</pre>
|
||||||
<th>{% trans "Time" %}</th>
|
{% else %}
|
||||||
<th>{% trans "Level" %}</th>
|
<div class="card-body text-muted">{% trans "None" %}</div>
|
||||||
<th>{% trans "Object" %}</th>
|
{% endif %}
|
||||||
<th>{% trans "Message" %}</th>
|
</div>
|
||||||
</tr>
|
{% endif %}
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for test, data in tests.items %}
|
|
||||||
<tr>
|
|
||||||
<th colspan="4" style="font-family: monospace">
|
|
||||||
<a name="{{ test }}"></a>{{ test }}
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
{% for time, level, obj, url, message in data.log %}
|
|
||||||
<tr class="{% if level == 'failure' %}danger{% elif level %}{{ level }}{% endif %}">
|
|
||||||
<td>{{ time }}</td>
|
|
||||||
<td>
|
|
||||||
<label class="badge text-bg-{% if level == 'failure' %}danger{% else %}{{ level }}{% endif %}">{{ level|title }}</label>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{% if obj and url %}
|
|
||||||
<a href="{{ url }}">{{ obj }}</a>
|
|
||||||
{% elif obj %}
|
|
||||||
{{ obj }}
|
|
||||||
{% else %}
|
|
||||||
{{ ''|placeholder }}
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
<td class="rendered-markdown">{{ message|markdown }}</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
{% elif job.started %}
|
||||||
|
{% include 'extras/inc/result_pending.html' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% elif job.started %}
|
</div>
|
||||||
{% include 'extras/inc/result_pending.html' %}
|
|
||||||
{% endif %}
|
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
{% extends 'generic/object_edit.html' %}
|
|
||||||
{% load helpers %}
|
|
||||||
{% load form_helpers %}
|
|
||||||
|
|
||||||
{% block form %}
|
|
||||||
<div class="field-group mb-5">
|
|
||||||
<div class="row mb-3">
|
|
||||||
<label class="col-sm-3 col-form-label text-lg-end required">
|
|
||||||
{{ object.parent|meta:"verbose_name"|bettertitle }}
|
|
||||||
</label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<div class="form-control-plaintext">
|
|
||||||
{{ object.parent|linkify }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% render_form form %}
|
|
||||||
</div>
|
|
||||||
{% endblock form %}
|
|
@ -11,7 +11,7 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block page-header %}
|
{% block page-header %}
|
||||||
<div class="container-xl mt-2">
|
<div class="container-fluid mt-2">
|
||||||
<nav class="breadcrumb-container" aria-label="breadcrumb">
|
<nav class="breadcrumb-container" aria-label="breadcrumb">
|
||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<li class="breadcrumb-item"><a href="{% url 'extras:script_list' %}">{% trans "Scripts" %}</a></li>
|
<li class="breadcrumb-item"><a href="{% url 'extras:script_list' %}">{% trans "Scripts" %}</a></li>
|
||||||
@ -32,28 +32,74 @@
|
|||||||
{% block tabs %}
|
{% block tabs %}
|
||||||
<ul class="nav nav-tabs" role="tablist">
|
<ul class="nav nav-tabs" role="tablist">
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<a href="#log" role="tab" data-bs-toggle="tab" class="nav-link active">{% trans "Log" %}</a>
|
<a href="#results" role="tab" data-bs-toggle="tab" class="nav-link active">{% trans "Results" %}</a>
|
||||||
</li>
|
|
||||||
<li class="nav-item" role="presentation">
|
|
||||||
<a href="#source" role="tab" data-bs-toggle="tab" class="nav-link">{% trans "Source" %}</a>
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div role="tabpanel" class="tab-pane active" id="log">
|
{# Object list tab #}
|
||||||
<div class="row">
|
<div class="tab-pane show active" id="results" role="tabpanel" aria-labelledby="results-tab">
|
||||||
<div class="col col-md-12"{% if not job.completed %} hx-get="{% url 'extras:script_result' job_pk=job.pk %}" hx-trigger="load delay:0.5s, every 5s"{% endif %}>
|
|
||||||
{% include 'extras/htmx/script_result.html' %}
|
{# Object table controls #}
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-auto ms-auto d-print-none">
|
||||||
|
{% if request.user.is_authenticated %}
|
||||||
|
<div class="table-configure input-group">
|
||||||
|
<button type="button" data-bs-toggle="modal" title="{% trans "Configure Table" %}" data-bs-target="#ObjectTable_config"
|
||||||
|
class="btn">
|
||||||
|
<i class="mdi mdi-cog"></i> {% trans "Configure Table" %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<form method="post" class="form form-horizontal">
|
||||||
|
{% csrf_token %}
|
||||||
|
{# "Select all" form #}
|
||||||
|
{% if table.paginator.num_pages > 1 %}
|
||||||
|
<div id="select-all-box" class="d-none card d-print-none">
|
||||||
|
<div class="form col-md-12">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="form-check">
|
||||||
|
<input type="checkbox" id="select-all" name="_all" class="form-check-input" />
|
||||||
|
<label for="select-all" class="form-check-label">
|
||||||
|
{% blocktrans trimmed with count=table.rows|length object_type_plural=table.data.verbose_name_plural %}
|
||||||
|
Select <strong>all {{ count }} {{ object_type_plural }}</strong> matching query
|
||||||
|
{% endblocktrans %}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="form form-horizontal">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="return_url" value="{% if return_url %}{{ return_url }}{% else %}{{ request.path }}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}{% endif %}" />
|
||||||
|
|
||||||
|
{# Objects table #}
|
||||||
|
<div class="col col-md-12"{% if not job.completed %} hx-get="{% url 'extras:script_result' job_pk=job.pk %}" hx-trigger="load delay:0.5s, every 5s"{% endif %}>
|
||||||
|
{% include 'extras/htmx/script_result.html' %}
|
||||||
|
</div>
|
||||||
|
{# /Objects table #}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{# /Object list tab #}
|
||||||
<div role="tabpanel" class="tab-pane" id="source">
|
|
||||||
<p><code>{{ script.filename }}</code></p>
|
{# Filters tab #}
|
||||||
<pre class="block">{{ script.source }}</pre>
|
{% if filter_form %}
|
||||||
</div>
|
<div class="tab-pane show" id="filters-form" role="tabpanel" aria-labelledby="filters-form-tab">
|
||||||
|
{% include 'inc/filter_list.html' %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{# /Filters tab #}
|
||||||
|
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
||||||
{% block modals %}
|
{% block modals %}
|
||||||
{% include 'inc/htmx_modal.html' %}
|
{% table_config_form table table_name="ObjectTable" %}
|
||||||
{% endblock modals %}
|
{% endblock modals %}
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
|
|
||||||
{% block page-header %}
|
{% block page-header %}
|
||||||
<div class="container-xl mt-2 d-print-none">
|
<div class="container-fluid mt-2 d-print-none">
|
||||||
<div class="d-flex justify-content-between">
|
<div class="d-flex justify-content-between">
|
||||||
|
|
||||||
{# Title #}
|
{# Title #}
|
||||||
@ -29,7 +29,7 @@
|
|||||||
|
|
||||||
{# Tabs #}
|
{# Tabs #}
|
||||||
<div class="page-tabs mt-3">
|
<div class="page-tabs mt-3">
|
||||||
<div class="container-xl">
|
<div class="container-fluid">
|
||||||
{% block tabs %}{% endblock %}
|
{% block tabs %}{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -71,7 +71,7 @@ Context:
|
|||||||
{# Selected objects list #}
|
{# Selected objects list #}
|
||||||
<div class="tab-pane" id="object-list" role="tabpanel" aria-labelledby="object-list-tab">
|
<div class="tab-pane" id="object-list" role="tabpanel" aria-labelledby="object-list-tab">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body table-responsive">
|
<div class="table-responsive">
|
||||||
{% render_table table 'inc/table.html' %}
|
{% render_table table 'inc/table.html' %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -49,23 +49,8 @@ Context:
|
|||||||
{% if form.fieldsets %}
|
{% if form.fieldsets %}
|
||||||
|
|
||||||
{# Render grouped fields according to declared fieldsets #}
|
{# Render grouped fields according to declared fieldsets #}
|
||||||
{% for group, fields in form.fieldsets %}
|
{% for fieldset in form.fieldsets %}
|
||||||
<div class="field-group mb-5">
|
{% render_fieldset form fieldset %}
|
||||||
<div class="row">
|
|
||||||
<h5 class="col-9 offset-3">
|
|
||||||
{% if group %}{{ group }}{% else %}{{ model|meta:"verbose_name"|bettertitle }}{% endif %}
|
|
||||||
</h5>
|
|
||||||
</div>
|
|
||||||
{% for name in fields %}
|
|
||||||
{% with field=form|getfield:name %}
|
|
||||||
{% if field.name in form.nullable_fields %}
|
|
||||||
{% render_field field bulk_nullable=True %}
|
|
||||||
{% else %}
|
|
||||||
{% render_field field %}
|
|
||||||
{% endif %}
|
|
||||||
{% endwith %}
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
{# Render tag add/remove fields #}
|
{# Render tag add/remove fields #}
|
||||||
|
@ -33,9 +33,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="container-xl px-0">
|
<div class="container-fluid px-0">
|
||||||
<div class="table-responsive">
|
<div class="card">
|
||||||
{% render_table table 'inc/table.html' %}
|
<div class="table-responsive">
|
||||||
|
{% render_table table 'inc/table.html' %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<form action="." method="post" class="form">
|
<form action="." method="post" class="form">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
@ -19,7 +19,7 @@ Context:
|
|||||||
{% endcomment %}
|
{% endcomment %}
|
||||||
|
|
||||||
{% block page-header %}
|
{% block page-header %}
|
||||||
<div class="container-xl">
|
<div class="container-fluid">
|
||||||
<div class="d-flex justify-content-between align-items-center mt-2">
|
<div class="d-flex justify-content-between align-items-center mt-2">
|
||||||
|
|
||||||
{# Breadcrumbs #}
|
{# Breadcrumbs #}
|
||||||
|
@ -9,21 +9,8 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
{# Render grouped fields according to Form #}
|
{# Render grouped fields according to Form #}
|
||||||
{% for group, fields in form.fieldsets %}
|
{% for fieldset in form.fieldsets %}
|
||||||
<div class="field-group mb-5">
|
{% render_fieldset form fieldset %}
|
||||||
{% if group %}
|
|
||||||
<div class="row">
|
|
||||||
<h5 class="col-9 offset-3">{{ group }}</h5>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% for name in fields %}
|
|
||||||
{% with field=form|getfield:name %}
|
|
||||||
{% if field and not field.field.widget.is_hidden %}
|
|
||||||
{% render_field field %}
|
|
||||||
{% endif %}
|
|
||||||
{% endwith %}
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
{% if form.custom_fields %}
|
{% if form.custom_fields %}
|
||||||
|
@ -9,18 +9,9 @@
|
|||||||
{{ field }}
|
{{ field }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{# List filters by group #}
|
{# List filters by group #}
|
||||||
{% for heading, fields in filter_form.fieldsets %}
|
{% for fieldset in filter_form.fieldsets %}
|
||||||
<div class="col col-12">
|
<div class="col col-12">
|
||||||
{% if heading %}
|
{% render_fieldset filter_form fieldset %}
|
||||||
<div class="hr-text">
|
|
||||||
<span>{{ heading }}</span>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% for name in fields %}
|
|
||||||
{% with field=filter_form|get_item:name %}
|
|
||||||
{% render_field field %}
|
|
||||||
{% endwith %}
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
</div>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
{# List all non-customfield filters as declared in the form class #}
|
{# List all non-customfield filters as declared in the form class #}
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
{% extends 'generic/object_edit.html' %}
|
|
||||||
{% load form_helpers %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block form %}
|
|
||||||
<div class="field-group my-5">
|
|
||||||
<div class="row">
|
|
||||||
<h5 class="col-9 offset-3">{% trans "FHRP Group Assignment" %}</h5>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-3">
|
|
||||||
<label class="col-sm-3 col-form-label text-lg-end">{% trans "Interface" %}</label>
|
|
||||||
<div class="col">
|
|
||||||
<input class="form-control" value="{{ form.instance.interface }}" disabled />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% render_field form.group %}
|
|
||||||
{% render_field form.priority %}
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
@ -1,93 +0,0 @@
|
|||||||
{% extends 'generic/object_edit.html' %}
|
|
||||||
{% load static %}
|
|
||||||
{% load form_helpers %}
|
|
||||||
{% load helpers %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block tabs %}
|
|
||||||
{% include 'ipam/inc/ipaddress_edit_header.html' with active_tab='add' %}
|
|
||||||
{% endblock tabs %}
|
|
||||||
|
|
||||||
{% block form %}
|
|
||||||
<div class="field-group my-5">
|
|
||||||
<div class="row">
|
|
||||||
<h5 class="col-9 offset-3">{% trans "IP Address" %}</h5>
|
|
||||||
</div>
|
|
||||||
{% render_field form.address %}
|
|
||||||
{% render_field form.status %}
|
|
||||||
{% render_field form.role %}
|
|
||||||
{% render_field form.vrf %}
|
|
||||||
{% render_field form.dns_name %}
|
|
||||||
{% render_field form.description %}
|
|
||||||
{% render_field form.tags %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field-group my-5">
|
|
||||||
<div class="row">
|
|
||||||
<h5 class="col-9 offset-3">{% trans "Tenancy" %}</h5>
|
|
||||||
</div>
|
|
||||||
{% render_field form.tenant_group %}
|
|
||||||
{% render_field form.tenant %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field-group my-5">
|
|
||||||
<div class="row">
|
|
||||||
<h5 class="col-9 offset-3">{% trans "Interface Assignment" %}</h5>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-9 offset-3">
|
|
||||||
<ul class="nav nav-pills mb-1" role="tablist">
|
|
||||||
<li role="presentation" class="nav-item">
|
|
||||||
<button role="tab" type="button" id="device_tab" data-bs-toggle="tab" aria-controls="device" data-bs-target="#device" class="nav-link {% if not form.initial.vminterface and not form.initial.fhrpgroup %}active{% endif %}">
|
|
||||||
{% trans "Device" %}
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
<li role="presentation" class="nav-item">
|
|
||||||
<button role="tab" type="button" id="vm_tab" data-bs-toggle="tab" aria-controls="vm" data-bs-target="#vm" class="nav-link {% if form.initial.vminterface %}active{% endif %}">
|
|
||||||
{% trans "Virtual Machine" %}
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
<li role="presentation" class="nav-item">
|
|
||||||
<button role="tab" type="button" id="fhrpgroup_tab" data-bs-toggle="tab" aria-controls="fhrpgroup" data-bs-target="#fhrpgroup" class="nav-link {% if form.initial.fhrpgroup %}active{% endif %}">
|
|
||||||
{% trans "FHRP Group" %}
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="tab-content p-0 border-0">
|
|
||||||
<div class="tab-pane {% if not form.initial.vminterface and not form.initial.fhrpgroup %}active{% endif %}" id="device" role="tabpanel" aria-labeled-by="device_tab">
|
|
||||||
{% render_field form.interface %}
|
|
||||||
</div>
|
|
||||||
<div class="tab-pane {% if form.initial.vminterface %}active{% endif %}" id="vm" role="tabpanel" aria-labeled-by="vm_tab">
|
|
||||||
{% render_field form.vminterface %}
|
|
||||||
</div>
|
|
||||||
<div class="tab-pane {% if form.initial.fhrpgroup %}active{% endif %}" id="fhrpgroup" role="tabpanel" aria-labeled-by="fhrpgroup_tab">
|
|
||||||
{% render_field form.fhrpgroup %}
|
|
||||||
</div>
|
|
||||||
{% render_field form.primary_for_parent %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field-group my-5">
|
|
||||||
<div class="row">
|
|
||||||
<h5 class="col-9 offset-3">{% trans "NAT IP (Inside" %})</h5>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
{% render_field form.nat_inside %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field-group my-5">
|
|
||||||
{% render_field form.comments %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if form.custom_fields %}
|
|
||||||
<div class="field-group my-5">
|
|
||||||
<div class="row">
|
|
||||||
<h5 class="col-9 offset-3">{% trans "Custom Fields" %}</h5>
|
|
||||||
</div>
|
|
||||||
{% render_custom_fields form %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user