Merge branch 'feature' into 14736-htmx
This commit is contained in:
commit
3061e16304
|
@ -14,10 +14,6 @@ django-debug-toolbar
|
|||
# https://github.com/carltongibson/django-filter/blob/main/CHANGES.rst
|
||||
django-filter
|
||||
|
||||
# Django debug toolbar extension with support for GraphiQL
|
||||
# https://github.com/flavors/django-graphiql-debug-toolbar/blob/main/CHANGES.rst
|
||||
django-graphiql-debug-toolbar
|
||||
|
||||
# HTMX utilities for Django
|
||||
# https://django-htmx.readthedocs.io/en/latest/changelog.html
|
||||
django-htmx
|
||||
|
@ -75,11 +71,6 @@ drf-spectacular-sidecar
|
|||
# https://github.com/kurtmckee/feedparser/blob/develop/CHANGELOG.rst
|
||||
feedparser
|
||||
|
||||
# Django wrapper for Graphene (GraphQL support)
|
||||
# https://github.com/graphql-python/graphene-django/releases
|
||||
# Pinned to v3.0.0 for GraphiQL UI issue (see #12762)
|
||||
graphene_django==3.0.0
|
||||
|
||||
# WSGI HTTP server
|
||||
# https://docs.gunicorn.org/en/latest/news.html
|
||||
gunicorn
|
||||
|
@ -136,8 +127,16 @@ social-auth-core
|
|||
# https://github.com/python-social-auth/social-app-django/blob/master/CHANGELOG.md
|
||||
social-auth-app-django
|
||||
|
||||
# Strawberry GraphQL
|
||||
# https://github.com/strawberry-graphql/strawberry/blob/main/CHANGELOG.md
|
||||
strawberry-graphql
|
||||
|
||||
# Strawberry GraphQL Django extension
|
||||
# https://github.com/strawberry-graphql/strawberry-django/blob/main/CHANGELOG.md
|
||||
strawberry-graphql-django
|
||||
|
||||
# SVG image rendering (used for rack elevations)
|
||||
# hhttps://github.com/mozman/svgwrite/blob/master/NEWS.rst
|
||||
# https://github.com/mozman/svgwrite/blob/master/NEWS.rst
|
||||
svgwrite
|
||||
|
||||
# Tabular dataset library (for table-based exports)
|
||||
|
|
|
@ -4,7 +4,7 @@ NetBox validates every object prior to it being written to the database to ensur
|
|||
|
||||
## Custom Validation Rules
|
||||
|
||||
Custom validation rules are expressed as a mapping of model attributes to a set of rules to which that attribute must conform. For example:
|
||||
Custom validation rules are expressed as a mapping of object attributes to a set of rules to which that attribute must conform. For example:
|
||||
|
||||
```json
|
||||
{
|
||||
|
@ -17,6 +17,8 @@ Custom validation rules are expressed as a mapping of model attributes to a set
|
|||
|
||||
This defines a custom validator which checks that the length of the `name` attribute for an object is at least five characters long, and no longer than 30 characters. This validation is executed _after_ NetBox has performed its own internal validation.
|
||||
|
||||
### Validation Types
|
||||
|
||||
The `CustomValidator` class supports several validation types:
|
||||
|
||||
* `min`: Minimum value
|
||||
|
@ -34,16 +36,33 @@ The `min` and `max` types should be defined for numeric values, whereas `min_len
|
|||
!!! warning
|
||||
Bear in mind that these validators merely supplement NetBox's own validation: They will not override it. For example, if a certain model field is required by NetBox, setting a validator for it with `{'prohibited': True}` will not work.
|
||||
|
||||
### Validating Request Parameters
|
||||
|
||||
!!! info "This feature was introduced in NetBox v4.0."
|
||||
|
||||
In addition to validating object attributes, custom validators can also match against parameters of the current request (where available). For example, the following rule will permit only the user named "admin" to modify an object:
|
||||
|
||||
```json
|
||||
{
|
||||
"request.user.username": {
|
||||
"eq": "admin"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
!!! tip
|
||||
Custom validation should generally not be used to enforce permissions. NetBox provides a robust [object-based permissions](../administration/permissions.md) mechanism which should be used for this purpose.
|
||||
|
||||
### Custom Validation Logic
|
||||
|
||||
There may be instances where the provided validation types are insufficient. NetBox provides a `CustomValidator` class which can be extended to enforce arbitrary validation logic by overriding its `validate()` method, and calling `fail()` when an unsatisfactory condition is detected.
|
||||
There may be instances where the provided validation types are insufficient. NetBox provides a `CustomValidator` class which can be extended to enforce arbitrary validation logic by overriding its `validate()` method, and calling `fail()` when an unsatisfactory condition is detected. The `validate()` method should accept an instance (the object being saved) as well as the current request effecting the change.
|
||||
|
||||
```python
|
||||
from extras.validators import CustomValidator
|
||||
|
||||
class MyValidator(CustomValidator):
|
||||
|
||||
def validate(self, instance):
|
||||
def validate(self, instance, request):
|
||||
if instance.status == 'active' and not instance.description:
|
||||
self.fail("Active sites must have a description set!", field='status')
|
||||
```
|
||||
|
|
|
@ -62,10 +62,11 @@ class Circuit(PrimaryModel):
|
|||
|
||||
1. Import `gettext_lazy` as `_`.
|
||||
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
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from utilities.forms.rendering import FieldSet
|
||||
|
||||
class CircuitBulkEditForm(NetBoxModelBulkEditForm):
|
||||
description = forms.CharField(
|
||||
|
@ -74,7 +75,7 @@ class CircuitBulkEditForm(NetBoxModelBulkEditForm):
|
|||
)
|
||||
|
||||
fieldsets = (
|
||||
(_('Circuit'), ('provider', 'type', 'status', 'description')),
|
||||
FieldSet('provider', 'type', 'status', 'description', name=_('Circuit')),
|
||||
)
|
||||
```
|
||||
|
||||
|
|
|
@ -26,3 +26,7 @@ The location's operational status.
|
|||
|
||||
!!! tip
|
||||
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.
|
||||
|
|
|
@ -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.
|
||||
|
||||
| Attribute | Description |
|
||||
|-------------|-------------------------------------------------------------|
|
||||
| `fieldsets` | A tuple of two-tuples defining the form's layout (optional) |
|
||||
| Attribute | Description |
|
||||
|-------------|---------------------------------------------------------------------------------------|
|
||||
| `fieldsets` | A tuple of `FieldSet` instances which control how form fields are rendered (optional) |
|
||||
|
||||
**Example**
|
||||
|
||||
```python
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from dcim.models import Site
|
||||
from netbox.forms import NetBoxModelForm
|
||||
from utilities.forms.fields import CommentField, DynamicModelChoiceField
|
||||
from utilities.forms.rendering import FieldSet
|
||||
from .models import MyModel
|
||||
|
||||
class MyModelForm(NetBoxModelForm):
|
||||
|
@ -33,8 +35,8 @@ class MyModelForm(NetBoxModelForm):
|
|||
)
|
||||
comments = CommentField()
|
||||
fieldsets = (
|
||||
('Model Stuff', ('name', 'status', 'site', 'tags')),
|
||||
('Tenancy', ('tenant_group', 'tenant')),
|
||||
FieldSet('name', 'status', 'site', 'tags', name=_('Model Stuff')),
|
||||
FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -52,6 +54,7 @@ This form facilitates the bulk import of new objects from CSV, JSON, or YAML dat
|
|||
**Example**
|
||||
|
||||
```python
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from dcim.models import Site
|
||||
from netbox.forms import NetBoxModelImportForm
|
||||
from utilities.forms import CSVModelChoiceField
|
||||
|
@ -62,7 +65,7 @@ class MyModelImportForm(NetBoxModelImportForm):
|
|||
site = CSVModelChoiceField(
|
||||
queryset=Site.objects.all(),
|
||||
to_field_name='name',
|
||||
help_text='Assigned site'
|
||||
help_text=_('Assigned site')
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -77,16 +80,18 @@ This form facilitates editing multiple objects in bulk. Unlike a model form, thi
|
|||
| Attribute | Description |
|
||||
|-------------------|---------------------------------------------------------------------------------------------|
|
||||
| `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) |
|
||||
|
||||
**Example**
|
||||
|
||||
```python
|
||||
from django import forms
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from dcim.models import Site
|
||||
from netbox.forms import NetBoxModelImportForm
|
||||
from utilities.forms import CommentField, DynamicModelChoiceField
|
||||
from utilities.forms.rendering import FieldSet
|
||||
from .models import MyModel, MyModelStatusChoices
|
||||
|
||||
|
||||
|
@ -106,7 +111,7 @@ class MyModelEditForm(NetBoxModelImportForm):
|
|||
|
||||
model = MyModel
|
||||
fieldsets = (
|
||||
('Model Stuff', ('name', 'status', 'site')),
|
||||
FieldSet('name', 'status', 'site', name=_('Model Stuff')),
|
||||
)
|
||||
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.
|
||||
|
||||
| Attribute | Description |
|
||||
|-------------------|-------------------------------------------------------------|
|
||||
| `model` | The model of object being edited |
|
||||
| `fieldsets` | A tuple of two-tuples defining the form's layout (optional) |
|
||||
| Attribute | Description |
|
||||
|-------------|---------------------------------------------------------------------------------------|
|
||||
| `model` | The model of object being edited |
|
||||
| `fieldsets` | A tuple of `FieldSet` instances which control how form fields are rendered (optional) |
|
||||
|
||||
**Example**
|
||||
|
||||
|
@ -206,3 +211,13 @@ In addition to the [form fields provided by Django](https://docs.djangoproject.c
|
|||
::: utilities.forms.fields.CSVMultipleContentTypeField
|
||||
options:
|
||||
members: false
|
||||
|
||||
## Form Rendering
|
||||
|
||||
::: utilities.forms.rendering.FieldSet
|
||||
|
||||
::: utilities.forms.rendering.InlineFields
|
||||
|
||||
::: utilities.forms.rendering.TabbedGroups
|
||||
|
||||
::: utilities.forms.rendering.ObjectAttribute
|
||||
|
|
|
@ -8,23 +8,32 @@ A plugin can extend NetBox's GraphQL API by registering its own schema class. By
|
|||
|
||||
```python
|
||||
# graphql.py
|
||||
import graphene
|
||||
from netbox.graphql.types import NetBoxObjectType
|
||||
from netbox.graphql.fields import ObjectField, ObjectListField
|
||||
from . import filtersets, models
|
||||
from typing import List
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
|
||||
class MyModelType(NetBoxObjectType):
|
||||
from . import models
|
||||
|
||||
class Meta:
|
||||
model = models.MyModel
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.MyModelFilterSet
|
||||
|
||||
class MyQuery(graphene.ObjectType):
|
||||
mymodel = ObjectField(MyModelType)
|
||||
mymodel_list = ObjectListField(MyModelType)
|
||||
@strawberry_django.type(
|
||||
models.MyModel,
|
||||
fields='__all__',
|
||||
)
|
||||
class MyModelType:
|
||||
pass
|
||||
|
||||
schema = MyQuery
|
||||
|
||||
@strawberry.type
|
||||
class MyQuery:
|
||||
@strawberry.field
|
||||
def dummymodel(self, id: int) -> DummyModelType:
|
||||
return None
|
||||
dummymodel_list: List[DummyModelType] = strawberry_django.field()
|
||||
|
||||
|
||||
schema = [
|
||||
MyQuery,
|
||||
]
|
||||
```
|
||||
|
||||
## GraphQL Objects
|
||||
|
@ -38,15 +47,3 @@ NetBox provides two object type classes for use by plugins.
|
|||
::: netbox.graphql.types.NetBoxObjectType
|
||||
options:
|
||||
members: false
|
||||
|
||||
## GraphQL Fields
|
||||
|
||||
NetBox provides two field classes for use by plugins.
|
||||
|
||||
::: netbox.graphql.fields.ObjectField
|
||||
options:
|
||||
members: false
|
||||
|
||||
::: netbox.graphql.fields.ObjectListField
|
||||
options:
|
||||
members: false
|
||||
|
|
|
@ -49,8 +49,8 @@ menu_items = (item1, item2, item3)
|
|||
Each menu item represents a link and (optionally) a set of buttons comprising one entry in NetBox's navigation menu. Menu items are defined as PluginMenuItem instances. An example is shown below.
|
||||
|
||||
```python title="navigation.py"
|
||||
from netbox.choices import ButtonColorChoices
|
||||
from netbox.plugins import PluginMenuButton, PluginMenuItem
|
||||
from utilities.choices import ButtonColorChoices
|
||||
|
||||
item1 = PluginMenuItem(
|
||||
link='plugins:myplugin:myview',
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
* 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 `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
|
||||
|
||||
|
@ -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.
|
||||
|
||||
#### 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
|
||||
|
||||
* [#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
|
||||
* [#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
|
||||
* [#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
|
||||
* [#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
|
||||
* [#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
|
||||
* [#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
|
||||
* [#15383](https://github.com/netbox-community/netbox/issues/15383) - Standardize filtering logic for the parents of recursively-nested models (parent & ancestor filters)
|
||||
|
||||
### 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
|
||||
* [#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
|
||||
* [#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
|
||||
* [#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)
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ from netbox.forms import NetBoxModelBulkEditForm
|
|||
from tenancy.models import Tenant
|
||||
from utilities.forms import add_blank_choice
|
||||
from utilities.forms.fields import ColorField, CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField
|
||||
from utilities.forms.rendering import FieldSet
|
||||
from utilities.forms.widgets import DatePicker, NumberWithOptions
|
||||
|
||||
__all__ = (
|
||||
|
@ -34,7 +35,7 @@ class ProviderBulkEditForm(NetBoxModelBulkEditForm):
|
|||
|
||||
model = Provider
|
||||
fieldsets = (
|
||||
(None, ('asns', 'description')),
|
||||
FieldSet('asns', 'description'),
|
||||
)
|
||||
nullable_fields = (
|
||||
'asns', 'description', 'comments',
|
||||
|
@ -56,7 +57,7 @@ class ProviderAccountBulkEditForm(NetBoxModelBulkEditForm):
|
|||
|
||||
model = ProviderAccount
|
||||
fieldsets = (
|
||||
(None, ('provider', 'description')),
|
||||
FieldSet('provider', 'description'),
|
||||
)
|
||||
nullable_fields = (
|
||||
'description', 'comments',
|
||||
|
@ -83,7 +84,7 @@ class ProviderNetworkBulkEditForm(NetBoxModelBulkEditForm):
|
|||
|
||||
model = ProviderNetwork
|
||||
fieldsets = (
|
||||
(None, ('provider', 'service_id', 'description')),
|
||||
FieldSet('provider', 'service_id', 'description'),
|
||||
)
|
||||
nullable_fields = (
|
||||
'service_id', 'description', 'comments',
|
||||
|
@ -103,7 +104,7 @@ class CircuitTypeBulkEditForm(NetBoxModelBulkEditForm):
|
|||
|
||||
model = CircuitType
|
||||
fieldsets = (
|
||||
(None, ('color', 'description')),
|
||||
FieldSet('color', 'description'),
|
||||
)
|
||||
nullable_fields = ('color', 'description')
|
||||
|
||||
|
@ -164,9 +165,9 @@ class CircuitBulkEditForm(NetBoxModelBulkEditForm):
|
|||
|
||||
model = Circuit
|
||||
fieldsets = (
|
||||
(_('Circuit'), ('provider', 'type', 'status', 'description')),
|
||||
(_('Service Parameters'), ('provider_account', 'install_date', 'termination_date', 'commit_rate')),
|
||||
(_('Tenancy'), ('tenant',)),
|
||||
FieldSet('provider', 'type', 'status', 'description', name=_('Circuit')),
|
||||
FieldSet('provider_account', 'install_date', 'termination_date', 'commit_rate', name=_('Service Parameters')),
|
||||
FieldSet('tenant', name=_('Tenancy')),
|
||||
)
|
||||
nullable_fields = (
|
||||
'tenant', 'commit_rate', 'description', 'comments',
|
||||
|
|
|
@ -8,6 +8,7 @@ from ipam.models import ASN
|
|||
from netbox.forms import NetBoxModelFilterSetForm
|
||||
from tenancy.forms import TenancyFilterForm, ContactModelFilterForm
|
||||
from utilities.forms.fields import ColorField, DynamicModelMultipleChoiceField, TagFilterField
|
||||
from utilities.forms.rendering import FieldSet
|
||||
from utilities.forms.widgets import DatePicker, NumberWithOptions
|
||||
|
||||
__all__ = (
|
||||
|
@ -22,10 +23,10 @@ __all__ = (
|
|||
class ProviderFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
|
||||
model = Provider
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id', 'tag')),
|
||||
(_('Location'), ('region_id', 'site_group_id', 'site_id')),
|
||||
(_('ASN'), ('asn',)),
|
||||
(_('Contacts'), ('contact', 'contact_role', 'contact_group')),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('region_id', 'site_group_id', 'site_id', name=_('Location')),
|
||||
FieldSet('asn', name=_('ASN')),
|
||||
FieldSet('contact', 'contact_role', 'contact_group', name=_('Contacts')),
|
||||
)
|
||||
region_id = DynamicModelMultipleChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
|
@ -61,8 +62,8 @@ class ProviderFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
|
|||
class ProviderAccountFilterForm(NetBoxModelFilterSetForm):
|
||||
model = ProviderAccount
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id', 'tag')),
|
||||
(_('Attributes'), ('provider_id', 'account')),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('provider_id', 'account', name=_('Attributes')),
|
||||
)
|
||||
provider_id = DynamicModelMultipleChoiceField(
|
||||
queryset=Provider.objects.all(),
|
||||
|
@ -79,8 +80,8 @@ class ProviderAccountFilterForm(NetBoxModelFilterSetForm):
|
|||
class ProviderNetworkFilterForm(NetBoxModelFilterSetForm):
|
||||
model = ProviderNetwork
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id', 'tag')),
|
||||
(_('Attributes'), ('provider_id', 'service_id')),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('provider_id', 'service_id', name=_('Attributes')),
|
||||
)
|
||||
provider_id = DynamicModelMultipleChoiceField(
|
||||
queryset=Provider.objects.all(),
|
||||
|
@ -98,8 +99,8 @@ class ProviderNetworkFilterForm(NetBoxModelFilterSetForm):
|
|||
class CircuitTypeFilterForm(NetBoxModelFilterSetForm):
|
||||
model = CircuitType
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id', 'tag')),
|
||||
(_('Attributes'), ('color',)),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('color', name=_('Attributes')),
|
||||
)
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
@ -112,12 +113,12 @@ class CircuitTypeFilterForm(NetBoxModelFilterSetForm):
|
|||
class CircuitFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilterSetForm):
|
||||
model = Circuit
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id', 'tag')),
|
||||
(_('Provider'), ('provider_id', 'provider_account_id', 'provider_network_id')),
|
||||
(_('Attributes'), ('type_id', 'status', 'install_date', 'termination_date', 'commit_rate')),
|
||||
(_('Location'), ('region_id', 'site_group_id', 'site_id')),
|
||||
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
|
||||
(_('Contacts'), ('contact', 'contact_role', 'contact_group')),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('provider_id', 'provider_account_id', 'provider_network_id', name=_('Provider')),
|
||||
FieldSet('type_id', 'status', 'install_date', 'termination_date', 'commit_rate', name=_('Attributes')),
|
||||
FieldSet('region_id', 'site_group_id', 'site_id', name=_('Location')),
|
||||
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||
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')
|
||||
type_id = DynamicModelMultipleChoiceField(
|
||||
|
|
|
@ -7,6 +7,7 @@ from ipam.models import ASN
|
|||
from netbox.forms import NetBoxModelForm
|
||||
from tenancy.forms import TenancyForm
|
||||
from utilities.forms.fields import CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, SlugField
|
||||
from utilities.forms.rendering import FieldSet, TabbedGroups
|
||||
from utilities.forms.widgets import DatePicker, NumberWithOptions
|
||||
|
||||
__all__ = (
|
||||
|
@ -29,7 +30,7 @@ class ProviderForm(NetBoxModelForm):
|
|||
comments = CommentField()
|
||||
|
||||
fieldsets = (
|
||||
(_('Provider'), ('name', 'slug', 'asns', 'description', 'tags')),
|
||||
FieldSet('name', 'slug', 'asns', 'description', 'tags'),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -61,7 +62,7 @@ class ProviderNetworkForm(NetBoxModelForm):
|
|||
comments = CommentField()
|
||||
|
||||
fieldsets = (
|
||||
(_('Provider Network'), ('provider', 'name', 'service_id', 'description', 'tags')),
|
||||
FieldSet('provider', 'name', 'service_id', 'description', 'tags'),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -75,9 +76,7 @@ class CircuitTypeForm(NetBoxModelForm):
|
|||
slug = SlugField()
|
||||
|
||||
fieldsets = (
|
||||
(_('Circuit Type'), (
|
||||
'name', 'slug', 'color', 'description', 'tags',
|
||||
)),
|
||||
FieldSet('name', 'slug', 'color', 'description', 'tags'),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -107,9 +106,9 @@ class CircuitForm(TenancyForm, NetBoxModelForm):
|
|||
comments = CommentField()
|
||||
|
||||
fieldsets = (
|
||||
(_('Circuit'), ('provider', 'provider_account', 'cid', 'type', 'status', 'description', 'tags')),
|
||||
(_('Service Parameters'), ('install_date', 'termination_date', 'commit_rate')),
|
||||
(_('Tenancy'), ('tenant_group', 'tenant')),
|
||||
FieldSet('provider', 'provider_account', 'cid', 'type', 'status', 'description', 'tags', name=_('Circuit')),
|
||||
FieldSet('install_date', 'termination_date', 'commit_rate', name=_('Service Parameters')),
|
||||
FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -146,6 +145,18 @@ class CircuitTerminationForm(NetBoxModelForm):
|
|||
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:
|
||||
model = CircuitTermination
|
||||
fields = [
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
import strawberry
|
||||
import strawberry_django
|
||||
from circuits import filtersets, models
|
||||
|
||||
from netbox.graphql.filter_mixins import autotype_decorator, BaseFilterMixin
|
||||
|
||||
__all__ = (
|
||||
'CircuitTerminationFilter',
|
||||
'CircuitFilter',
|
||||
'CircuitTypeFilter',
|
||||
'ProviderFilter',
|
||||
'ProviderAccountFilter',
|
||||
'ProviderNetworkFilter',
|
||||
)
|
||||
|
||||
|
||||
@strawberry_django.filter(models.CircuitTermination, lookups=True)
|
||||
@autotype_decorator(filtersets.CircuitTerminationFilterSet)
|
||||
class CircuitTerminationFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.Circuit, lookups=True)
|
||||
@autotype_decorator(filtersets.CircuitFilterSet)
|
||||
class CircuitFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.CircuitType, lookups=True)
|
||||
@autotype_decorator(filtersets.CircuitTypeFilterSet)
|
||||
class CircuitTypeFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.Provider, lookups=True)
|
||||
@autotype_decorator(filtersets.ProviderFilterSet)
|
||||
class ProviderFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.ProviderAccount, lookups=True)
|
||||
@autotype_decorator(filtersets.ProviderAccountFilterSet)
|
||||
class ProviderAccountFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.ProviderNetwork, lookups=True)
|
||||
@autotype_decorator(filtersets.ProviderNetworkFilterSet)
|
||||
class ProviderNetworkFilter(BaseFilterMixin):
|
||||
pass
|
|
@ -1,41 +1,40 @@
|
|||
import graphene
|
||||
from typing import List
|
||||
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
|
||||
from circuits import models
|
||||
from netbox.graphql.fields import ObjectField, ObjectListField
|
||||
from .types import *
|
||||
from utilities.graphql_optimizer import gql_query_optimizer
|
||||
|
||||
|
||||
class CircuitsQuery(graphene.ObjectType):
|
||||
circuit = ObjectField(CircuitType)
|
||||
circuit_list = ObjectListField(CircuitType)
|
||||
@strawberry.type
|
||||
class CircuitsQuery:
|
||||
@strawberry.field
|
||||
def circuit(self, id: int) -> CircuitType:
|
||||
return models.Circuit.objects.get(pk=id)
|
||||
circuit_list: List[CircuitType] = strawberry_django.field()
|
||||
|
||||
def resolve_circuit_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.Circuit.objects.all(), info)
|
||||
@strawberry.field
|
||||
def circuit_termination(self, id: int) -> CircuitTerminationType:
|
||||
return models.CircuitTermination.objects.get(pk=id)
|
||||
circuit_termination_list: List[CircuitTerminationType] = strawberry_django.field()
|
||||
|
||||
circuit_termination = ObjectField(CircuitTerminationType)
|
||||
circuit_termination_list = ObjectListField(CircuitTerminationType)
|
||||
@strawberry.field
|
||||
def circuit_type(self, id: int) -> CircuitTypeType:
|
||||
return models.CircuitType.objects.get(pk=id)
|
||||
circuit_type_list: List[CircuitTypeType] = strawberry_django.field()
|
||||
|
||||
def resolve_circuit_termination_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.CircuitTermination.objects.all(), info)
|
||||
@strawberry.field
|
||||
def provider(self, id: int) -> ProviderType:
|
||||
return models.Provider.objects.get(pk=id)
|
||||
provider_list: List[ProviderType] = strawberry_django.field()
|
||||
|
||||
circuit_type = ObjectField(CircuitTypeType)
|
||||
circuit_type_list = ObjectListField(CircuitTypeType)
|
||||
@strawberry.field
|
||||
def provider_account(self, id: int) -> ProviderAccountType:
|
||||
return models.ProviderAccount.objects.get(pk=id)
|
||||
provider_account_list: List[ProviderAccountType] = strawberry_django.field()
|
||||
|
||||
def resolve_circuit_type_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.CircuitType.objects.all(), info)
|
||||
|
||||
provider = ObjectField(ProviderType)
|
||||
provider_list = ObjectListField(ProviderType)
|
||||
|
||||
def resolve_provider_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.Provider.objects.all(), info)
|
||||
|
||||
provider_account = ObjectField(ProviderAccountType)
|
||||
provider_account_list = ObjectListField(ProviderAccountType)
|
||||
|
||||
provider_network = ObjectField(ProviderNetworkType)
|
||||
provider_network_list = ObjectListField(ProviderNetworkType)
|
||||
|
||||
def resolve_provider_network_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.ProviderNetwork.objects.all(), info)
|
||||
@strawberry.field
|
||||
def provider_network(self, id: int) -> ProviderNetworkType:
|
||||
return models.ProviderNetwork.objects.get(pk=id)
|
||||
provider_network_list: List[ProviderNetworkType] = strawberry_django.field()
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
import graphene
|
||||
from typing import Annotated, List
|
||||
|
||||
from circuits import filtersets, models
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
|
||||
from circuits import models
|
||||
from dcim.graphql.mixins import CabledObjectMixin
|
||||
from extras.graphql.mixins import CustomFieldsMixin, TagsMixin, ContactsMixin
|
||||
from netbox.graphql.types import ObjectType, OrganizationalObjectType, NetBoxObjectType
|
||||
from extras.graphql.mixins import ContactsMixin, CustomFieldsMixin, TagsMixin
|
||||
from netbox.graphql.types import NetBoxObjectType, ObjectType, OrganizationalObjectType
|
||||
from tenancy.graphql.types import TenantType
|
||||
from .filters import *
|
||||
|
||||
__all__ = (
|
||||
'CircuitTerminationType',
|
||||
|
@ -15,48 +20,93 @@ __all__ = (
|
|||
)
|
||||
|
||||
|
||||
class CircuitTerminationType(CustomFieldsMixin, TagsMixin, CabledObjectMixin, ObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.CircuitTermination
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.CircuitTerminationFilterSet
|
||||
|
||||
|
||||
class CircuitType(NetBoxObjectType, ContactsMixin):
|
||||
class Meta:
|
||||
model = models.Circuit
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.CircuitFilterSet
|
||||
|
||||
|
||||
class CircuitTypeType(OrganizationalObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.CircuitType
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.CircuitTypeFilterSet
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.Provider,
|
||||
fields='__all__',
|
||||
filters=ProviderFilter
|
||||
)
|
||||
class ProviderType(NetBoxObjectType, ContactsMixin):
|
||||
|
||||
class Meta:
|
||||
model = models.Provider
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.ProviderFilterSet
|
||||
@strawberry_django.field
|
||||
def networks(self) -> List[Annotated["ProviderNetworkType", strawberry.lazy('circuits.graphql.types')]]:
|
||||
return self.networks.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def circuits(self) -> List[Annotated["CircuitType", strawberry.lazy('circuits.graphql.types')]]:
|
||||
return self.circuits.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def asns(self) -> List[Annotated["ASNType", strawberry.lazy('ipam.graphql.types')]]:
|
||||
return self.asns.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def accounts(self) -> List[Annotated["ProviderAccountType", strawberry.lazy('circuits.graphql.types')]]:
|
||||
return self.accounts.all()
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.ProviderAccount,
|
||||
fields='__all__',
|
||||
filters=ProviderAccountFilter
|
||||
)
|
||||
class ProviderAccountType(NetBoxObjectType):
|
||||
provider: Annotated["ProviderType", strawberry.lazy('circuits.graphql.types')]
|
||||
|
||||
class Meta:
|
||||
model = models.ProviderAccount
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.ProviderAccountFilterSet
|
||||
@strawberry_django.field
|
||||
def circuits(self) -> List[Annotated["CircuitType", strawberry.lazy('circuits.graphql.types')]]:
|
||||
return self.circuits.all()
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.ProviderNetwork,
|
||||
fields='__all__',
|
||||
filters=ProviderNetworkFilter
|
||||
)
|
||||
class ProviderNetworkType(NetBoxObjectType):
|
||||
provider: Annotated["ProviderType", strawberry.lazy('circuits.graphql.types')]
|
||||
|
||||
class Meta:
|
||||
model = models.ProviderNetwork
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.ProviderNetworkFilterSet
|
||||
@strawberry_django.field
|
||||
def circuit_terminations(self) -> List[Annotated["CircuitTerminationType", strawberry.lazy('circuits.graphql.types')]]:
|
||||
return self.circuit_terminations.all()
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.CircuitTermination,
|
||||
fields='__all__',
|
||||
filters=CircuitTerminationFilter
|
||||
)
|
||||
class CircuitTerminationType(CustomFieldsMixin, TagsMixin, CabledObjectMixin, ObjectType):
|
||||
circuit: Annotated["CircuitType", strawberry.lazy('circuits.graphql.types')]
|
||||
provider_network: Annotated["ProviderNetworkType", strawberry.lazy('circuits.graphql.types')] | None
|
||||
site: Annotated["SiteType", strawberry.lazy('dcim.graphql.types')] | None
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.CircuitType,
|
||||
fields='__all__',
|
||||
filters=CircuitTypeFilter
|
||||
)
|
||||
class CircuitTypeType(OrganizationalObjectType):
|
||||
color: str
|
||||
|
||||
@strawberry_django.field
|
||||
def circuits(self) -> List[Annotated["CircuitType", strawberry.lazy('circuits.graphql.types')]]:
|
||||
return self.circuits.all()
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.Circuit,
|
||||
fields='__all__',
|
||||
filters=CircuitFilter
|
||||
)
|
||||
class CircuitType(NetBoxObjectType, ContactsMixin):
|
||||
provider: ProviderType
|
||||
provider_account: ProviderAccountType | None
|
||||
termination_a: CircuitTerminationType | None
|
||||
termination_z: CircuitTerminationType | None
|
||||
type: CircuitTypeType
|
||||
tenant: TenantType | None
|
||||
|
||||
@strawberry_django.field
|
||||
def terminations(self) -> List[CircuitTerminationType]:
|
||||
return self.terminations.all()
|
||||
|
|
|
@ -6,7 +6,7 @@ from dcim.views import PathTraceView
|
|||
from netbox.views import generic
|
||||
from tenancy.views import ObjectContactsView
|
||||
from utilities.forms import ConfirmationForm
|
||||
from utilities.utils import count_related
|
||||
from utilities.query import count_related
|
||||
from utilities.views import register_model_view
|
||||
from . import filtersets, forms, tables
|
||||
from .models import *
|
||||
|
@ -412,7 +412,6 @@ class CircuitContactsView(ObjectContactsView):
|
|||
class CircuitTerminationEditView(generic.ObjectEditView):
|
||||
queryset = CircuitTermination.objects.all()
|
||||
form = forms.CircuitTerminationForm
|
||||
template_name = 'circuits/circuittermination_edit.html'
|
||||
|
||||
|
||||
@register_model_view(CircuitTermination, 'delete')
|
||||
|
|
|
@ -5,6 +5,7 @@ from core.models import *
|
|||
from netbox.forms import NetBoxModelBulkEditForm
|
||||
from netbox.utils import get_data_backend_choices
|
||||
from utilities.forms.fields import CommentField
|
||||
from utilities.forms.rendering import FieldSet
|
||||
from utilities.forms.widgets import BulkEditNullBooleanSelect
|
||||
|
||||
__all__ = (
|
||||
|
@ -41,7 +42,7 @@ class DataSourceBulkEditForm(NetBoxModelBulkEditForm):
|
|||
|
||||
model = DataSource
|
||||
fieldsets = (
|
||||
(None, ('type', 'enabled', 'description', 'comments', 'parameters', 'ignore_rules')),
|
||||
FieldSet('type', 'enabled', 'description', 'comments', 'parameters', 'ignore_rules'),
|
||||
)
|
||||
nullable_fields = (
|
||||
'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 utilities.forms import BOOLEAN_WITH_BLANK_CHOICES, FilterForm
|
||||
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__ = (
|
||||
'ConfigRevisionFilterForm',
|
||||
|
@ -22,8 +23,8 @@ __all__ = (
|
|||
class DataSourceFilterForm(NetBoxModelFilterSetForm):
|
||||
model = DataSource
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id')),
|
||||
(_('Data Source'), ('type', 'status')),
|
||||
FieldSet('q', 'filter_id'),
|
||||
FieldSet('type', 'status', name=_('Data Source')),
|
||||
)
|
||||
type = forms.MultipleChoiceField(
|
||||
label=_('Type'),
|
||||
|
@ -47,8 +48,8 @@ class DataSourceFilterForm(NetBoxModelFilterSetForm):
|
|||
class DataFileFilterForm(NetBoxModelFilterSetForm):
|
||||
model = DataFile
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id')),
|
||||
(_('File'), ('source_id',)),
|
||||
FieldSet('q', 'filter_id'),
|
||||
FieldSet('source_id', name=_('File')),
|
||||
)
|
||||
source_id = DynamicModelMultipleChoiceField(
|
||||
queryset=DataSource.objects.all(),
|
||||
|
@ -59,12 +60,12 @@ class DataFileFilterForm(NetBoxModelFilterSetForm):
|
|||
|
||||
class JobFilterForm(SavedFiltersMixin, FilterForm):
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id')),
|
||||
(_('Attributes'), ('object_type', 'status')),
|
||||
(_('Creation'), (
|
||||
FieldSet('q', 'filter_id'),
|
||||
FieldSet('object_type', 'status', name=_('Attributes')),
|
||||
FieldSet(
|
||||
'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(
|
||||
label=_('Object Type'),
|
||||
|
@ -125,5 +126,5 @@ class JobFilterForm(SavedFiltersMixin, FilterForm):
|
|||
|
||||
class ConfigRevisionFilterForm(SavedFiltersMixin, FilterForm):
|
||||
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 utilities.forms import get_field_value
|
||||
from utilities.forms.fields import CommentField
|
||||
from utilities.forms.rendering import FieldSet
|
||||
from utilities.forms.widgets import HTMXSelect
|
||||
|
||||
__all__ = (
|
||||
|
@ -49,11 +50,11 @@ class DataSourceForm(NetBoxModelForm):
|
|||
@property
|
||||
def fieldsets(self):
|
||||
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:
|
||||
fieldsets.append(
|
||||
(_('Backend Parameters'), self.backend_fields)
|
||||
FieldSet(*self.backend_fields, name=_('Backend Parameters'))
|
||||
)
|
||||
|
||||
return fieldsets
|
||||
|
@ -91,8 +92,8 @@ class ManagedFileForm(SyncedDataMixin, NetBoxModelForm):
|
|||
)
|
||||
|
||||
fieldsets = (
|
||||
(_('File Upload'), ('upload_file',)),
|
||||
(_('Data Source'), ('data_source', 'data_file', 'auto_sync_enabled')),
|
||||
FieldSet('upload_file', name=_('File Upload')),
|
||||
FieldSet('data_source', 'data_file', 'auto_sync_enabled', name=_('Data Source')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -144,18 +145,24 @@ class ConfigRevisionForm(forms.ModelForm, metaclass=ConfigFormMetaclass):
|
|||
"""
|
||||
|
||||
fieldsets = (
|
||||
(_('Rack Elevations'), ('RACK_ELEVATION_DEFAULT_UNIT_HEIGHT', 'RACK_ELEVATION_DEFAULT_UNIT_WIDTH')),
|
||||
(_('Power'), ('POWERFEED_DEFAULT_VOLTAGE', 'POWERFEED_DEFAULT_AMPERAGE', 'POWERFEED_DEFAULT_MAX_UTILIZATION')),
|
||||
(_('IPAM'), ('ENFORCE_GLOBAL_UNIQUE', 'PREFER_IPV4')),
|
||||
(_('Security'), ('ALLOWED_URL_SCHEMES',)),
|
||||
(_('Banners'), ('BANNER_LOGIN', 'BANNER_MAINTENANCE', 'BANNER_TOP', 'BANNER_BOTTOM')),
|
||||
(_('Pagination'), ('PAGINATE_COUNT', 'MAX_PAGE_SIZE')),
|
||||
(_('Validation'), ('CUSTOM_VALIDATORS', 'PROTECTION_RULES')),
|
||||
(_('User Preferences'), ('DEFAULT_USER_PREFERENCES',)),
|
||||
(_('Miscellaneous'), (
|
||||
FieldSet(
|
||||
'RACK_ELEVATION_DEFAULT_UNIT_HEIGHT', 'RACK_ELEVATION_DEFAULT_UNIT_WIDTH', name=_('Rack Elevations')
|
||||
),
|
||||
FieldSet(
|
||||
'POWERFEED_DEFAULT_VOLTAGE', 'POWERFEED_DEFAULT_AMPERAGE', 'POWERFEED_DEFAULT_MAX_UTILIZATION',
|
||||
name=_('Power')
|
||||
),
|
||||
FieldSet('ENFORCE_GLOBAL_UNIQUE', 'PREFER_IPV4', name=_('IPAM')),
|
||||
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',
|
||||
)),
|
||||
(_('Config Revision'), ('comment',))
|
||||
name=_('Miscellaneous')
|
||||
),
|
||||
FieldSet('comment', name=_('Config Revision'))
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
import strawberry_django
|
||||
|
||||
from core import filtersets, models
|
||||
from netbox.graphql.filter_mixins import autotype_decorator, BaseFilterMixin
|
||||
|
||||
__all__ = (
|
||||
'DataFileFilter',
|
||||
'DataSourceFilter',
|
||||
)
|
||||
|
||||
|
||||
@strawberry_django.filter(models.DataFile, lookups=True)
|
||||
@autotype_decorator(filtersets.DataFileFilterSet)
|
||||
class DataFileFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.DataSource, lookups=True)
|
||||
@autotype_decorator(filtersets.DataSourceFilterSet)
|
||||
class DataSourceFilter(BaseFilterMixin):
|
||||
pass
|
|
@ -1,20 +1,20 @@
|
|||
import graphene
|
||||
from typing import List
|
||||
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
|
||||
from core import models
|
||||
from netbox.graphql.fields import ObjectField, ObjectListField
|
||||
from .types import *
|
||||
from utilities.graphql_optimizer import gql_query_optimizer
|
||||
|
||||
|
||||
class CoreQuery(graphene.ObjectType):
|
||||
data_file = ObjectField(DataFileType)
|
||||
data_file_list = ObjectListField(DataFileType)
|
||||
@strawberry.type
|
||||
class CoreQuery:
|
||||
@strawberry.field
|
||||
def data_file(self, id: int) -> DataFileType:
|
||||
return models.DataFile.objects.get(pk=id)
|
||||
data_file_list: List[DataFileType] = strawberry_django.field()
|
||||
|
||||
def resolve_data_file_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.DataFile.objects.all(), info)
|
||||
|
||||
data_source = ObjectField(DataSourceType)
|
||||
data_source_list = ObjectListField(DataSourceType)
|
||||
|
||||
def resolve_data_source_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.DataSource.objects.all(), info)
|
||||
@strawberry.field
|
||||
def data_source(self, id: int) -> DataSourceType:
|
||||
return models.DataSource.objects.get(pk=id)
|
||||
data_source_list: List[DataSourceType] = strawberry_django.field()
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
from core import filtersets, models
|
||||
from typing import Annotated, List
|
||||
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
|
||||
from core import models
|
||||
from netbox.graphql.types import BaseObjectType, NetBoxObjectType
|
||||
from .filters import *
|
||||
|
||||
__all__ = (
|
||||
'DataFileType',
|
||||
|
@ -7,15 +13,22 @@ __all__ = (
|
|||
)
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.DataFile,
|
||||
exclude=['data',],
|
||||
filters=DataFileFilter
|
||||
)
|
||||
class DataFileType(BaseObjectType):
|
||||
class Meta:
|
||||
model = models.DataFile
|
||||
exclude = ('data',)
|
||||
filterset_class = filtersets.DataFileFilterSet
|
||||
source: Annotated["DataSourceType", strawberry.lazy('core.graphql.types')]
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.DataSource,
|
||||
fields='__all__',
|
||||
filters=DataSourceFilter
|
||||
)
|
||||
class DataSourceType(NetBoxObjectType):
|
||||
class Meta:
|
||||
model = models.DataSource
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.DataSourceFilterSet
|
||||
|
||||
@strawberry_django.field
|
||||
def datafiles(self) -> List[Annotated["DataFileType", strawberry.lazy('core.graphql.types')]]:
|
||||
return self.datafiles.all()
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import hashlib
|
||||
import logging
|
||||
import os
|
||||
import yaml
|
||||
|
@ -18,7 +19,6 @@ from netbox.constants import CENSOR_TOKEN, CENSOR_TOKEN_CHANGED
|
|||
from netbox.models import PrimaryModel
|
||||
from netbox.models.features import JobsMixin
|
||||
from netbox.registry import registry
|
||||
from utilities.files import sha256_hash
|
||||
from utilities.querysets import RestrictedQuerySet
|
||||
from ..choices import *
|
||||
from ..exceptions import SyncError
|
||||
|
@ -357,7 +357,8 @@ class DataFile(models.Model):
|
|||
has changed.
|
||||
"""
|
||||
file_path = os.path.join(source_root, self.path)
|
||||
file_hash = sha256_hash(file_path).hexdigest()
|
||||
with open(file_path, 'rb') as f:
|
||||
file_hash = hashlib.sha256(f.read()).hexdigest()
|
||||
|
||||
# Update instance file attributes & data
|
||||
if is_modified := file_hash != self.hash:
|
||||
|
|
|
@ -26,7 +26,7 @@ from netbox.views.generic.base import BaseObjectView
|
|||
from netbox.views.generic.mixins import TableMixin
|
||||
from utilities.forms import ConfirmationForm
|
||||
from utilities.htmx import render_partial
|
||||
from utilities.utils import count_related
|
||||
from utilities.query import count_related
|
||||
from utilities.views import ContentTypePermissionRequiredMixin, register_model_view
|
||||
from . import filtersets, forms, tables
|
||||
from .models import *
|
||||
|
|
|
@ -92,7 +92,7 @@ class LocationSerializer(NestedGroupModelSerializer):
|
|||
class Meta:
|
||||
model = Location
|
||||
fields = [
|
||||
'id', 'url', 'display', 'name', 'slug', 'site', 'parent', 'status', 'tenant', 'description', 'tags',
|
||||
'custom_fields', 'created', 'last_updated', 'rack_count', 'device_count', '_depth',
|
||||
'id', 'url', 'display', 'name', 'slug', 'site', 'parent', 'status', 'tenant', 'facility', 'description',
|
||||
'tags', 'custom_fields', 'created', 'last_updated', 'rack_count', 'device_count', '_depth',
|
||||
]
|
||||
brief_fields = ('id', 'url', 'display', 'name', 'slug', 'description', 'rack_count', '_depth')
|
||||
|
|
|
@ -10,12 +10,12 @@ from extras.filtersets import LocalConfigContextFilterSet
|
|||
from extras.models import ConfigTemplate
|
||||
from ipam.filtersets import PrimaryIPFilterSet
|
||||
from ipam.models import ASN, IPAddress, VRF
|
||||
from netbox.choices import ColorChoices
|
||||
from netbox.filtersets import (
|
||||
BaseFilterSet, ChangeLoggedModelFilterSet, OrganizationalModelFilterSet, NetBoxModelFilterSet,
|
||||
)
|
||||
from tenancy.filtersets import TenancyFilterSet, ContactModelFilterSet
|
||||
from tenancy.models import *
|
||||
from utilities.choices import ColorChoices
|
||||
from utilities.filters import (
|
||||
ContentTypeFilter, MultiValueCharFilter, MultiValueMACAddressFilter, MultiValueNumberFilter, MultiValueWWNFilter,
|
||||
NumericArrayFilter, TreeNodeMultipleChoiceFilter,
|
||||
|
@ -270,13 +270,14 @@ class LocationFilterSet(TenancyFilterSet, ContactModelFilterSet, OrganizationalM
|
|||
|
||||
class Meta:
|
||||
model = Location
|
||||
fields = ('id', 'name', 'slug', 'status', 'description')
|
||||
fields = ('id', 'name', 'slug', 'status', 'facility', 'description')
|
||||
|
||||
def search(self, queryset, name, value):
|
||||
if not value.strip():
|
||||
return queryset
|
||||
return queryset.filter(
|
||||
Q(name__icontains=value) |
|
||||
Q(facility__icontains=value) |
|
||||
Q(description__icontains=value)
|
||||
)
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ from netbox.forms import NetBoxModelBulkEditForm
|
|||
from tenancy.models import Tenant
|
||||
from utilities.forms import BulkEditForm, add_blank_choice, form_from_model
|
||||
from utilities.forms.fields import ColorField, CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField
|
||||
from utilities.forms.rendering import FieldSet
|
||||
from utilities.forms.widgets import BulkEditNullBooleanSelect, NumberWithOptions
|
||||
from wireless.models import WirelessLAN, WirelessLANGroup
|
||||
from wireless.choices import WirelessRoleChoices
|
||||
|
@ -75,7 +76,7 @@ class RegionBulkEditForm(NetBoxModelBulkEditForm):
|
|||
|
||||
model = Region
|
||||
fieldsets = (
|
||||
(None, ('parent', 'description')),
|
||||
FieldSet('parent', 'description'),
|
||||
)
|
||||
nullable_fields = ('parent', 'description')
|
||||
|
||||
|
@ -94,7 +95,7 @@ class SiteGroupBulkEditForm(NetBoxModelBulkEditForm):
|
|||
|
||||
model = SiteGroup
|
||||
fieldsets = (
|
||||
(None, ('parent', 'description')),
|
||||
FieldSet('parent', 'description'),
|
||||
)
|
||||
nullable_fields = ('parent', 'description')
|
||||
|
||||
|
@ -154,7 +155,7 @@ class SiteBulkEditForm(NetBoxModelBulkEditForm):
|
|||
|
||||
model = Site
|
||||
fieldsets = (
|
||||
(None, ('status', 'region', 'group', 'tenant', 'asns', 'time_zone', 'description')),
|
||||
FieldSet('status', 'region', 'group', 'tenant', 'asns', 'time_zone', 'description'),
|
||||
)
|
||||
nullable_fields = (
|
||||
'region', 'group', 'tenant', 'asns', 'time_zone', 'description', 'comments',
|
||||
|
@ -194,7 +195,7 @@ class LocationBulkEditForm(NetBoxModelBulkEditForm):
|
|||
|
||||
model = Location
|
||||
fieldsets = (
|
||||
(None, ('site', 'parent', 'status', 'tenant', 'description')),
|
||||
FieldSet('site', 'parent', 'status', 'tenant', 'description'),
|
||||
)
|
||||
nullable_fields = ('parent', 'tenant', 'description')
|
||||
|
||||
|
@ -212,7 +213,7 @@ class RackRoleBulkEditForm(NetBoxModelBulkEditForm):
|
|||
|
||||
model = RackRole
|
||||
fieldsets = (
|
||||
(None, ('color', 'description')),
|
||||
FieldSet('color', 'description'),
|
||||
)
|
||||
nullable_fields = ('color', 'description')
|
||||
|
||||
|
@ -341,12 +342,13 @@ class RackBulkEditForm(NetBoxModelBulkEditForm):
|
|||
|
||||
model = Rack
|
||||
fieldsets = (
|
||||
(_('Rack'), ('status', 'role', 'tenant', 'serial', 'asset_tag', 'description')),
|
||||
(_('Location'), ('region', 'site_group', 'site', 'location')),
|
||||
(_('Hardware'), (
|
||||
FieldSet('status', 'role', 'tenant', 'serial', 'asset_tag', 'description', name=_('Rack')),
|
||||
FieldSet('region', 'site_group', 'site', 'location', name=_('Location')),
|
||||
FieldSet(
|
||||
'type', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit', 'mounting_depth',
|
||||
)),
|
||||
(_('Weight'), ('weight', 'max_weight', 'weight_unit')),
|
||||
name=_('Hardware')
|
||||
),
|
||||
FieldSet('weight', 'max_weight', 'weight_unit', name=_('Weight')),
|
||||
)
|
||||
nullable_fields = (
|
||||
'location', 'tenant', 'role', 'serial', 'asset_tag', 'outer_width', 'outer_depth', 'outer_unit', 'weight',
|
||||
|
@ -376,7 +378,7 @@ class RackReservationBulkEditForm(NetBoxModelBulkEditForm):
|
|||
|
||||
model = RackReservation
|
||||
fieldsets = (
|
||||
(None, ('user', 'tenant', 'description')),
|
||||
FieldSet('user', 'tenant', 'description'),
|
||||
)
|
||||
nullable_fields = ('comments',)
|
||||
|
||||
|
@ -390,7 +392,7 @@ class ManufacturerBulkEditForm(NetBoxModelBulkEditForm):
|
|||
|
||||
model = Manufacturer
|
||||
fieldsets = (
|
||||
(None, ('description',)),
|
||||
FieldSet('description'),
|
||||
)
|
||||
nullable_fields = ('description',)
|
||||
|
||||
|
@ -450,11 +452,11 @@ class DeviceTypeBulkEditForm(NetBoxModelBulkEditForm):
|
|||
|
||||
model = DeviceType
|
||||
fieldsets = (
|
||||
(_('Device Type'), (
|
||||
FieldSet(
|
||||
'manufacturer', 'default_platform', 'part_number', 'u_height', 'exclude_from_utilization', 'is_full_depth',
|
||||
'airflow', 'description',
|
||||
)),
|
||||
(_('Weight'), ('weight', 'weight_unit')),
|
||||
'airflow', 'description', name=_('Device Type')
|
||||
),
|
||||
FieldSet('weight', 'weight_unit', name=_('Weight')),
|
||||
)
|
||||
nullable_fields = ('part_number', 'airflow', 'weight', 'weight_unit', 'description', 'comments')
|
||||
|
||||
|
@ -489,8 +491,8 @@ class ModuleTypeBulkEditForm(NetBoxModelBulkEditForm):
|
|||
|
||||
model = ModuleType
|
||||
fieldsets = (
|
||||
(_('Module Type'), ('manufacturer', 'part_number', 'description')),
|
||||
(_('Weight'), ('weight', 'weight_unit')),
|
||||
FieldSet('manufacturer', 'part_number', 'description', name=_('Module Type')),
|
||||
FieldSet('weight', 'weight_unit', name=_('Weight')),
|
||||
)
|
||||
nullable_fields = ('part_number', 'weight', 'weight_unit', 'description', 'comments')
|
||||
|
||||
|
@ -518,7 +520,7 @@ class DeviceRoleBulkEditForm(NetBoxModelBulkEditForm):
|
|||
|
||||
model = DeviceRole
|
||||
fieldsets = (
|
||||
(None, ('color', 'vm_role', 'config_template', 'description')),
|
||||
FieldSet('color', 'vm_role', 'config_template', 'description'),
|
||||
)
|
||||
nullable_fields = ('color', 'config_template', 'description')
|
||||
|
||||
|
@ -542,7 +544,7 @@ class PlatformBulkEditForm(NetBoxModelBulkEditForm):
|
|||
|
||||
model = Platform
|
||||
fieldsets = (
|
||||
(None, ('manufacturer', 'config_template', 'description')),
|
||||
FieldSet('manufacturer', 'config_template', 'description'),
|
||||
)
|
||||
nullable_fields = ('manufacturer', 'config_template', 'description')
|
||||
|
||||
|
@ -621,10 +623,10 @@ class DeviceBulkEditForm(NetBoxModelBulkEditForm):
|
|||
|
||||
model = Device
|
||||
fieldsets = (
|
||||
(_('Device'), ('role', 'status', 'tenant', 'platform', 'description')),
|
||||
(_('Location'), ('site', 'location')),
|
||||
(_('Hardware'), ('manufacturer', 'device_type', 'airflow', 'serial')),
|
||||
(_('Configuration'), ('config_template',)),
|
||||
FieldSet('role', 'status', 'tenant', 'platform', 'description', name=_('Device')),
|
||||
FieldSet('site', 'location', name=_('Location')),
|
||||
FieldSet('manufacturer', 'device_type', 'airflow', 'serial', name=_('Hardware')),
|
||||
FieldSet('config_template', name=_('Configuration')),
|
||||
)
|
||||
nullable_fields = (
|
||||
'location', 'tenant', 'platform', 'serial', 'airflow', 'description', 'comments',
|
||||
|
@ -668,7 +670,7 @@ class ModuleBulkEditForm(NetBoxModelBulkEditForm):
|
|||
|
||||
model = Module
|
||||
fieldsets = (
|
||||
(None, ('manufacturer', 'module_type', 'status', 'serial', 'description')),
|
||||
FieldSet('manufacturer', 'module_type', 'status', 'serial', 'description'),
|
||||
)
|
||||
nullable_fields = ('serial', 'description', 'comments')
|
||||
|
||||
|
@ -720,8 +722,8 @@ class CableBulkEditForm(NetBoxModelBulkEditForm):
|
|||
|
||||
model = Cable
|
||||
fieldsets = (
|
||||
(None, ('type', 'status', 'tenant', 'label', 'description')),
|
||||
(_('Attributes'), ('color', 'length', 'length_unit')),
|
||||
FieldSet('type', 'status', 'tenant', 'label', 'description'),
|
||||
FieldSet('color', 'length', 'length_unit', name=_('Attributes')),
|
||||
)
|
||||
nullable_fields = (
|
||||
'type', 'status', 'tenant', 'label', 'color', 'length', 'description', 'comments',
|
||||
|
@ -743,7 +745,7 @@ class VirtualChassisBulkEditForm(NetBoxModelBulkEditForm):
|
|||
|
||||
model = VirtualChassis
|
||||
fieldsets = (
|
||||
(None, ('domain', 'description')),
|
||||
FieldSet('domain', 'description'),
|
||||
)
|
||||
nullable_fields = ('domain', 'description', 'comments')
|
||||
|
||||
|
@ -791,7 +793,7 @@ class PowerPanelBulkEditForm(NetBoxModelBulkEditForm):
|
|||
|
||||
model = PowerPanel
|
||||
fieldsets = (
|
||||
(None, ('region', 'site_group', 'site', 'location', 'description')),
|
||||
FieldSet('region', 'site_group', 'site', 'location', 'description'),
|
||||
)
|
||||
nullable_fields = ('location', 'description', 'comments')
|
||||
|
||||
|
@ -861,8 +863,8 @@ class PowerFeedBulkEditForm(NetBoxModelBulkEditForm):
|
|||
|
||||
model = PowerFeed
|
||||
fieldsets = (
|
||||
(None, ('power_panel', 'rack', 'status', 'type', 'mark_connected', 'description', 'tenant')),
|
||||
(_('Power'), ('supply', 'phase', 'voltage', 'amperage', 'max_utilization'))
|
||||
FieldSet('power_panel', 'rack', 'status', 'type', 'mark_connected', 'description', 'tenant'),
|
||||
FieldSet('supply', 'phase', 'voltage', 'amperage', 'max_utilization', name=_('Power'))
|
||||
)
|
||||
nullable_fields = ('location', 'tenant', 'description', 'comments')
|
||||
|
||||
|
@ -1210,7 +1212,7 @@ class ConsolePortBulkEditForm(
|
|||
|
||||
model = ConsolePort
|
||||
fieldsets = (
|
||||
(None, ('module', 'type', 'label', 'speed', 'description', 'mark_connected')),
|
||||
FieldSet('module', 'type', 'label', 'speed', 'description', 'mark_connected'),
|
||||
)
|
||||
nullable_fields = ('module', 'label', 'description')
|
||||
|
||||
|
@ -1227,7 +1229,7 @@ class ConsoleServerPortBulkEditForm(
|
|||
|
||||
model = ConsoleServerPort
|
||||
fieldsets = (
|
||||
(None, ('module', 'type', 'label', 'speed', 'description', 'mark_connected')),
|
||||
FieldSet('module', 'type', 'label', 'speed', 'description', 'mark_connected'),
|
||||
)
|
||||
nullable_fields = ('module', 'label', 'description')
|
||||
|
||||
|
@ -1244,8 +1246,8 @@ class PowerPortBulkEditForm(
|
|||
|
||||
model = PowerPort
|
||||
fieldsets = (
|
||||
(None, ('module', 'type', 'label', 'description', 'mark_connected')),
|
||||
(_('Power'), ('maximum_draw', 'allocated_draw')),
|
||||
FieldSet('module', 'type', 'label', 'description', 'mark_connected'),
|
||||
FieldSet('maximum_draw', 'allocated_draw', name=_('Power')),
|
||||
)
|
||||
nullable_fields = ('module', 'label', 'description', 'maximum_draw', 'allocated_draw')
|
||||
|
||||
|
@ -1262,8 +1264,8 @@ class PowerOutletBulkEditForm(
|
|||
|
||||
model = PowerOutlet
|
||||
fieldsets = (
|
||||
(None, ('module', 'type', 'label', 'description', 'mark_connected')),
|
||||
(_('Power'), ('feed_leg', 'power_port')),
|
||||
FieldSet('module', 'type', 'label', 'description', 'mark_connected'),
|
||||
FieldSet('feed_leg', 'power_port', name=_('Power')),
|
||||
)
|
||||
nullable_fields = ('module', 'label', 'type', 'feed_leg', 'power_port', 'description')
|
||||
|
||||
|
@ -1395,20 +1397,21 @@ class InterfaceBulkEditForm(
|
|||
|
||||
model = Interface
|
||||
fieldsets = (
|
||||
(None, ('module', 'type', 'label', 'speed', 'duplex', 'description')),
|
||||
(_('Addressing'), ('vrf', 'mac_address', 'wwn')),
|
||||
(_('Operation'), ('vdcs', 'mtu', 'tx_power', 'enabled', 'mgmt_only', 'mark_connected')),
|
||||
(_('PoE'), ('poe_mode', 'poe_type')),
|
||||
(_('Related Interfaces'), ('parent', 'bridge', 'lag')),
|
||||
(_('802.1Q Switching'), ('mode', 'vlan_group', 'untagged_vlan', 'tagged_vlans')),
|
||||
(_('Wireless'), (
|
||||
FieldSet('module', 'type', 'label', 'speed', 'duplex', 'description'),
|
||||
FieldSet('vrf', 'mac_address', 'wwn', name=_('Addressing')),
|
||||
FieldSet('vdcs', 'mtu', 'tx_power', 'enabled', 'mgmt_only', 'mark_connected', name=_('Operation')),
|
||||
FieldSet('poe_mode', 'poe_type', name=_('PoE')),
|
||||
FieldSet('parent', 'bridge', 'lag', name=_('Related Interfaces')),
|
||||
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',
|
||||
)),
|
||||
name=_('Wireless')
|
||||
),
|
||||
)
|
||||
nullable_fields = (
|
||||
'module', 'label', 'parent', 'bridge', 'lag', 'speed', 'duplex', 'mac_address', 'wwn', 'vdcs', 'mtu', 'description',
|
||||
'poe_mode', 'poe_type', 'mode', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'untagged_vlan',
|
||||
'tagged_vlans', 'vrf', 'wireless_lans'
|
||||
'module', 'label', 'parent', 'bridge', 'lag', 'speed', 'duplex', 'mac_address', 'wwn', 'vdcs', 'mtu',
|
||||
'description', 'poe_mode', 'poe_type', 'mode', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width',
|
||||
'tx_power', 'untagged_vlan', 'tagged_vlans', 'vrf', 'wireless_lans'
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
@ -1488,7 +1491,7 @@ class FrontPortBulkEditForm(
|
|||
|
||||
model = FrontPort
|
||||
fieldsets = (
|
||||
(None, ('module', 'type', 'label', 'color', 'description', 'mark_connected')),
|
||||
FieldSet('module', 'type', 'label', 'color', 'description', 'mark_connected'),
|
||||
)
|
||||
nullable_fields = ('module', 'label', 'description', 'color')
|
||||
|
||||
|
@ -1505,7 +1508,7 @@ class RearPortBulkEditForm(
|
|||
|
||||
model = RearPort
|
||||
fieldsets = (
|
||||
(None, ('module', 'type', 'label', 'color', 'description', 'mark_connected')),
|
||||
FieldSet('module', 'type', 'label', 'color', 'description', 'mark_connected'),
|
||||
)
|
||||
nullable_fields = ('module', 'label', 'description', 'color')
|
||||
|
||||
|
@ -1516,7 +1519,7 @@ class ModuleBayBulkEditForm(
|
|||
):
|
||||
model = ModuleBay
|
||||
fieldsets = (
|
||||
(None, ('label', 'position', 'description')),
|
||||
FieldSet('label', 'position', 'description'),
|
||||
)
|
||||
nullable_fields = ('label', 'position', 'description')
|
||||
|
||||
|
@ -1527,7 +1530,7 @@ class DeviceBayBulkEditForm(
|
|||
):
|
||||
model = DeviceBay
|
||||
fieldsets = (
|
||||
(None, ('label', 'description')),
|
||||
FieldSet('label', 'description'),
|
||||
)
|
||||
nullable_fields = ('label', 'description')
|
||||
|
||||
|
@ -1554,7 +1557,7 @@ class InventoryItemBulkEditForm(
|
|||
|
||||
model = InventoryItem
|
||||
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')
|
||||
|
||||
|
@ -1576,7 +1579,7 @@ class InventoryItemRoleBulkEditForm(NetBoxModelBulkEditForm):
|
|||
|
||||
model = InventoryItemRole
|
||||
fieldsets = (
|
||||
(None, ('color', 'description')),
|
||||
FieldSet('color', 'description'),
|
||||
)
|
||||
nullable_fields = ('color', 'description')
|
||||
|
||||
|
@ -1599,6 +1602,6 @@ class VirtualDeviceContextBulkEditForm(NetBoxModelBulkEditForm):
|
|||
)
|
||||
model = VirtualDeviceContext
|
||||
fieldsets = (
|
||||
(None, ('device', 'status', 'tenant')),
|
||||
FieldSet('device', 'status', 'tenant'),
|
||||
)
|
||||
nullable_fields = ('device', 'tenant', )
|
||||
|
|
|
@ -157,7 +157,7 @@ class LocationImportForm(NetBoxModelImportForm):
|
|||
|
||||
class Meta:
|
||||
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):
|
||||
super().__init__(data, *args, **kwargs)
|
||||
|
|
|
@ -12,7 +12,8 @@ from netbox.forms import NetBoxModelFilterSetForm
|
|||
from tenancy.forms import ContactModelFilterForm, TenancyFilterForm
|
||||
from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES, FilterForm, add_blank_choice
|
||||
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 wireless.choices import *
|
||||
|
||||
|
@ -132,8 +133,8 @@ class DeviceComponentFilterForm(NetBoxModelFilterSetForm):
|
|||
class RegionFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
|
||||
model = Region
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id', 'tag', 'parent_id')),
|
||||
(_('Contacts'), ('contact', 'contact_role', 'contact_group'))
|
||||
FieldSet('q', 'filter_id', 'tag', 'parent_id'),
|
||||
FieldSet('contact', 'contact_role', 'contact_group', name=_('Contacts'))
|
||||
)
|
||||
parent_id = DynamicModelMultipleChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
|
@ -146,8 +147,8 @@ class RegionFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
|
|||
class SiteGroupFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
|
||||
model = SiteGroup
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id', 'tag', 'parent_id')),
|
||||
(_('Contacts'), ('contact', 'contact_role', 'contact_group'))
|
||||
FieldSet('q', 'filter_id', 'tag', 'parent_id'),
|
||||
FieldSet('contact', 'contact_role', 'contact_group', name=_('Contacts'))
|
||||
)
|
||||
parent_id = DynamicModelMultipleChoiceField(
|
||||
queryset=SiteGroup.objects.all(),
|
||||
|
@ -160,10 +161,10 @@ class SiteGroupFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
|
|||
class SiteFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilterSetForm):
|
||||
model = Site
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id', 'tag')),
|
||||
(_('Attributes'), ('status', 'region_id', 'group_id', 'asn_id')),
|
||||
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
|
||||
(_('Contacts'), ('contact', 'contact_role', 'contact_group')),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('status', 'region_id', 'group_id', 'asn_id', name=_('Attributes')),
|
||||
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||
FieldSet('contact', 'contact_role', 'contact_group', name=_('Contacts')),
|
||||
)
|
||||
selector_fields = ('filter_id', 'q', 'region_id', 'group_id')
|
||||
status = forms.MultipleChoiceField(
|
||||
|
@ -192,10 +193,10 @@ class SiteFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilte
|
|||
class LocationFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilterSetForm):
|
||||
model = Location
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id', 'tag')),
|
||||
(_('Attributes'), ('region_id', 'site_group_id', 'site_id', 'parent_id', 'status')),
|
||||
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
|
||||
(_('Contacts'), ('contact', 'contact_role', 'contact_group')),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('region_id', 'site_group_id', 'site_id', 'parent_id', 'status', name=_('Attributes')),
|
||||
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||
FieldSet('contact', 'contact_role', 'contact_group', name=_('Contacts')),
|
||||
)
|
||||
region_id = DynamicModelMultipleChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
|
@ -241,13 +242,13 @@ class RackRoleFilterForm(NetBoxModelFilterSetForm):
|
|||
class RackFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilterSetForm):
|
||||
model = Rack
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id', 'tag')),
|
||||
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id')),
|
||||
(_('Function'), ('status', 'role_id')),
|
||||
(_('Hardware'), ('type', 'width', 'serial', 'asset_tag')),
|
||||
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
|
||||
(_('Contacts'), ('contact', 'contact_role', 'contact_group')),
|
||||
(_('Weight'), ('weight', 'max_weight', 'weight_unit')),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', name=_('Location')),
|
||||
FieldSet('status', 'role_id', name=_('Function')),
|
||||
FieldSet('type', 'width', 'serial', 'asset_tag', name=_('Hardware')),
|
||||
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||
FieldSet('contact', 'contact_role', 'contact_group', name=_('Contacts')),
|
||||
FieldSet('weight', 'max_weight', 'weight_unit', name=_('Weight')),
|
||||
)
|
||||
selector_fields = ('filter_id', 'q', 'region_id', 'site_group_id', 'site_id', 'location_id')
|
||||
region_id = DynamicModelMultipleChoiceField(
|
||||
|
@ -326,13 +327,13 @@ class RackFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilte
|
|||
|
||||
class RackElevationFilterForm(RackFilterForm):
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id', 'tag')),
|
||||
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'id')),
|
||||
(_('Function'), ('status', 'role_id')),
|
||||
(_('Hardware'), ('type', 'width', 'serial', 'asset_tag')),
|
||||
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
|
||||
(_('Contacts'), ('contact', 'contact_role', 'contact_group')),
|
||||
(_('Weight'), ('weight', 'max_weight', 'weight_unit')),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'id', name=_('Location')),
|
||||
FieldSet('status', 'role_id', name=_('Function')),
|
||||
FieldSet('type', 'width', 'serial', 'asset_tag', name=_('Hardware')),
|
||||
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||
FieldSet('contact', 'contact_role', 'contact_group', name=_('Contacts')),
|
||||
FieldSet('weight', 'max_weight', 'weight_unit', name=_('Weight')),
|
||||
)
|
||||
id = DynamicModelMultipleChoiceField(
|
||||
queryset=Rack.objects.all(),
|
||||
|
@ -348,10 +349,10 @@ class RackElevationFilterForm(RackFilterForm):
|
|||
class RackReservationFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
model = RackReservation
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id', 'tag')),
|
||||
(_('User'), ('user_id',)),
|
||||
(_('Rack'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
|
||||
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('user_id', name=_('User')),
|
||||
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Rack')),
|
||||
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||
)
|
||||
region_id = DynamicModelMultipleChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
|
@ -401,8 +402,8 @@ class RackReservationFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
|||
class ManufacturerFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
|
||||
model = Manufacturer
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id', 'tag')),
|
||||
(_('Contacts'), ('contact', 'contact_role', 'contact_group'))
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('contact', 'contact_role', 'contact_group', name=_('Contacts'))
|
||||
)
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
@ -410,14 +411,16 @@ class ManufacturerFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
|
|||
class DeviceTypeFilterForm(NetBoxModelFilterSetForm):
|
||||
model = DeviceType
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id', 'tag')),
|
||||
(_('Hardware'), ('manufacturer_id', 'default_platform_id', 'part_number', 'subdevice_role', 'airflow')),
|
||||
(_('Images'), ('has_front_image', 'has_rear_image')),
|
||||
(_('Components'), (
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet(
|
||||
'manufacturer_id', 'default_platform_id', 'part_number', 'subdevice_role', 'airflow', name=_('Hardware')
|
||||
),
|
||||
FieldSet('has_front_image', 'has_rear_image', name=_('Images')),
|
||||
FieldSet(
|
||||
'console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces',
|
||||
'pass_through_ports', 'device_bays', 'module_bays', 'inventory_items',
|
||||
)),
|
||||
(_('Weight'), ('weight', 'weight_unit')),
|
||||
'pass_through_ports', 'device_bays', 'module_bays', 'inventory_items', name=_('Components')
|
||||
),
|
||||
FieldSet('weight', 'weight_unit', name=_('Weight')),
|
||||
)
|
||||
selector_fields = ('filter_id', 'q', 'manufacturer_id')
|
||||
manufacturer_id = DynamicModelMultipleChoiceField(
|
||||
|
@ -536,13 +539,13 @@ class DeviceTypeFilterForm(NetBoxModelFilterSetForm):
|
|||
class ModuleTypeFilterForm(NetBoxModelFilterSetForm):
|
||||
model = ModuleType
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id', 'tag')),
|
||||
(_('Hardware'), ('manufacturer_id', 'part_number')),
|
||||
(_('Components'), (
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('manufacturer_id', 'part_number', name=_('Hardware')),
|
||||
FieldSet(
|
||||
'console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces',
|
||||
'pass_through_ports',
|
||||
)),
|
||||
(_('Weight'), ('weight', 'weight_unit')),
|
||||
'pass_through_ports', name=_('Components')
|
||||
),
|
||||
FieldSet('weight', 'weight_unit', name=_('Weight')),
|
||||
)
|
||||
selector_fields = ('filter_id', 'q', 'manufacturer_id')
|
||||
manufacturer_id = DynamicModelMultipleChoiceField(
|
||||
|
@ -642,18 +645,20 @@ class DeviceFilterForm(
|
|||
):
|
||||
model = Device
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id', 'tag')),
|
||||
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
|
||||
(_('Operation'), ('status', 'role_id', 'airflow', 'serial', 'asset_tag', 'mac_address')),
|
||||
(_('Hardware'), ('manufacturer_id', 'device_type_id', 'platform_id')),
|
||||
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
|
||||
(_('Contacts'), ('contact', 'contact_role', 'contact_group')),
|
||||
(_('Components'), (
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
|
||||
FieldSet('status', 'role_id', 'airflow', 'serial', 'asset_tag', 'mac_address', name=_('Operation')),
|
||||
FieldSet('manufacturer_id', 'device_type_id', 'platform_id', name=_('Hardware')),
|
||||
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||
FieldSet('contact', 'contact_role', 'contact_group', name=_('Contacts')),
|
||||
FieldSet(
|
||||
'console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces', 'pass_through_ports',
|
||||
)),
|
||||
(_('Miscellaneous'), (
|
||||
name=_('Components')
|
||||
),
|
||||
FieldSet(
|
||||
'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')
|
||||
region_id = DynamicModelMultipleChoiceField(
|
||||
|
@ -817,9 +822,9 @@ class VirtualDeviceContextFilterForm(
|
|||
):
|
||||
model = VirtualDeviceContext
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id', 'tag')),
|
||||
(_('Attributes'), ('device', 'status', 'has_primary_ip')),
|
||||
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('device', 'status', 'has_primary_ip', name=_('Attributes')),
|
||||
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||
)
|
||||
device = DynamicModelMultipleChoiceField(
|
||||
queryset=Device.objects.all(),
|
||||
|
@ -844,8 +849,8 @@ class VirtualDeviceContextFilterForm(
|
|||
class ModuleFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
model = Module
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id', 'tag')),
|
||||
(_('Hardware'), ('manufacturer_id', 'module_type_id', 'status', 'serial', 'asset_tag')),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('manufacturer_id', 'module_type_id', 'status', 'serial', 'asset_tag', name=_('Hardware')),
|
||||
)
|
||||
manufacturer_id = DynamicModelMultipleChoiceField(
|
||||
queryset=Manufacturer.objects.all(),
|
||||
|
@ -879,9 +884,9 @@ class ModuleFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, NetBoxMo
|
|||
class VirtualChassisFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
model = VirtualChassis
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id', 'tag')),
|
||||
(_('Location'), ('region_id', 'site_group_id', 'site_id')),
|
||||
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('region_id', 'site_group_id', 'site_id', name=_('Location')),
|
||||
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||
)
|
||||
region_id = DynamicModelMultipleChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
|
@ -908,10 +913,10 @@ class VirtualChassisFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
|||
class CableFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
model = Cable
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id', 'tag')),
|
||||
(_('Location'), ('site_id', 'location_id', 'rack_id', 'device_id')),
|
||||
(_('Attributes'), ('type', 'status', 'color', 'length', 'length_unit', 'unterminated')),
|
||||
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('site_id', 'location_id', 'rack_id', 'device_id', name=_('Location')),
|
||||
FieldSet('type', 'status', 'color', 'length', 'length_unit', 'unterminated', name=_('Attributes')),
|
||||
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||
)
|
||||
region_id = DynamicModelMultipleChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
|
@ -992,9 +997,9 @@ class CableFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
|||
class PowerPanelFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
|
||||
model = PowerPanel
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id', 'tag')),
|
||||
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id')),
|
||||
(_('Contacts'), ('contact', 'contact_role', 'contact_group')),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', name=_('Location')),
|
||||
FieldSet('contact', 'contact_role', 'contact_group', name=_('Contacts')),
|
||||
)
|
||||
selector_fields = ('filter_id', 'q', 'site_id', 'location_id')
|
||||
region_id = DynamicModelMultipleChoiceField(
|
||||
|
@ -1031,10 +1036,10 @@ class PowerPanelFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
|
|||
class PowerFeedFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
model = PowerFeed
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id', 'tag')),
|
||||
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'power_panel_id', 'rack_id')),
|
||||
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
|
||||
(_('Attributes'), ('status', 'type', 'supply', 'phase', 'voltage', 'amperage', 'max_utilization')),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('region_id', 'site_group_id', 'site_id', 'power_panel_id', 'rack_id', name=_('Location')),
|
||||
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||
FieldSet('status', 'type', 'supply', 'phase', 'voltage', 'amperage', 'max_utilization', name=_('Attributes')),
|
||||
)
|
||||
region_id = DynamicModelMultipleChoiceField(
|
||||
queryset=Region.objects.all(),
|
||||
|
@ -1141,11 +1146,11 @@ class PathEndpointFilterForm(CabledFilterForm):
|
|||
class ConsolePortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
|
||||
model = ConsolePort
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id', 'tag')),
|
||||
(_('Attributes'), ('name', 'label', 'type', 'speed')),
|
||||
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
|
||||
(_('Device'), ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')),
|
||||
(_('Connection'), ('cabled', 'connected', 'occupied')),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('name', 'label', 'type', 'speed', name=_('Attributes')),
|
||||
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
|
||||
FieldSet('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id', name=_('Device')),
|
||||
FieldSet('cabled', 'connected', 'occupied', name=_('Connection')),
|
||||
)
|
||||
type = forms.MultipleChoiceField(
|
||||
label=_('Type'),
|
||||
|
@ -1163,11 +1168,11 @@ class ConsolePortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
|
|||
class ConsoleServerPortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
|
||||
model = ConsoleServerPort
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id', 'tag')),
|
||||
(_('Attributes'), ('name', 'label', 'type', 'speed')),
|
||||
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
|
||||
(_('Device'), ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')),
|
||||
(_('Connection'), ('cabled', 'connected', 'occupied')),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('name', 'label', 'type', 'speed', name=_('Attributes')),
|
||||
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
|
||||
FieldSet('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id', name=_('Device')),
|
||||
FieldSet('cabled', 'connected', 'occupied', name=_('Connection')),
|
||||
)
|
||||
type = forms.MultipleChoiceField(
|
||||
label=_('Type'),
|
||||
|
@ -1185,11 +1190,11 @@ class ConsoleServerPortFilterForm(PathEndpointFilterForm, DeviceComponentFilterF
|
|||
class PowerPortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
|
||||
model = PowerPort
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id', 'tag')),
|
||||
(_('Attributes'), ('name', 'label', 'type')),
|
||||
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
|
||||
(_('Device'), ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')),
|
||||
(_('Connection'), ('cabled', 'connected', 'occupied')),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('name', 'label', 'type', name=_('Attributes')),
|
||||
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
|
||||
FieldSet('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id', name=_('Device')),
|
||||
FieldSet('cabled', 'connected', 'occupied', name=_('Connection')),
|
||||
)
|
||||
type = forms.MultipleChoiceField(
|
||||
label=_('Type'),
|
||||
|
@ -1202,11 +1207,11 @@ class PowerPortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
|
|||
class PowerOutletFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
|
||||
model = PowerOutlet
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id', 'tag')),
|
||||
(_('Attributes'), ('name', 'label', 'type')),
|
||||
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
|
||||
(_('Device'), ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')),
|
||||
(_('Connection'), ('cabled', 'connected', 'occupied')),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('name', 'label', 'type', name=_('Attributes')),
|
||||
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
|
||||
FieldSet('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id', name=_('Device')),
|
||||
FieldSet('cabled', 'connected', 'occupied', name=_('Connection')),
|
||||
)
|
||||
type = forms.MultipleChoiceField(
|
||||
label=_('Type'),
|
||||
|
@ -1219,14 +1224,14 @@ class PowerOutletFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
|
|||
class InterfaceFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
|
||||
model = Interface
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id', 'tag')),
|
||||
(_('Attributes'), ('name', 'label', 'kind', 'type', 'speed', 'duplex', 'enabled', 'mgmt_only')),
|
||||
(_('Addressing'), ('vrf_id', 'l2vpn_id', 'mac_address', 'wwn')),
|
||||
(_('PoE'), ('poe_mode', 'poe_type')),
|
||||
(_('Wireless'), ('rf_role', 'rf_channel', 'rf_channel_width', 'tx_power')),
|
||||
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
|
||||
(_('Device'), ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id', 'vdc_id')),
|
||||
(_('Connection'), ('cabled', 'connected', 'occupied')),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('name', 'label', 'kind', 'type', 'speed', 'duplex', 'enabled', 'mgmt_only', name=_('Attributes')),
|
||||
FieldSet('vrf_id', 'l2vpn_id', 'mac_address', 'wwn', name=_('Addressing')),
|
||||
FieldSet('poe_mode', 'poe_type', name=_('PoE')),
|
||||
FieldSet('rf_role', 'rf_channel', 'rf_channel_width', 'tx_power', name=_('Wireless')),
|
||||
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', 'vdc_id', name=_('Device')),
|
||||
FieldSet('cabled', 'connected', 'occupied', name=_('Connection')),
|
||||
)
|
||||
selector_fields = ('filter_id', 'q', 'device_id')
|
||||
vdc_id = DynamicModelMultipleChoiceField(
|
||||
|
@ -1330,11 +1335,11 @@ class InterfaceFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
|
|||
|
||||
class FrontPortFilterForm(CabledFilterForm, DeviceComponentFilterForm):
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id', 'tag')),
|
||||
(_('Attributes'), ('name', 'label', 'type', 'color')),
|
||||
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
|
||||
(_('Device'), ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')),
|
||||
(_('Cable'), ('cabled', 'occupied')),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('name', 'label', 'type', 'color', name=_('Attributes')),
|
||||
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
|
||||
FieldSet('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id', name=_('Device')),
|
||||
FieldSet('cabled', 'occupied', name=_('Cable')),
|
||||
)
|
||||
model = FrontPort
|
||||
type = forms.MultipleChoiceField(
|
||||
|
@ -1352,11 +1357,11 @@ class FrontPortFilterForm(CabledFilterForm, DeviceComponentFilterForm):
|
|||
class RearPortFilterForm(CabledFilterForm, DeviceComponentFilterForm):
|
||||
model = RearPort
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id', 'tag')),
|
||||
(_('Attributes'), ('name', 'label', 'type', 'color')),
|
||||
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
|
||||
(_('Device'), ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')),
|
||||
(_('Cable'), ('cabled', 'occupied')),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('name', 'label', 'type', 'color', name=_('Attributes')),
|
||||
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
|
||||
FieldSet('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id', name=_('Device')),
|
||||
FieldSet('cabled', 'occupied', name=_('Cable')),
|
||||
)
|
||||
type = forms.MultipleChoiceField(
|
||||
label=_('Type'),
|
||||
|
@ -1373,10 +1378,10 @@ class RearPortFilterForm(CabledFilterForm, DeviceComponentFilterForm):
|
|||
class ModuleBayFilterForm(DeviceComponentFilterForm):
|
||||
model = ModuleBay
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id', 'tag')),
|
||||
(_('Attributes'), ('name', 'label', 'position')),
|
||||
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
|
||||
(_('Device'), ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('name', 'label', 'position', name=_('Attributes')),
|
||||
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
|
||||
FieldSet('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id', name=_('Device')),
|
||||
)
|
||||
tag = TagFilterField(model)
|
||||
position = forms.CharField(
|
||||
|
@ -1388,10 +1393,10 @@ class ModuleBayFilterForm(DeviceComponentFilterForm):
|
|||
class DeviceBayFilterForm(DeviceComponentFilterForm):
|
||||
model = DeviceBay
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id', 'tag')),
|
||||
(_('Attributes'), ('name', 'label')),
|
||||
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
|
||||
(_('Device'), ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('name', 'label', name=_('Attributes')),
|
||||
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', name=_('Location')),
|
||||
FieldSet('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id', name=_('Device')),
|
||||
)
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
@ -1399,10 +1404,13 @@ class DeviceBayFilterForm(DeviceComponentFilterForm):
|
|||
class InventoryItemFilterForm(DeviceComponentFilterForm):
|
||||
model = InventoryItem
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id', 'tag')),
|
||||
(_('Attributes'), ('name', 'label', 'role_id', 'manufacturer_id', 'serial', 'asset_tag', 'discovered')),
|
||||
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
|
||||
(_('Device'), ('device_type_id', 'device_role_id', 'device_id', 'virtual_chassis_id')),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet(
|
||||
'name', 'label', 'role_id', 'manufacturer_id', 'serial', 'asset_tag', 'discovered',
|
||||
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(
|
||||
queryset=InventoryItemRole.objects.all(),
|
||||
|
|
|
@ -16,6 +16,7 @@ from utilities.forms.fields import (
|
|||
CommentField, ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, JSONField,
|
||||
NumericArrayField, SlugField,
|
||||
)
|
||||
from utilities.forms.rendering import FieldSet, InlineFields, TabbedGroups
|
||||
from utilities.forms.widgets import APISelect, ClearableFileInput, HTMXSelect, NumberWithOptions, SelectWithPK
|
||||
from virtualization.models import Cluster
|
||||
from wireless.models import WirelessLAN, WirelessLANGroup
|
||||
|
@ -77,9 +78,7 @@ class RegionForm(NetBoxModelForm):
|
|||
slug = SlugField()
|
||||
|
||||
fieldsets = (
|
||||
(_('Region'), (
|
||||
'parent', 'name', 'slug', 'description', 'tags',
|
||||
)),
|
||||
FieldSet('parent', 'name', 'slug', 'description', 'tags'),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -98,9 +97,7 @@ class SiteGroupForm(NetBoxModelForm):
|
|||
slug = SlugField()
|
||||
|
||||
fieldsets = (
|
||||
(_('Site Group'), (
|
||||
'parent', 'name', 'slug', 'description', 'tags',
|
||||
)),
|
||||
FieldSet('parent', 'name', 'slug', 'description', 'tags'),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -135,11 +132,12 @@ class SiteForm(TenancyForm, NetBoxModelForm):
|
|||
comments = CommentField()
|
||||
|
||||
fieldsets = (
|
||||
(_('Site'), (
|
||||
FieldSet(
|
||||
'name', 'slug', 'status', 'region', 'group', 'facility', 'asns', 'time_zone', 'description', 'tags',
|
||||
)),
|
||||
(_('Tenancy'), ('tenant_group', 'tenant')),
|
||||
(_('Contact Info'), ('physical_address', 'shipping_address', 'latitude', 'longitude')),
|
||||
name=_('Site')
|
||||
),
|
||||
FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
|
||||
FieldSet('physical_address', 'shipping_address', 'latitude', 'longitude', name=_('Contact Info')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -179,14 +177,14 @@ class LocationForm(TenancyForm, NetBoxModelForm):
|
|||
slug = SlugField()
|
||||
|
||||
fieldsets = (
|
||||
(_('Location'), ('site', 'parent', 'name', 'slug', 'status', 'description', 'tags')),
|
||||
(_('Tenancy'), ('tenant_group', 'tenant')),
|
||||
FieldSet('site', 'parent', 'name', 'slug', 'status', 'facility', 'description', 'tags', name=_('Location')),
|
||||
FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Location
|
||||
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()
|
||||
|
||||
fieldsets = (
|
||||
(_('Rack Role'), (
|
||||
'name', 'slug', 'color', 'description', 'tags',
|
||||
)),
|
||||
FieldSet('name', 'slug', 'color', 'description', 'tags', name=_('Rack Role')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -227,6 +223,18 @@ class RackForm(TenancyForm, NetBoxModelForm):
|
|||
)
|
||||
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:
|
||||
model = Rack
|
||||
fields = [
|
||||
|
@ -256,8 +264,8 @@ class RackReservationForm(TenancyForm, NetBoxModelForm):
|
|||
comments = CommentField()
|
||||
|
||||
fieldsets = (
|
||||
(_('Reservation'), ('rack', 'units', 'user', 'description', 'tags')),
|
||||
(_('Tenancy'), ('tenant_group', 'tenant')),
|
||||
FieldSet('rack', 'units', 'user', 'description', 'tags', name=_('Reservation')),
|
||||
FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -271,9 +279,7 @@ class ManufacturerForm(NetBoxModelForm):
|
|||
slug = SlugField()
|
||||
|
||||
fieldsets = (
|
||||
(_('Manufacturer'), (
|
||||
'name', 'slug', 'description', 'tags',
|
||||
)),
|
||||
FieldSet('name', 'slug', 'description', 'tags', name=_('Manufacturer')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -304,12 +310,12 @@ class DeviceTypeForm(NetBoxModelForm):
|
|||
comments = CommentField()
|
||||
|
||||
fieldsets = (
|
||||
(_('Device Type'), ('manufacturer', 'model', 'slug', 'default_platform', 'description', 'tags')),
|
||||
(_('Chassis'), (
|
||||
FieldSet('manufacturer', 'model', 'slug', 'default_platform', 'description', 'tags', name=_('Device Type')),
|
||||
FieldSet(
|
||||
'u_height', 'exclude_from_utilization', 'is_full_depth', 'part_number', 'subdevice_role', 'airflow',
|
||||
'weight', 'weight_unit',
|
||||
)),
|
||||
(_('Images'), ('front_image', 'rear_image')),
|
||||
'weight', 'weight_unit', name=_('Chassis')
|
||||
),
|
||||
FieldSet('front_image', 'rear_image', name=_('Images')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -337,8 +343,8 @@ class ModuleTypeForm(NetBoxModelForm):
|
|||
comments = CommentField()
|
||||
|
||||
fieldsets = (
|
||||
(_('Module Type'), ('manufacturer', 'model', 'part_number', 'description', 'tags')),
|
||||
(_('Weight'), ('weight', 'weight_unit'))
|
||||
FieldSet('manufacturer', 'model', 'part_number', 'description', 'tags', name=_('Module Type')),
|
||||
FieldSet('weight', 'weight_unit', name=_('Weight'))
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -357,9 +363,9 @@ class DeviceRoleForm(NetBoxModelForm):
|
|||
slug = SlugField()
|
||||
|
||||
fieldsets = (
|
||||
(_('Device Role'), (
|
||||
'name', 'slug', 'color', 'vm_role', 'config_template', 'description', 'tags',
|
||||
)),
|
||||
FieldSet(
|
||||
'name', 'slug', 'color', 'vm_role', 'config_template', 'description', 'tags', name=_('Device Role')
|
||||
),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -386,7 +392,7 @@ class PlatformForm(NetBoxModelForm):
|
|||
)
|
||||
|
||||
fieldsets = (
|
||||
(_('Platform'), ('name', 'slug', 'manufacturer', 'config_template', 'description', 'tags')),
|
||||
FieldSet('name', 'slug', 'manufacturer', 'config_template', 'description', 'tags', name=_('Platform')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -601,10 +607,8 @@ class ModuleForm(ModuleCommonForm, NetBoxModelForm):
|
|||
)
|
||||
|
||||
fieldsets = (
|
||||
(_('Module'), ('device', 'module_bay', 'module_type', 'status', 'description', 'tags')),
|
||||
(_('Hardware'), (
|
||||
'serial', 'asset_tag', 'replicate_components', 'adopt_components',
|
||||
)),
|
||||
FieldSet('device', 'module_bay', 'module_type', 'status', 'description', 'tags', name=_('Module')),
|
||||
FieldSet('serial', 'asset_tag', 'replicate_components', 'adopt_components', name=_('Hardware')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -658,7 +662,7 @@ class PowerPanelForm(NetBoxModelForm):
|
|||
comments = CommentField()
|
||||
|
||||
fieldsets = (
|
||||
('Power Panel', ('site', 'location', 'name', 'description', 'tags')),
|
||||
FieldSet('site', 'location', 'name', 'description', 'tags', name=_('Power Panel')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -683,9 +687,12 @@ class PowerFeedForm(TenancyForm, NetBoxModelForm):
|
|||
comments = CommentField()
|
||||
|
||||
fieldsets = (
|
||||
(_('Power Feed'), ('power_panel', 'rack', 'name', 'status', 'type', 'description', 'mark_connected', 'tags')),
|
||||
(_('Characteristics'), ('supply', 'voltage', 'amperage', 'phase', 'max_utilization')),
|
||||
(_('Tenancy'), ('tenant_group', 'tenant')),
|
||||
FieldSet(
|
||||
'power_panel', 'rack', 'name', 'status', 'type', 'description', 'mark_connected', 'tags',
|
||||
name=_('Power Feed')
|
||||
),
|
||||
FieldSet('supply', 'voltage', 'amperage', 'phase', 'max_utilization', name=_('Characteristics')),
|
||||
FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -832,7 +839,7 @@ class ModularComponentTemplateForm(ComponentTemplateForm):
|
|||
|
||||
class ConsolePortTemplateForm(ModularComponentTemplateForm):
|
||||
fieldsets = (
|
||||
(None, ('device_type', 'module_type', 'name', 'label', 'type', 'description')),
|
||||
FieldSet('device_type', 'module_type', 'name', 'label', 'type', 'description'),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -844,7 +851,7 @@ class ConsolePortTemplateForm(ModularComponentTemplateForm):
|
|||
|
||||
class ConsoleServerPortTemplateForm(ModularComponentTemplateForm):
|
||||
fieldsets = (
|
||||
(None, ('device_type', 'module_type', 'name', 'label', 'type', 'description')),
|
||||
FieldSet('device_type', 'module_type', 'name', 'label', 'type', 'description'),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -856,9 +863,9 @@ class ConsoleServerPortTemplateForm(ModularComponentTemplateForm):
|
|||
|
||||
class PowerPortTemplateForm(ModularComponentTemplateForm):
|
||||
fieldsets = (
|
||||
(None, (
|
||||
FieldSet(
|
||||
'device_type', 'module_type', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description',
|
||||
)),
|
||||
),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -879,7 +886,7 @@ class PowerOutletTemplateForm(ModularComponentTemplateForm):
|
|||
)
|
||||
|
||||
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:
|
||||
|
@ -901,9 +908,11 @@ class InterfaceTemplateForm(ModularComponentTemplateForm):
|
|||
)
|
||||
|
||||
fieldsets = (
|
||||
(None, ('device_type', 'module_type', 'name', 'label', 'type', 'enabled', 'mgmt_only', 'description', 'bridge')),
|
||||
(_('PoE'), ('poe_mode', 'poe_type')),
|
||||
(_('Wireless'), ('rf_role',)),
|
||||
FieldSet(
|
||||
'device_type', 'module_type', 'name', 'label', 'type', 'enabled', 'mgmt_only', 'description', 'bridge',
|
||||
),
|
||||
FieldSet('poe_mode', 'poe_type', name=_('PoE')),
|
||||
FieldSet('rf_role', name=_('Wireless')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -925,10 +934,10 @@ class FrontPortTemplateForm(ModularComponentTemplateForm):
|
|||
)
|
||||
|
||||
fieldsets = (
|
||||
(None, (
|
||||
FieldSet(
|
||||
'device_type', 'module_type', 'name', 'label', 'type', 'color', 'rear_port', 'rear_port_position',
|
||||
'description',
|
||||
)),
|
||||
),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -941,7 +950,7 @@ class FrontPortTemplateForm(ModularComponentTemplateForm):
|
|||
|
||||
class RearPortTemplateForm(ModularComponentTemplateForm):
|
||||
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:
|
||||
|
@ -953,7 +962,7 @@ class RearPortTemplateForm(ModularComponentTemplateForm):
|
|||
|
||||
class ModuleBayTemplateForm(ComponentTemplateForm):
|
||||
fieldsets = (
|
||||
(None, ('device_type', 'name', 'label', 'position', 'description')),
|
||||
FieldSet('device_type', 'name', 'label', 'position', 'description'),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -965,7 +974,7 @@ class ModuleBayTemplateForm(ComponentTemplateForm):
|
|||
|
||||
class DeviceBayTemplateForm(ComponentTemplateForm):
|
||||
fieldsets = (
|
||||
(None, ('device_type', 'name', 'label', 'description')),
|
||||
FieldSet('device_type', 'name', 'label', 'description'),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -1006,10 +1015,10 @@ class InventoryItemTemplateForm(ComponentTemplateForm):
|
|||
)
|
||||
|
||||
fieldsets = (
|
||||
(None, (
|
||||
FieldSet(
|
||||
'device_type', 'parent', 'name', 'label', 'role', 'manufacturer', 'part_id', 'description',
|
||||
'component_type', 'component_id',
|
||||
)),
|
||||
),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -1052,9 +1061,9 @@ class ModularDeviceComponentForm(DeviceComponentForm):
|
|||
|
||||
class ConsolePortForm(ModularDeviceComponentForm):
|
||||
fieldsets = (
|
||||
(None, (
|
||||
FieldSet(
|
||||
'device', 'module', 'name', 'label', 'type', 'speed', 'mark_connected', 'description', 'tags',
|
||||
)),
|
||||
),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -1065,11 +1074,10 @@ class ConsolePortForm(ModularDeviceComponentForm):
|
|||
|
||||
|
||||
class ConsoleServerPortForm(ModularDeviceComponentForm):
|
||||
|
||||
fieldsets = (
|
||||
(None, (
|
||||
FieldSet(
|
||||
'device', 'module', 'name', 'label', 'type', 'speed', 'mark_connected', 'description', 'tags',
|
||||
)),
|
||||
),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -1080,12 +1088,11 @@ class ConsoleServerPortForm(ModularDeviceComponentForm):
|
|||
|
||||
|
||||
class PowerPortForm(ModularDeviceComponentForm):
|
||||
|
||||
fieldsets = (
|
||||
(None, (
|
||||
FieldSet(
|
||||
'device', 'module', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'mark_connected',
|
||||
'description', 'tags',
|
||||
)),
|
||||
),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -1107,10 +1114,10 @@ class PowerOutletForm(ModularDeviceComponentForm):
|
|||
)
|
||||
|
||||
fieldsets = (
|
||||
(None, (
|
||||
FieldSet(
|
||||
'device', 'module', 'name', 'label', 'type', 'power_port', 'feed_leg', 'mark_connected', 'description',
|
||||
'tags',
|
||||
)),
|
||||
),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -1206,15 +1213,18 @@ class InterfaceForm(InterfaceCommonForm, ModularDeviceComponentForm):
|
|||
)
|
||||
|
||||
fieldsets = (
|
||||
(_('Interface'), ('device', 'module', 'name', 'label', 'type', 'speed', 'duplex', 'description', 'tags')),
|
||||
(_('Addressing'), ('vrf', 'mac_address', 'wwn')),
|
||||
(_('Operation'), ('vdcs', 'mtu', 'tx_power', 'enabled', 'mgmt_only', 'mark_connected')),
|
||||
(_('Related Interfaces'), ('parent', 'bridge', 'lag')),
|
||||
(_('PoE'), ('poe_mode', 'poe_type')),
|
||||
(_('802.1Q Switching'), ('mode', 'vlan_group', 'untagged_vlan', 'tagged_vlans')),
|
||||
(_('Wireless'), (
|
||||
FieldSet(
|
||||
'device', 'module', 'name', 'label', 'type', 'speed', 'duplex', 'description', 'tags', name=_('Interface')
|
||||
),
|
||||
FieldSet('vrf', 'mac_address', 'wwn', name=_('Addressing')),
|
||||
FieldSet('vdcs', 'mtu', 'tx_power', 'enabled', 'mgmt_only', 'mark_connected', name=_('Operation')),
|
||||
FieldSet('parent', 'bridge', 'lag', name=_('Related Interfaces')),
|
||||
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',
|
||||
)),
|
||||
name=_('Wireless')
|
||||
),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -1245,10 +1255,10 @@ class FrontPortForm(ModularDeviceComponentForm):
|
|||
)
|
||||
|
||||
fieldsets = (
|
||||
(None, (
|
||||
FieldSet(
|
||||
'device', 'module', 'name', 'label', 'type', 'color', 'rear_port', 'rear_port_position', 'mark_connected',
|
||||
'description', 'tags',
|
||||
)),
|
||||
),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -1261,9 +1271,9 @@ class FrontPortForm(ModularDeviceComponentForm):
|
|||
|
||||
class RearPortForm(ModularDeviceComponentForm):
|
||||
fieldsets = (
|
||||
(None, (
|
||||
FieldSet(
|
||||
'device', 'module', 'name', 'label', 'type', 'color', 'positions', 'mark_connected', 'description', 'tags',
|
||||
)),
|
||||
),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -1275,7 +1285,7 @@ class RearPortForm(ModularDeviceComponentForm):
|
|||
|
||||
class ModuleBayForm(DeviceComponentForm):
|
||||
fieldsets = (
|
||||
(None, ('device', 'name', 'label', 'position', 'description', 'tags',)),
|
||||
FieldSet('device', 'name', 'label', 'position', 'description', 'tags',),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -1287,7 +1297,7 @@ class ModuleBayForm(DeviceComponentForm):
|
|||
|
||||
class DeviceBayForm(DeviceComponentForm):
|
||||
fieldsets = (
|
||||
(None, ('device', 'name', 'label', 'description', 'tags',)),
|
||||
FieldSet('device', 'name', 'label', 'description', 'tags',),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -1395,8 +1405,20 @@ class InventoryItemForm(DeviceComponentForm):
|
|||
)
|
||||
|
||||
fieldsets = (
|
||||
(_('Inventory Item'), ('device', 'parent', 'name', 'label', 'role', 'description', 'tags')),
|
||||
(_('Hardware'), ('manufacturer', 'part_id', 'serial', 'asset_tag')),
|
||||
FieldSet('device', 'parent', 'name', 'label', 'role', 'description', 'tags', name=_('Inventory Item')),
|
||||
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:
|
||||
|
@ -1412,22 +1434,17 @@ class InventoryItemForm(DeviceComponentForm):
|
|||
component_type = initial.get('component_type')
|
||||
component_id = initial.get('component_id')
|
||||
|
||||
# Used for picking the default active tab for component selection
|
||||
self.no_component = True
|
||||
|
||||
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):
|
||||
if type(instance.component) is component_model.model_class():
|
||||
initial[component_model.model] = instance.component
|
||||
self.no_component = False
|
||||
break
|
||||
elif component_type and component_id:
|
||||
# When adding the InventoryItem from a component page
|
||||
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():
|
||||
initial[content_type.model] = component
|
||||
self.no_component = False
|
||||
|
||||
kwargs['initial'] = initial
|
||||
|
||||
|
@ -1461,9 +1478,7 @@ class InventoryItemRoleForm(NetBoxModelForm):
|
|||
slug = SlugField()
|
||||
|
||||
fieldsets = (
|
||||
(_('Inventory Item Role'), (
|
||||
'name', 'slug', 'color', 'description', 'tags',
|
||||
)),
|
||||
FieldSet('name', 'slug', 'color', 'description', 'tags', name=_('Inventory Item Role')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -1499,8 +1514,11 @@ class VirtualDeviceContextForm(TenancyForm, NetBoxModelForm):
|
|||
)
|
||||
|
||||
fieldsets = (
|
||||
(_('Virtual Device Context'), ('device', 'name', 'status', 'identifier', 'primary_ip4', 'primary_ip6', 'tags')),
|
||||
(_('Tenancy'), ('tenant_group', 'tenant'))
|
||||
FieldSet(
|
||||
'device', 'name', 'status', 'identifier', 'primary_ip4', 'primary_ip6', 'tags',
|
||||
name=_('Virtual Device Context')
|
||||
),
|
||||
FieldSet('tenant_group', 'tenant', name=_('Tenancy'))
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
|
|
@ -4,6 +4,7 @@ from django.utils.translation import gettext_lazy as _
|
|||
from dcim.models import *
|
||||
from netbox.forms import NetBoxModelForm
|
||||
from utilities.forms.fields import DynamicModelChoiceField, DynamicModelMultipleChoiceField, ExpandableNameField
|
||||
from utilities.forms.rendering import FieldSet
|
||||
from utilities.forms.widgets import APISelect
|
||||
from . import model_forms
|
||||
|
||||
|
@ -113,7 +114,7 @@ class FrontPortTemplateCreateForm(ComponentCreateForm, model_forms.FrontPortTemp
|
|||
|
||||
# Override fieldsets from FrontPortTemplateForm to omit rear_port_position
|
||||
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):
|
||||
|
@ -274,9 +275,9 @@ class FrontPortCreateForm(ComponentCreateForm, model_forms.FrontPortForm):
|
|||
|
||||
# Override fieldsets from FrontPortForm to omit rear_port_position
|
||||
fieldsets = (
|
||||
(None, (
|
||||
FieldSet(
|
||||
'device', 'module', 'name', 'label', 'type', 'color', 'rear_port', 'mark_connected', 'description', 'tags',
|
||||
)),
|
||||
),
|
||||
)
|
||||
|
||||
class Meta(model_forms.FrontPortForm.Meta):
|
||||
|
|
|
@ -0,0 +1,294 @@
|
|||
import strawberry_django
|
||||
|
||||
from dcim import filtersets, models
|
||||
from netbox.graphql.filter_mixins import autotype_decorator, BaseFilterMixin
|
||||
|
||||
__all__ = (
|
||||
'CableFilter',
|
||||
'CableTerminationFilter',
|
||||
'ConsolePortFilter',
|
||||
'ConsolePortTemplateFilter',
|
||||
'ConsoleServerPortFilter',
|
||||
'ConsoleServerPortTemplateFilter',
|
||||
'DeviceFilter',
|
||||
'DeviceBayFilter',
|
||||
'DeviceBayTemplateFilter',
|
||||
'InventoryItemTemplateFilter',
|
||||
'DeviceRoleFilter',
|
||||
'DeviceTypeFilter',
|
||||
'FrontPortFilter',
|
||||
'FrontPortTemplateFilter',
|
||||
'InterfaceFilter',
|
||||
'InterfaceTemplateFilter',
|
||||
'InventoryItemFilter',
|
||||
'InventoryItemRoleFilter',
|
||||
'LocationFilter',
|
||||
'ManufacturerFilter',
|
||||
'ModuleFilter',
|
||||
'ModuleBayFilter',
|
||||
'ModuleBayTemplateFilter',
|
||||
'ModuleTypeFilter',
|
||||
'PlatformFilter',
|
||||
'PowerFeedFilter',
|
||||
'PowerOutletFilter',
|
||||
'PowerOutletTemplateFilter',
|
||||
'PowerPanelFilter',
|
||||
'PowerPortFilter',
|
||||
'PowerPortTemplateFilter',
|
||||
'RackFilter',
|
||||
'RackReservationFilter',
|
||||
'RackRoleFilter',
|
||||
'RearPortFilter',
|
||||
'RearPortTemplateFilter',
|
||||
'RegionFilter',
|
||||
'SiteFilter',
|
||||
'SiteGroupFilter',
|
||||
'VirtualChassisFilter',
|
||||
'VirtualDeviceContextFilter',
|
||||
)
|
||||
|
||||
|
||||
@strawberry_django.filter(models.Cable, lookups=True)
|
||||
@autotype_decorator(filtersets.CableFilterSet)
|
||||
class CableFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.CableTermination, lookups=True)
|
||||
@autotype_decorator(filtersets.CableTerminationFilterSet)
|
||||
class CableTerminationFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.ConsolePort, lookups=True)
|
||||
@autotype_decorator(filtersets.ConsolePortFilterSet)
|
||||
class ConsolePortFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.ConsolePortTemplate, lookups=True)
|
||||
@autotype_decorator(filtersets.ConsolePortTemplateFilterSet)
|
||||
class ConsolePortTemplateFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.ConsoleServerPort, lookups=True)
|
||||
@autotype_decorator(filtersets.ConsoleServerPortFilterSet)
|
||||
class ConsoleServerPortFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.ConsoleServerPortTemplate, lookups=True)
|
||||
@autotype_decorator(filtersets.ConsoleServerPortTemplateFilterSet)
|
||||
class ConsoleServerPortTemplateFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.Device, lookups=True)
|
||||
@autotype_decorator(filtersets.DeviceFilterSet)
|
||||
class DeviceFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.DeviceBay, lookups=True)
|
||||
@autotype_decorator(filtersets.DeviceBayFilterSet)
|
||||
class DeviceBayFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.DeviceBayTemplate, lookups=True)
|
||||
@autotype_decorator(filtersets.DeviceBayTemplateFilterSet)
|
||||
class DeviceBayTemplateFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.InventoryItemTemplate, lookups=True)
|
||||
@autotype_decorator(filtersets.InventoryItemTemplateFilterSet)
|
||||
class InventoryItemTemplateFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.DeviceRole, lookups=True)
|
||||
@autotype_decorator(filtersets.DeviceRoleFilterSet)
|
||||
class DeviceRoleFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.DeviceType, lookups=True)
|
||||
@autotype_decorator(filtersets.DeviceTypeFilterSet)
|
||||
class DeviceTypeFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.FrontPort, lookups=True)
|
||||
@autotype_decorator(filtersets.FrontPortFilterSet)
|
||||
class FrontPortFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.FrontPortTemplate, lookups=True)
|
||||
@autotype_decorator(filtersets.FrontPortTemplateFilterSet)
|
||||
class FrontPortTemplateFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.Interface, lookups=True)
|
||||
@autotype_decorator(filtersets.InterfaceFilterSet)
|
||||
class InterfaceFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.InterfaceTemplate, lookups=True)
|
||||
@autotype_decorator(filtersets.InterfaceTemplateFilterSet)
|
||||
class InterfaceTemplateFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.InventoryItem, lookups=True)
|
||||
@autotype_decorator(filtersets.InventoryItemFilterSet)
|
||||
class InventoryItemFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.InventoryItemRole, lookups=True)
|
||||
@autotype_decorator(filtersets.InventoryItemRoleFilterSet)
|
||||
class InventoryItemRoleFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.Location, lookups=True)
|
||||
@autotype_decorator(filtersets.LocationFilterSet)
|
||||
class LocationFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.Manufacturer, lookups=True)
|
||||
@autotype_decorator(filtersets.ManufacturerFilterSet)
|
||||
class ManufacturerFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.Module, lookups=True)
|
||||
@autotype_decorator(filtersets.ModuleFilterSet)
|
||||
class ModuleFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.ModuleBay, lookups=True)
|
||||
@autotype_decorator(filtersets.ModuleBayFilterSet)
|
||||
class ModuleBayFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.ModuleBayTemplate, lookups=True)
|
||||
@autotype_decorator(filtersets.ModuleBayTemplateFilterSet)
|
||||
class ModuleBayTemplateFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.ModuleType, lookups=True)
|
||||
@autotype_decorator(filtersets.ModuleTypeFilterSet)
|
||||
class ModuleTypeFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.Platform, lookups=True)
|
||||
@autotype_decorator(filtersets.PlatformFilterSet)
|
||||
class PlatformFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.PowerFeed, lookups=True)
|
||||
@autotype_decorator(filtersets.PowerFeedFilterSet)
|
||||
class PowerFeedFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.PowerOutlet, lookups=True)
|
||||
@autotype_decorator(filtersets.PowerOutletFilterSet)
|
||||
class PowerOutletFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.PowerOutletTemplate, lookups=True)
|
||||
@autotype_decorator(filtersets.PowerOutletTemplateFilterSet)
|
||||
class PowerOutletTemplateFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.PowerPanel, lookups=True)
|
||||
@autotype_decorator(filtersets.PowerPanelFilterSet)
|
||||
class PowerPanelFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.PowerPort, lookups=True)
|
||||
@autotype_decorator(filtersets.PowerPortFilterSet)
|
||||
class PowerPortFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.PowerPortTemplate, lookups=True)
|
||||
@autotype_decorator(filtersets.PowerPortTemplateFilterSet)
|
||||
class PowerPortTemplateFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.Rack, lookups=True)
|
||||
@autotype_decorator(filtersets.RackFilterSet)
|
||||
class RackFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.RackReservation, lookups=True)
|
||||
@autotype_decorator(filtersets.RackReservationFilterSet)
|
||||
class RackReservationFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.RackRole, lookups=True)
|
||||
@autotype_decorator(filtersets.RackRoleFilterSet)
|
||||
class RackRoleFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.RearPort, lookups=True)
|
||||
@autotype_decorator(filtersets.RearPortFilterSet)
|
||||
class RearPortFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.RearPortTemplate, lookups=True)
|
||||
@autotype_decorator(filtersets.RearPortTemplateFilterSet)
|
||||
class RearPortTemplateFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.Region, lookups=True)
|
||||
@autotype_decorator(filtersets.RegionFilterSet)
|
||||
class RegionFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.Site, lookups=True)
|
||||
@autotype_decorator(filtersets.SiteFilterSet)
|
||||
class SiteFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.SiteGroup, lookups=True)
|
||||
@autotype_decorator(filtersets.SiteGroupFilterSet)
|
||||
class SiteGroupFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.VirtualChassis, lookups=True)
|
||||
@autotype_decorator(filtersets.VirtualChassisFilterSet)
|
||||
class VirtualChassisFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.VirtualDeviceContext, lookups=True)
|
||||
@autotype_decorator(filtersets.VirtualDeviceContextFilterSet)
|
||||
class VirtualDeviceContextFilter(BaseFilterMixin):
|
||||
pass
|
|
@ -1,4 +1,3 @@
|
|||
import graphene
|
||||
from circuits.graphql.types import CircuitTerminationType, ProviderNetworkType
|
||||
from circuits.models import CircuitTermination, ProviderNetwork
|
||||
from dcim.graphql.types import (
|
||||
|
@ -37,79 +36,7 @@ from dcim.models import (
|
|||
)
|
||||
|
||||
|
||||
class LinkPeerType(graphene.Union):
|
||||
class Meta:
|
||||
types = (
|
||||
CircuitTerminationType,
|
||||
ConsolePortType,
|
||||
ConsoleServerPortType,
|
||||
FrontPortType,
|
||||
InterfaceType,
|
||||
PowerFeedType,
|
||||
PowerOutletType,
|
||||
PowerPortType,
|
||||
RearPortType,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def resolve_type(cls, instance, info):
|
||||
if type(instance) is CircuitTermination:
|
||||
return CircuitTerminationType
|
||||
if type(instance) is ConsolePortType:
|
||||
return ConsolePortType
|
||||
if type(instance) is ConsoleServerPort:
|
||||
return ConsoleServerPortType
|
||||
if type(instance) is FrontPort:
|
||||
return FrontPortType
|
||||
if type(instance) is Interface:
|
||||
return InterfaceType
|
||||
if type(instance) is PowerFeed:
|
||||
return PowerFeedType
|
||||
if type(instance) is PowerOutlet:
|
||||
return PowerOutletType
|
||||
if type(instance) is PowerPort:
|
||||
return PowerPortType
|
||||
if type(instance) is RearPort:
|
||||
return RearPortType
|
||||
|
||||
|
||||
class CableTerminationTerminationType(graphene.Union):
|
||||
class Meta:
|
||||
types = (
|
||||
CircuitTerminationType,
|
||||
ConsolePortType,
|
||||
ConsoleServerPortType,
|
||||
FrontPortType,
|
||||
InterfaceType,
|
||||
PowerFeedType,
|
||||
PowerOutletType,
|
||||
PowerPortType,
|
||||
RearPortType,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def resolve_type(cls, instance, info):
|
||||
if type(instance) is CircuitTermination:
|
||||
return CircuitTerminationType
|
||||
if type(instance) is ConsolePortType:
|
||||
return ConsolePortType
|
||||
if type(instance) is ConsoleServerPort:
|
||||
return ConsoleServerPortType
|
||||
if type(instance) is FrontPort:
|
||||
return FrontPortType
|
||||
if type(instance) is Interface:
|
||||
return InterfaceType
|
||||
if type(instance) is PowerFeed:
|
||||
return PowerFeedType
|
||||
if type(instance) is PowerOutlet:
|
||||
return PowerOutletType
|
||||
if type(instance) is PowerPort:
|
||||
return PowerPortType
|
||||
if type(instance) is RearPort:
|
||||
return RearPortType
|
||||
|
||||
|
||||
class InventoryItemTemplateComponentType(graphene.Union):
|
||||
class InventoryItemTemplateComponentType:
|
||||
class Meta:
|
||||
types = (
|
||||
ConsolePortTemplateType,
|
||||
|
@ -139,7 +66,7 @@ class InventoryItemTemplateComponentType(graphene.Union):
|
|||
return RearPortTemplateType
|
||||
|
||||
|
||||
class InventoryItemComponentType(graphene.Union):
|
||||
class InventoryItemComponentType:
|
||||
class Meta:
|
||||
types = (
|
||||
ConsolePortType,
|
||||
|
@ -169,7 +96,7 @@ class InventoryItemComponentType(graphene.Union):
|
|||
return RearPortType
|
||||
|
||||
|
||||
class ConnectedEndpointType(graphene.Union):
|
||||
class ConnectedEndpointType:
|
||||
class Meta:
|
||||
types = (
|
||||
CircuitTerminationType,
|
||||
|
|
|
@ -1,20 +1,47 @@
|
|||
import graphene
|
||||
from typing import Annotated, List, Union
|
||||
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
|
||||
__all__ = (
|
||||
'CabledObjectMixin',
|
||||
'PathEndpointMixin',
|
||||
)
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class CabledObjectMixin:
|
||||
link_peers = graphene.List('dcim.graphql.gfk_mixins.LinkPeerType')
|
||||
cable: Annotated["CableType", strawberry.lazy('dcim.graphql.types')] | None
|
||||
|
||||
def resolve_cable_end(self, info):
|
||||
# Handle empty values
|
||||
return self.cable_end or None
|
||||
|
||||
def resolve_link_peers(self, info):
|
||||
@strawberry_django.field
|
||||
def link_peers(self) -> List[Annotated[Union[
|
||||
Annotated["CircuitTerminationType", strawberry.lazy('circuits.graphql.types')],
|
||||
Annotated["ConsolePortType", strawberry.lazy('dcim.graphql.types')],
|
||||
Annotated["ConsoleServerPortType", strawberry.lazy('dcim.graphql.types')],
|
||||
Annotated["FrontPortType", strawberry.lazy('dcim.graphql.types')],
|
||||
Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')],
|
||||
Annotated["PowerFeedType", strawberry.lazy('dcim.graphql.types')],
|
||||
Annotated["PowerOutletType", strawberry.lazy('dcim.graphql.types')],
|
||||
Annotated["PowerPortType", strawberry.lazy('dcim.graphql.types')],
|
||||
Annotated["RearPortType", strawberry.lazy('dcim.graphql.types')],
|
||||
], strawberry.union("LinkPeerType")]]:
|
||||
return self.link_peers
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class PathEndpointMixin:
|
||||
connected_endpoints = graphene.List('dcim.graphql.gfk_mixins.ConnectedEndpointType')
|
||||
|
||||
def resolve_connected_endpoints(self, info):
|
||||
# Handle empty values
|
||||
@strawberry_django.field
|
||||
def connected_endpoints(self) -> List[Annotated[Union[
|
||||
Annotated["CircuitTerminationType", strawberry.lazy('circuits.graphql.types')],
|
||||
Annotated["ConsolePortType", strawberry.lazy('dcim.graphql.types')],
|
||||
Annotated["ConsoleServerPortType", strawberry.lazy('dcim.graphql.types')],
|
||||
Annotated["FrontPortType", strawberry.lazy('dcim.graphql.types')],
|
||||
Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')],
|
||||
Annotated["PowerFeedType", strawberry.lazy('dcim.graphql.types')],
|
||||
Annotated["PowerOutletType", strawberry.lazy('dcim.graphql.types')],
|
||||
Annotated["PowerPortType", strawberry.lazy('dcim.graphql.types')],
|
||||
Annotated["ProviderNetworkType", strawberry.lazy('circuits.graphql.types')],
|
||||
Annotated["RearPortType", strawberry.lazy('dcim.graphql.types')],
|
||||
], strawberry.union("ConnectedEndpointType")]]:
|
||||
return self.connected_endpoints or None
|
||||
|
|
|
@ -1,249 +1,210 @@
|
|||
import graphene
|
||||
from typing import List
|
||||
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
|
||||
from netbox.graphql.fields import ObjectField, ObjectListField
|
||||
from .types import *
|
||||
from dcim import models
|
||||
from .types import VirtualDeviceContextType
|
||||
from utilities.graphql_optimizer import gql_query_optimizer
|
||||
|
||||
|
||||
class DCIMQuery(graphene.ObjectType):
|
||||
cable = ObjectField(CableType)
|
||||
cable_list = ObjectListField(CableType)
|
||||
|
||||
def resolve_cable_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.Cable.objects.all(), info)
|
||||
|
||||
console_port = ObjectField(ConsolePortType)
|
||||
console_port_list = ObjectListField(ConsolePortType)
|
||||
|
||||
def resolve_console_port_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.ConsolePort.objects.all(), info)
|
||||
|
||||
console_port_template = ObjectField(ConsolePortTemplateType)
|
||||
console_port_template_list = ObjectListField(ConsolePortTemplateType)
|
||||
|
||||
def resolve_console_port_template_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.ConsolePortTemplate.objects.all(), info)
|
||||
|
||||
console_server_port = ObjectField(ConsoleServerPortType)
|
||||
console_server_port_list = ObjectListField(ConsoleServerPortType)
|
||||
|
||||
def resolve_console_server_port_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.ConsoleServerPort.objects.all(), info)
|
||||
|
||||
console_server_port_template = ObjectField(ConsoleServerPortTemplateType)
|
||||
console_server_port_template_list = ObjectListField(ConsoleServerPortTemplateType)
|
||||
|
||||
def resolve_console_server_port_template_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.ConsoleServerPortTemplate.objects.all(), info)
|
||||
|
||||
device = ObjectField(DeviceType)
|
||||
device_list = ObjectListField(DeviceType)
|
||||
|
||||
def resolve_device_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.Device.objects.all(), info)
|
||||
|
||||
device_bay = ObjectField(DeviceBayType)
|
||||
device_bay_list = ObjectListField(DeviceBayType)
|
||||
|
||||
def resolve_device_bay_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.DeviceBay.objects.all(), info)
|
||||
|
||||
device_bay_template = ObjectField(DeviceBayTemplateType)
|
||||
device_bay_template_list = ObjectListField(DeviceBayTemplateType)
|
||||
|
||||
def resolve_device_bay_template_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.DeviceBayTemplate.objects.all(), info)
|
||||
|
||||
device_role = ObjectField(DeviceRoleType)
|
||||
device_role_list = ObjectListField(DeviceRoleType)
|
||||
|
||||
def resolve_device_role_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.DeviceRole.objects.all(), info)
|
||||
|
||||
device_type = ObjectField(DeviceTypeType)
|
||||
device_type_list = ObjectListField(DeviceTypeType)
|
||||
|
||||
def resolve_device_type_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.DeviceType.objects.all(), info)
|
||||
|
||||
front_port = ObjectField(FrontPortType)
|
||||
front_port_list = ObjectListField(FrontPortType)
|
||||
|
||||
def resolve_front_port_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.FrontPort.objects.all(), info)
|
||||
|
||||
front_port_template = ObjectField(FrontPortTemplateType)
|
||||
front_port_template_list = ObjectListField(FrontPortTemplateType)
|
||||
|
||||
def resolve_front_port_template_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.FrontPortTemplate.objects.all(), info)
|
||||
|
||||
interface = ObjectField(InterfaceType)
|
||||
interface_list = ObjectListField(InterfaceType)
|
||||
|
||||
def resolve_interface_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.Interface.objects.all(), info)
|
||||
|
||||
interface_template = ObjectField(InterfaceTemplateType)
|
||||
interface_template_list = ObjectListField(InterfaceTemplateType)
|
||||
|
||||
def resolve_interface_template_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.InterfaceTemplate.objects.all(), info)
|
||||
|
||||
inventory_item = ObjectField(InventoryItemType)
|
||||
inventory_item_list = ObjectListField(InventoryItemType)
|
||||
|
||||
def resolve_inventory_item_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.InventoryItem.objects.all(), info)
|
||||
|
||||
inventory_item_role = ObjectField(InventoryItemRoleType)
|
||||
inventory_item_role_list = ObjectListField(InventoryItemRoleType)
|
||||
|
||||
def resolve_inventory_item_role_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.InventoryItemRole.objects.all(), info)
|
||||
|
||||
inventory_item_template = ObjectField(InventoryItemTemplateType)
|
||||
inventory_item_template_list = ObjectListField(InventoryItemTemplateType)
|
||||
|
||||
def resolve_inventory_item_template_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.InventoryItemTemplate.objects.all(), info)
|
||||
|
||||
location = ObjectField(LocationType)
|
||||
location_list = ObjectListField(LocationType)
|
||||
|
||||
def resolve_location_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.Location.objects.all(), info)
|
||||
|
||||
manufacturer = ObjectField(ManufacturerType)
|
||||
manufacturer_list = ObjectListField(ManufacturerType)
|
||||
|
||||
def resolve_manufacturer_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.Manufacturer.objects.all(), info)
|
||||
|
||||
module = ObjectField(ModuleType)
|
||||
module_list = ObjectListField(ModuleType)
|
||||
|
||||
def resolve_module_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.Module.objects.all(), info)
|
||||
|
||||
module_bay = ObjectField(ModuleBayType)
|
||||
module_bay_list = ObjectListField(ModuleBayType)
|
||||
|
||||
def resolve_module_bay_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.ModuleBay.objects.all(), info)
|
||||
|
||||
module_bay_template = ObjectField(ModuleBayTemplateType)
|
||||
module_bay_template_list = ObjectListField(ModuleBayTemplateType)
|
||||
|
||||
def resolve_module_bay_template_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.ModuleBayTemplate.objects.all(), info)
|
||||
|
||||
module_type = ObjectField(ModuleTypeType)
|
||||
module_type_list = ObjectListField(ModuleTypeType)
|
||||
|
||||
def resolve_module_type_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.ModuleType.objects.all(), info)
|
||||
|
||||
platform = ObjectField(PlatformType)
|
||||
platform_list = ObjectListField(PlatformType)
|
||||
|
||||
def resolve_platform_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.Platform.objects.all(), info)
|
||||
|
||||
power_feed = ObjectField(PowerFeedType)
|
||||
power_feed_list = ObjectListField(PowerFeedType)
|
||||
|
||||
def resolve_power_feed_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.PowerFeed.objects.all(), info)
|
||||
|
||||
power_outlet = ObjectField(PowerOutletType)
|
||||
power_outlet_list = ObjectListField(PowerOutletType)
|
||||
|
||||
def resolve_power_outlet_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.PowerOutlet.objects.all(), info)
|
||||
|
||||
power_outlet_template = ObjectField(PowerOutletTemplateType)
|
||||
power_outlet_template_list = ObjectListField(PowerOutletTemplateType)
|
||||
|
||||
def resolve_power_outlet_template_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.PowerOutletTemplate.objects.all(), info)
|
||||
|
||||
power_panel = ObjectField(PowerPanelType)
|
||||
power_panel_list = ObjectListField(PowerPanelType)
|
||||
|
||||
def resolve_power_panel_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.PowerPanel.objects.all(), info)
|
||||
|
||||
power_port = ObjectField(PowerPortType)
|
||||
power_port_list = ObjectListField(PowerPortType)
|
||||
|
||||
def resolve_power_port_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.PowerPort.objects.all(), info)
|
||||
|
||||
power_port_template = ObjectField(PowerPortTemplateType)
|
||||
power_port_template_list = ObjectListField(PowerPortTemplateType)
|
||||
|
||||
def resolve_power_port_template_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.PowerPortTemplate.objects.all(), info)
|
||||
|
||||
rack = ObjectField(RackType)
|
||||
rack_list = ObjectListField(RackType)
|
||||
|
||||
def resolve_rack_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.Rack.objects.all(), info)
|
||||
|
||||
rack_reservation = ObjectField(RackReservationType)
|
||||
rack_reservation_list = ObjectListField(RackReservationType)
|
||||
|
||||
def resolve_rack_reservation_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.RackReservation.objects.all(), info)
|
||||
|
||||
rack_role = ObjectField(RackRoleType)
|
||||
rack_role_list = ObjectListField(RackRoleType)
|
||||
|
||||
def resolve_rack_role_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.RackRole.objects.all(), info)
|
||||
|
||||
rear_port = ObjectField(RearPortType)
|
||||
rear_port_list = ObjectListField(RearPortType)
|
||||
|
||||
def resolve_rear_port_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.RearPort.objects.all(), info)
|
||||
|
||||
rear_port_template = ObjectField(RearPortTemplateType)
|
||||
rear_port_template_list = ObjectListField(RearPortTemplateType)
|
||||
|
||||
def resolve_rear_port_template_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.RearPortTemplate.objects.all(), info)
|
||||
|
||||
region = ObjectField(RegionType)
|
||||
region_list = ObjectListField(RegionType)
|
||||
|
||||
def resolve_region_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.Region.objects.all(), info)
|
||||
|
||||
site = ObjectField(SiteType)
|
||||
site_list = ObjectListField(SiteType)
|
||||
|
||||
def resolve_site_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.Site.objects.all(), info)
|
||||
|
||||
site_group = ObjectField(SiteGroupType)
|
||||
site_group_list = ObjectListField(SiteGroupType)
|
||||
|
||||
def resolve_site_group_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.SiteGroup.objects.all(), info)
|
||||
|
||||
virtual_chassis = ObjectField(VirtualChassisType)
|
||||
virtual_chassis_list = ObjectListField(VirtualChassisType)
|
||||
|
||||
def resolve_virtual_chassis_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.VirtualChassis.objects.all(), info)
|
||||
|
||||
virtual_device_context = ObjectField(VirtualDeviceContextType)
|
||||
virtual_device_context_list = ObjectListField(VirtualDeviceContextType)
|
||||
|
||||
def resolve_virtual_device_context_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.VirtualDeviceContext.objects.all(), info)
|
||||
from .types import *
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class DCIMQuery:
|
||||
@strawberry.field
|
||||
def cable(self, id: int) -> CableType:
|
||||
return models.Cable.objects.get(pk=id)
|
||||
cable_list: List[CableType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def console_port(self, id: int) -> ConsolePortType:
|
||||
return models.ConsolePort.objects.get(pk=id)
|
||||
console_port_list: List[ConsolePortType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def console_port_template(self, id: int) -> ConsolePortTemplateType:
|
||||
return models.ConsolePortTemplate.objects.get(pk=id)
|
||||
console_port_template_list: List[ConsolePortTemplateType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def console_server_port(self, id: int) -> ConsoleServerPortType:
|
||||
return models.ConsoleServerPort.objects.get(pk=id)
|
||||
console_server_port_list: List[ConsoleServerPortType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def console_server_port_template(self, id: int) -> ConsoleServerPortTemplateType:
|
||||
return models.ConsoleServerPortTemplate.objects.get(pk=id)
|
||||
console_server_port_template_list: List[ConsoleServerPortTemplateType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def device(self, id: int) -> DeviceType:
|
||||
return models.Device.objects.get(pk=id)
|
||||
device_list: List[DeviceType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def device_bay(self, id: int) -> DeviceBayType:
|
||||
return models.DeviceBay.objects.get(pk=id)
|
||||
device_bay_list: List[DeviceBayType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def device_bay_template(self, id: int) -> DeviceBayTemplateType:
|
||||
return models.DeviceBayTemplate.objects.get(pk=id)
|
||||
device_bay_template_list: List[DeviceBayTemplateType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def device_role(self, id: int) -> DeviceRoleType:
|
||||
return models.DeviceRole.objects.get(pk=id)
|
||||
device_role_list: List[DeviceRoleType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def device_type(self, id: int) -> DeviceTypeType:
|
||||
return models.DeviceType.objects.get(pk=id)
|
||||
device_type_list: List[DeviceTypeType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def front_port(self, id: int) -> FrontPortType:
|
||||
return models.FrontPort.objects.get(pk=id)
|
||||
front_port_list: List[FrontPortType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def front_port_template(self, id: int) -> FrontPortTemplateType:
|
||||
return models.FrontPortTemplate.objects.get(pk=id)
|
||||
front_port_template_list: List[FrontPortTemplateType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def interface(self, id: int) -> InterfaceType:
|
||||
return models.Interface.objects.get(pk=id)
|
||||
interface_list: List[InterfaceType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def interface_template(self, id: int) -> InterfaceTemplateType:
|
||||
return models.InterfaceTemplate.objects.get(pk=id)
|
||||
interface_template_list: List[InterfaceTemplateType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def inventory_item(self, id: int) -> InventoryItemType:
|
||||
return models.InventoryItem.objects.get(pk=id)
|
||||
inventory_item_list: List[InventoryItemType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def inventory_item_role(self, id: int) -> InventoryItemRoleType:
|
||||
return models.InventoryItemRole.objects.get(pk=id)
|
||||
inventory_item_role_list: List[InventoryItemRoleType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def inventory_item_template(self, id: int) -> InventoryItemTemplateType:
|
||||
return models.InventoryItemTemplate.objects.get(pk=id)
|
||||
inventory_item_template_list: List[InventoryItemTemplateType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def location(self, id: int) -> LocationType:
|
||||
return models.Location.objects.get(pk=id)
|
||||
location_list: List[LocationType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def manufacturer(self, id: int) -> ManufacturerType:
|
||||
return models.Manufacturer.objects.get(pk=id)
|
||||
manufacturer_list: List[ManufacturerType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def module(self, id: int) -> ModuleType:
|
||||
return models.Module.objects.get(pk=id)
|
||||
module_list: List[ModuleType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def module_bay(self, id: int) -> ModuleBayType:
|
||||
return models.ModuleBay.objects.get(pk=id)
|
||||
module_bay_list: List[ModuleBayType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def module_bay_template(self, id: int) -> ModuleBayTemplateType:
|
||||
return models.ModuleBayTemplate.objects.get(pk=id)
|
||||
module_bay_template_list: List[ModuleBayTemplateType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def module_type(self, id: int) -> ModuleTypeType:
|
||||
return models.ModuleType.objects.get(pk=id)
|
||||
module_type_list: List[ModuleTypeType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def platform(self, id: int) -> PlatformType:
|
||||
return models.Platform.objects.get(pk=id)
|
||||
platform_list: List[PlatformType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def power_feed(self, id: int) -> PowerFeedType:
|
||||
return models.PowerFeed.objects.get(pk=id)
|
||||
power_feed_list: List[PowerFeedType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def power_outlet(self, id: int) -> PowerOutletType:
|
||||
return models.PowerOutlet.objects.get(pk=id)
|
||||
power_outlet_list: List[PowerOutletType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def power_outlet_template(self, id: int) -> PowerOutletTemplateType:
|
||||
return models.PowerOutletTemplate.objects.get(pk=id)
|
||||
power_outlet_template_list: List[PowerOutletTemplateType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def power_panel(self, id: int) -> PowerPanelType:
|
||||
return models.PowerPanel.objects.get(id=id)
|
||||
power_panel_list: List[PowerPanelType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def power_port(self, id: int) -> PowerPortType:
|
||||
return models.PowerPort.objects.get(id=id)
|
||||
power_port_list: List[PowerPortType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def power_port_template(self, id: int) -> PowerPortTemplateType:
|
||||
return models.PowerPortTemplate.objects.get(id=id)
|
||||
power_port_template_list: List[PowerPortTemplateType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def rack(self, id: int) -> RackType:
|
||||
return models.Rack.objects.get(id=id)
|
||||
rack_list: List[RackType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def rack_reservation(self, id: int) -> RackReservationType:
|
||||
return models.RackReservation.objects.get(id=id)
|
||||
rack_reservation_list: List[RackReservationType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def rack_role(self, id: int) -> RackRoleType:
|
||||
return models.RackRole.objects.get(id=id)
|
||||
rack_role_list: List[RackRoleType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def rear_port(self, id: int) -> RearPortType:
|
||||
return models.RearPort.objects.get(id=id)
|
||||
rear_port_list: List[RearPortType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def rear_port_template(self, id: int) -> RearPortTemplateType:
|
||||
return models.RearPortTemplate.objects.get(id=id)
|
||||
rear_port_template_list: List[RearPortTemplateType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def region(self, id: int) -> RegionType:
|
||||
return models.Region.objects.get(id=id)
|
||||
region_list: List[RegionType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def site(self, id: int) -> SiteType:
|
||||
return models.Site.objects.get(id=id)
|
||||
site_list: List[SiteType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def site_group(self, id: int) -> SiteGroupType:
|
||||
return models.SiteGroup.objects.get(id=id)
|
||||
site_group_list: List[SiteGroupType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def virtual_chassis(self, id: int) -> VirtualChassisType:
|
||||
return models.VirtualChassis.objects.get(id=id)
|
||||
virtual_chassis_list: List[VirtualChassisType] = strawberry_django.field()
|
||||
|
||||
@strawberry.field
|
||||
def virtual_device_context(self, id: int) -> VirtualDeviceContextType:
|
||||
return models.VirtualDeviceContext.objects.get(id=id)
|
||||
virtual_device_context_list: List[VirtualDeviceContextType] = strawberry_django.field()
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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),
|
||||
),
|
||||
]
|
|
@ -15,9 +15,9 @@ from dcim.constants import *
|
|||
from dcim.fields import PathField
|
||||
from dcim.utils import decompile_path_node, object_to_path_node
|
||||
from netbox.models import ChangeLoggedModel, PrimaryModel
|
||||
from utilities.conversion import to_meters
|
||||
from utilities.fields import ColorField
|
||||
from utilities.querysets import RestrictedQuerySet
|
||||
from utilities.utils import to_meters
|
||||
from wireless.models import WirelessLink
|
||||
from .device_components import FrontPort, RearPort, PathEndpoint
|
||||
|
||||
|
|
|
@ -12,8 +12,8 @@ from mptt.models import MPTTModel, TreeForeignKey
|
|||
from dcim.choices import *
|
||||
from dcim.constants import *
|
||||
from dcim.fields import MACAddressField, WWNField
|
||||
from netbox.choices import ColorChoices
|
||||
from netbox.models import OrganizationalModel, NetBoxModel
|
||||
from utilities.choices import ColorChoices
|
||||
from utilities.fields import ColorField, NaturalOrderingField
|
||||
from utilities.mptt import TreeManager
|
||||
from utilities.ordering import naturalize_interface
|
||||
|
|
|
@ -18,10 +18,10 @@ from dcim.choices import *
|
|||
from dcim.constants import *
|
||||
from extras.models import ConfigContextModel, CustomField
|
||||
from extras.querysets import ConfigContextModelQuerySet
|
||||
from netbox.choices import ColorChoices
|
||||
from netbox.config import ConfigItem
|
||||
from netbox.models import OrganizationalModel, PrimaryModel
|
||||
from netbox.models.features import ContactsMixin, ImageAttachmentsMixin
|
||||
from utilities.choices import ColorChoices
|
||||
from utilities.fields import ColorField, CounterCacheField, NaturalOrderingField
|
||||
from utilities.tracking import TrackingModelMixin
|
||||
from .device_components import *
|
||||
|
|
|
@ -2,7 +2,7 @@ from django.core.exceptions import ValidationError
|
|||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from dcim.choices import *
|
||||
from utilities.utils import to_grams
|
||||
from utilities.conversion import to_grams
|
||||
|
||||
__all__ = (
|
||||
'RenderConfigMixin',
|
||||
|
|
|
@ -14,11 +14,12 @@ from django.utils.translation import gettext_lazy as _
|
|||
from dcim.choices import *
|
||||
from dcim.constants import *
|
||||
from dcim.svg import RackElevationSVG
|
||||
from netbox.choices import ColorChoices
|
||||
from netbox.models import OrganizationalModel, PrimaryModel
|
||||
from netbox.models.features import ContactsMixin, ImageAttachmentsMixin
|
||||
from utilities.choices import ColorChoices
|
||||
from utilities.conversion import to_grams
|
||||
from utilities.data import array_to_string, drange
|
||||
from utilities.fields import ColorField, NaturalOrderingField
|
||||
from utilities.utils import array_to_string, drange, to_grams
|
||||
from .device_components import PowerPort
|
||||
from .devices import Device, Module
|
||||
from .mixins import WeightMixin
|
||||
|
|
|
@ -275,6 +275,12 @@ class Location(ContactsMixin, ImageAttachmentsMixin, NestedGroupModel):
|
|||
blank=True,
|
||||
null=True
|
||||
)
|
||||
facility = models.CharField(
|
||||
verbose_name=_('facility'),
|
||||
max_length=50,
|
||||
blank=True,
|
||||
help_text=_('Local facility ID or description')
|
||||
)
|
||||
|
||||
# Generic relations
|
||||
vlan_groups = GenericRelation(
|
||||
|
@ -284,7 +290,7 @@ class Location(ContactsMixin, ImageAttachmentsMixin, NestedGroupModel):
|
|||
related_query_name='location'
|
||||
)
|
||||
|
||||
clone_fields = ('site', 'parent', 'status', 'tenant', 'description')
|
||||
clone_fields = ('site', 'parent', 'status', 'tenant', 'facility', 'description')
|
||||
prerequisite_models = (
|
||||
'dcim.Site',
|
||||
)
|
||||
|
|
|
@ -132,10 +132,11 @@ class LocationIndex(SearchIndex):
|
|||
model = models.Location
|
||||
fields = (
|
||||
('name', 100),
|
||||
('facility', 100),
|
||||
('slug', 110),
|
||||
('description', 500),
|
||||
)
|
||||
display_attrs = ('site', 'status', 'tenant', 'description')
|
||||
display_attrs = ('site', 'status', 'tenant', 'facility', 'description')
|
||||
|
||||
|
||||
@register_search
|
||||
|
|
|
@ -6,7 +6,7 @@ from svgwrite.text import Text
|
|||
from django.conf import settings
|
||||
|
||||
from dcim.constants import CABLE_TRACE_SVG_DEFAULT_WIDTH
|
||||
from utilities.utils import foreground_color
|
||||
from utilities.html import foreground_color
|
||||
|
||||
|
||||
__all__ = (
|
||||
|
|
|
@ -14,7 +14,8 @@ from django.urls import reverse
|
|||
from django.utils.http import urlencode
|
||||
|
||||
from netbox.config import get_config
|
||||
from utilities.utils import foreground_color, array_to_ranges
|
||||
from utilities.data import array_to_ranges
|
||||
from utilities.html import foreground_color
|
||||
from dcim.constants import RACK_ELEVATION_BORDER_WIDTH
|
||||
|
||||
|
||||
|
|
|
@ -152,7 +152,9 @@ class LocationTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
|
|||
class Meta(NetBoxTable.Meta):
|
||||
model = Location
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'site', 'status', 'tenant', 'tenant_group', 'rack_count', 'device_count', 'description',
|
||||
'slug', 'contacts', 'tags', 'actions', 'created', 'last_updated',
|
||||
'pk', 'id', 'name', 'site', 'status', 'facility', 'tenant', 'tenant_group', 'rack_count', 'device_count',
|
||||
'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')
|
||||
|
|
|
@ -6,13 +6,12 @@ from dcim.choices import *
|
|||
from dcim.filtersets import *
|
||||
from dcim.models import *
|
||||
from ipam.models import ASN, IPAddress, RIR, VRF
|
||||
from netbox.choices import ColorChoices
|
||||
from tenancy.models import Tenant, TenantGroup
|
||||
from utilities.choices import ColorChoices
|
||||
from utilities.testing import ChangeLoggedFilterSetTests, create_test_device
|
||||
from virtualization.models import Cluster, ClusterType
|
||||
from wireless.choices import WirelessChannelChoices, WirelessRoleChoices
|
||||
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
|
@ -359,9 +358,9 @@ class LocationTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||
location.save()
|
||||
|
||||
locations = (
|
||||
Location(name='Location 1A', slug='location-1a', site=sites[0], parent=parent_locations[0], status=LocationStatusChoices.STATUS_PLANNED, description='foobar1'),
|
||||
Location(name='Location 2A', slug='location-2a', site=sites[1], parent=parent_locations[1], status=LocationStatusChoices.STATUS_STAGING, description='foobar2'),
|
||||
Location(name='Location 3A', slug='location-3a', site=sites[2], parent=parent_locations[2], status=LocationStatusChoices.STATUS_DECOMMISSIONING, description='foobar3'),
|
||||
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 2A', slug='location-2a', site=sites[1], parent=parent_locations[1], status=LocationStatusChoices.STATUS_STAGING, facility='Facility 2', description='foobar2'),
|
||||
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:
|
||||
location.save()
|
||||
|
@ -390,6 +389,10 @@ class LocationTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||
params = {'status': [LocationStatusChoices.STATUS_PLANNED, LocationStatusChoices.STATUS_STAGING]}
|
||||
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):
|
||||
params = {'description': ['foobar1', 'foobar2']}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
|
|
@ -7,7 +7,7 @@ from dcim.choices import *
|
|||
from dcim.models import *
|
||||
from extras.models import CustomField
|
||||
from tenancy.models import Tenant
|
||||
from utilities.utils import drange
|
||||
from utilities.data import drange
|
||||
|
||||
|
||||
class LocationTestCase(TestCase):
|
||||
|
|
|
@ -11,12 +11,11 @@ from dcim.choices import *
|
|||
from dcim.constants import *
|
||||
from dcim.models import *
|
||||
from ipam.models import ASN, RIR, VLAN, VRF
|
||||
from netbox.choices import CSVDelimiterChoices, ImportFormatChoices
|
||||
from tenancy.models import Tenant
|
||||
from utilities.choices import CSVDelimiterChoices, ImportFormatChoices
|
||||
from utilities.testing import ViewTestCases, create_tags, create_test_device, post_data
|
||||
from wireless.models import WirelessLAN
|
||||
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
|
@ -213,6 +212,7 @@ class LocationTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
|
|||
'slug': 'location-x',
|
||||
'site': site.pk,
|
||||
'status': LocationStatusChoices.STATUS_PLANNED,
|
||||
'facility': 'Facility X',
|
||||
'tenant': tenant.pk,
|
||||
'description': 'A new location',
|
||||
'tags': [t.pk for t in tags],
|
||||
|
|
|
@ -25,8 +25,8 @@ from tenancy.views import ObjectContactsView
|
|||
from utilities.forms import ConfirmationForm
|
||||
from utilities.paginator import EnhancedPaginator, get_paginate_count
|
||||
from utilities.permissions import get_permission_for_model
|
||||
from utilities.query import count_related
|
||||
from utilities.query_functions import CollateAsChar
|
||||
from utilities.utils import count_related
|
||||
from utilities.views import GetReturnURLMixin, ObjectPermissionRequiredMixin, ViewTab, register_model_view
|
||||
from virtualization.models import VirtualMachine
|
||||
from . import filtersets, forms, tables
|
||||
|
@ -727,7 +727,6 @@ class RackNonRackedView(generic.ObjectChildrenView):
|
|||
class RackEditView(generic.ObjectEditView):
|
||||
queryset = Rack.objects.all()
|
||||
form = forms.RackForm
|
||||
template_name = 'dcim/rack_edit.html'
|
||||
|
||||
|
||||
@register_model_view(Rack, 'delete')
|
||||
|
@ -2925,14 +2924,12 @@ class InventoryItemView(generic.ObjectView):
|
|||
class InventoryItemEditView(generic.ObjectEditView):
|
||||
queryset = InventoryItem.objects.all()
|
||||
form = forms.InventoryItemForm
|
||||
template_name = 'dcim/inventoryitem_edit.html'
|
||||
|
||||
|
||||
class InventoryItemCreateView(generic.ComponentCreateView):
|
||||
queryset = InventoryItem.objects.all()
|
||||
form = forms.InventoryItemCreateForm
|
||||
model_form = forms.InventoryItemForm
|
||||
template_name = 'dcim/inventoryitem_edit.html'
|
||||
|
||||
|
||||
@register_model_view(InventoryItem, 'delete')
|
||||
|
|
|
@ -20,7 +20,7 @@ from netbox.api.metadata import ContentTypeMetadata
|
|||
from netbox.api.renderers import TextRenderer
|
||||
from netbox.api.viewsets import NetBoxModelViewSet
|
||||
from utilities.exceptions import RQWorkerNotRunningException
|
||||
from utilities.utils import copy_safe_request
|
||||
from utilities.request import copy_safe_request
|
||||
from . import serializers
|
||||
from .mixins import ConfigTemplateRenderMixin
|
||||
|
||||
|
|
|
@ -2,7 +2,8 @@ import logging
|
|||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from utilities.choices import ButtonColorChoices, ChoiceSet
|
||||
from netbox.choices import ButtonColorChoices
|
||||
from utilities.choices import ChoiceSet
|
||||
|
||||
|
||||
#
|
||||
|
|
|
@ -14,10 +14,12 @@ from django.utils.translation import gettext as _
|
|||
|
||||
from core.models import ObjectType
|
||||
from extras.choices import BookmarkOrderingChoices
|
||||
from utilities.choices import ButtonColorChoices
|
||||
from netbox.choices import ButtonColorChoices
|
||||
from utilities.object_types import object_type_identifier, object_type_name
|
||||
from utilities.permissions import get_permission_for_model
|
||||
from utilities.querydict import dict_to_querydict
|
||||
from utilities.templatetags.builtins.filters import render_markdown
|
||||
from utilities.utils import content_type_identifier, content_type_name, dict_to_querydict, get_viewname
|
||||
from utilities.views import get_viewname
|
||||
from .utils import register_widget
|
||||
|
||||
__all__ = (
|
||||
|
@ -33,15 +35,15 @@ __all__ = (
|
|||
|
||||
def get_object_type_choices():
|
||||
return [
|
||||
(content_type_identifier(ct), content_type_name(ct))
|
||||
for ct in ObjectType.objects.public().order_by('app_label', 'model')
|
||||
(object_type_identifier(ot), object_type_name(ot))
|
||||
for ot in ObjectType.objects.public().order_by('app_label', 'model')
|
||||
]
|
||||
|
||||
|
||||
def get_bookmarks_object_type_choices():
|
||||
return [
|
||||
(content_type_identifier(ct), content_type_name(ct))
|
||||
for ct in ObjectType.objects.with_feature('bookmarks').order_by('app_label', 'model')
|
||||
(object_type_identifier(ot), object_type_name(ot))
|
||||
for ot in ObjectType.objects.with_feature('bookmarks').order_by('app_label', 'model')
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
import logging
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.utils import timezone
|
||||
from django.utils.module_loading import import_string
|
||||
from django.utils.translation import gettext as _
|
||||
|
@ -15,9 +12,9 @@ from netbox.constants import RQ_QUEUE_DEFAULT
|
|||
from netbox.registry import registry
|
||||
from utilities.api import get_serializer_for_model
|
||||
from utilities.rqworker import get_rq_retry
|
||||
from utilities.utils import serialize_object
|
||||
from utilities.serialization import serialize_object
|
||||
from .choices import *
|
||||
from .models import EventRule, ScriptModule
|
||||
from .models import EventRule
|
||||
|
||||
logger = logging.getLogger('netbox.events_processor')
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES, FilterForm, add_blank_ch
|
|||
from utilities.forms.fields import (
|
||||
ContentTypeChoiceField, ContentTypeMultipleChoiceField, DynamicModelMultipleChoiceField, TagFilterField,
|
||||
)
|
||||
from utilities.forms.rendering import FieldSet
|
||||
from utilities.forms.widgets import APISelectMultiple, DateTimePicker
|
||||
from virtualization.models import Cluster, ClusterGroup, ClusterType
|
||||
|
||||
|
@ -36,11 +37,11 @@ __all__ = (
|
|||
|
||||
class CustomFieldFilterForm(SavedFiltersMixin, FilterForm):
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id')),
|
||||
(_('Attributes'), (
|
||||
FieldSet('q', 'filter_id'),
|
||||
FieldSet(
|
||||
'type', 'related_object_type_id', 'group_name', 'weight', 'required', 'choice_set_id', 'ui_visible',
|
||||
'ui_editable', 'is_cloneable',
|
||||
)),
|
||||
'ui_editable', 'is_cloneable', name=_('Attributes')
|
||||
),
|
||||
)
|
||||
related_object_type_id = ContentTypeMultipleChoiceField(
|
||||
queryset=ObjectType.objects.with_feature('custom_fields'),
|
||||
|
@ -93,8 +94,8 @@ class CustomFieldFilterForm(SavedFiltersMixin, FilterForm):
|
|||
|
||||
class CustomFieldChoiceSetFilterForm(SavedFiltersMixin, FilterForm):
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id')),
|
||||
(_('Choices'), ('base_choices', 'choice')),
|
||||
FieldSet('q', 'filter_id'),
|
||||
FieldSet('base_choices', 'choice', name=_('Choices')),
|
||||
)
|
||||
base_choices = forms.MultipleChoiceField(
|
||||
choices=CustomFieldChoiceSetBaseChoices,
|
||||
|
@ -107,8 +108,8 @@ class CustomFieldChoiceSetFilterForm(SavedFiltersMixin, FilterForm):
|
|||
|
||||
class CustomLinkFilterForm(SavedFiltersMixin, FilterForm):
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id')),
|
||||
(_('Attributes'), ('object_type', 'enabled', 'new_window', 'weight')),
|
||||
FieldSet('q', 'filter_id'),
|
||||
FieldSet('object_type', 'enabled', 'new_window', 'weight', name=_('Attributes')),
|
||||
)
|
||||
object_type = ContentTypeMultipleChoiceField(
|
||||
label=_('Object types'),
|
||||
|
@ -137,9 +138,9 @@ class CustomLinkFilterForm(SavedFiltersMixin, FilterForm):
|
|||
|
||||
class ExportTemplateFilterForm(SavedFiltersMixin, FilterForm):
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id')),
|
||||
(_('Data'), ('data_source_id', 'data_file_id')),
|
||||
(_('Attributes'), ('object_type_id', 'mime_type', 'file_extension', 'as_attachment')),
|
||||
FieldSet('q', 'filter_id'),
|
||||
FieldSet('data_source_id', 'data_file_id', name=_('Data')),
|
||||
FieldSet('object_type_id', 'mime_type', 'file_extension', 'as_attachment', name=_('Attributes')),
|
||||
)
|
||||
data_source_id = DynamicModelMultipleChoiceField(
|
||||
queryset=DataSource.objects.all(),
|
||||
|
@ -178,8 +179,8 @@ class ExportTemplateFilterForm(SavedFiltersMixin, FilterForm):
|
|||
|
||||
class ImageAttachmentFilterForm(SavedFiltersMixin, FilterForm):
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id')),
|
||||
(_('Attributes'), ('object_type_id', 'name',)),
|
||||
FieldSet('q', 'filter_id'),
|
||||
FieldSet('object_type_id', 'name', name=_('Attributes')),
|
||||
)
|
||||
object_type_id = ContentTypeChoiceField(
|
||||
label=_('Object type'),
|
||||
|
@ -194,8 +195,8 @@ class ImageAttachmentFilterForm(SavedFiltersMixin, FilterForm):
|
|||
|
||||
class SavedFilterFilterForm(SavedFiltersMixin, FilterForm):
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id')),
|
||||
(_('Attributes'), ('object_type', 'enabled', 'shared', 'weight')),
|
||||
FieldSet('q', 'filter_id'),
|
||||
FieldSet('object_type', 'enabled', 'shared', 'weight', name=_('Attributes')),
|
||||
)
|
||||
object_type = ContentTypeMultipleChoiceField(
|
||||
label=_('Object types'),
|
||||
|
@ -225,8 +226,8 @@ class SavedFilterFilterForm(SavedFiltersMixin, FilterForm):
|
|||
class WebhookFilterForm(NetBoxModelFilterSetForm):
|
||||
model = Webhook
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id', 'tag')),
|
||||
(_('Attributes'), ('payload_url', 'http_method', 'http_content_type')),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('payload_url', 'http_method', 'http_content_type', name=_('Attributes')),
|
||||
)
|
||||
http_content_type = forms.CharField(
|
||||
label=_('HTTP content type'),
|
||||
|
@ -249,9 +250,9 @@ class EventRuleFilterForm(NetBoxModelFilterSetForm):
|
|||
tag = TagFilterField(model)
|
||||
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id', 'tag')),
|
||||
(_('Attributes'), ('object_type_id', 'action_type', 'enabled')),
|
||||
(_('Events'), ('type_create', 'type_update', 'type_delete', 'type_job_start', 'type_job_end')),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('object_type_id', 'action_type', 'enabled', name=_('Attributes')),
|
||||
FieldSet('type_create', 'type_update', 'type_delete', 'type_job_start', 'type_job_end', name=_('Events')),
|
||||
)
|
||||
object_type_id = ContentTypeMultipleChoiceField(
|
||||
queryset=ObjectType.objects.with_feature('event_rules'),
|
||||
|
@ -323,12 +324,12 @@ class TagFilterForm(SavedFiltersMixin, FilterForm):
|
|||
|
||||
class ConfigContextFilterForm(SavedFiltersMixin, FilterForm):
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id', 'tag_id')),
|
||||
(_('Data'), ('data_source_id', 'data_file_id')),
|
||||
(_('Location'), ('region_id', 'site_group_id', 'site_id', 'location_id')),
|
||||
(_('Device'), ('device_type_id', 'platform_id', 'role_id')),
|
||||
(_('Cluster'), ('cluster_type_id', 'cluster_group_id', 'cluster_id')),
|
||||
(_('Tenant'), ('tenant_group_id', 'tenant_id'))
|
||||
FieldSet('q', 'filter_id', 'tag_id'),
|
||||
FieldSet('data_source_id', 'data_file_id', name=_('Data')),
|
||||
FieldSet('region_id', 'site_group_id', 'site_id', 'location_id', name=_('Location')),
|
||||
FieldSet('device_type_id', 'platform_id', 'role_id', name=_('Device')),
|
||||
FieldSet('cluster_type_id', 'cluster_group_id', 'cluster_id', name=_('Cluster')),
|
||||
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant'))
|
||||
)
|
||||
data_source_id = DynamicModelMultipleChoiceField(
|
||||
queryset=DataSource.objects.all(),
|
||||
|
@ -412,8 +413,8 @@ class ConfigContextFilterForm(SavedFiltersMixin, FilterForm):
|
|||
|
||||
class ConfigTemplateFilterForm(SavedFiltersMixin, FilterForm):
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id', 'tag')),
|
||||
(_('Data'), ('data_source_id', 'data_file_id')),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('data_source_id', 'data_file_id', name=_('Data')),
|
||||
)
|
||||
data_source_id = DynamicModelMultipleChoiceField(
|
||||
queryset=DataSource.objects.all(),
|
||||
|
@ -444,9 +445,9 @@ class LocalConfigContextFilterForm(forms.Form):
|
|||
class JournalEntryFilterForm(NetBoxModelFilterSetForm):
|
||||
model = JournalEntry
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id', 'tag')),
|
||||
(_('Creation'), ('created_before', 'created_after', 'created_by_id')),
|
||||
(_('Attributes'), ('assigned_object_type_id', 'kind'))
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('created_before', 'created_after', 'created_by_id', name=_('Creation')),
|
||||
FieldSet('assigned_object_type_id', 'kind', name=_('Attributes')),
|
||||
)
|
||||
created_after = forms.DateTimeField(
|
||||
required=False,
|
||||
|
@ -482,9 +483,9 @@ class JournalEntryFilterForm(NetBoxModelFilterSetForm):
|
|||
class ObjectChangeFilterForm(SavedFiltersMixin, FilterForm):
|
||||
model = ObjectChange
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id')),
|
||||
(_('Time'), ('time_before', 'time_after')),
|
||||
(_('Attributes'), ('action', 'user_id', 'changed_object_type_id')),
|
||||
FieldSet('q', 'filter_id'),
|
||||
FieldSet('time_before', 'time_after', name=_('Time')),
|
||||
FieldSet('action', 'user_id', 'changed_object_type_id', name=_('Attributes')),
|
||||
)
|
||||
time_after = forms.DateTimeField(
|
||||
required=False,
|
||||
|
|
|
@ -17,6 +17,7 @@ from utilities.forms.fields import (
|
|||
CommentField, ContentTypeChoiceField, ContentTypeMultipleChoiceField, DynamicModelChoiceField,
|
||||
DynamicModelMultipleChoiceField, JSONField, SlugField,
|
||||
)
|
||||
from utilities.forms.rendering import FieldSet, ObjectAttribute
|
||||
from utilities.forms.widgets import ChoicesWidget, HTMXSelect
|
||||
from virtualization.models import Cluster, ClusterGroup, ClusterType
|
||||
|
||||
|
@ -54,12 +55,15 @@ class CustomFieldForm(forms.ModelForm):
|
|||
)
|
||||
|
||||
fieldsets = (
|
||||
(_('Custom Field'), (
|
||||
FieldSet(
|
||||
'object_types', 'name', 'label', 'group_name', 'type', 'related_object_type', 'required', 'description',
|
||||
)),
|
||||
(_('Behavior'), ('search_weight', 'filter_logic', 'ui_visible', 'ui_editable', 'weight', 'is_cloneable')),
|
||||
(_('Values'), ('default', 'choice_set')),
|
||||
(_('Validation'), ('validation_minimum', 'validation_maximum', 'validation_regex')),
|
||||
name=_('Custom Field')
|
||||
),
|
||||
FieldSet(
|
||||
'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:
|
||||
|
@ -128,8 +132,11 @@ class CustomLinkForm(forms.ModelForm):
|
|||
)
|
||||
|
||||
fieldsets = (
|
||||
(_('Custom Link'), ('name', 'object_types', 'weight', 'group_name', 'button_class', 'enabled', 'new_window')),
|
||||
(_('Templates'), ('link_text', 'link_url')),
|
||||
FieldSet(
|
||||
'name', 'object_types', 'weight', 'group_name', 'button_class', 'enabled', 'new_window',
|
||||
name=_('Custom Link')
|
||||
),
|
||||
FieldSet('link_text', 'link_url', name=_('Templates')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -162,9 +169,9 @@ class ExportTemplateForm(SyncedDataMixin, forms.ModelForm):
|
|||
)
|
||||
|
||||
fieldsets = (
|
||||
(_('Export Template'), ('name', 'object_types', 'description', 'template_code')),
|
||||
(_('Data Source'), ('data_source', 'data_file', 'auto_sync_enabled')),
|
||||
(_('Rendering'), ('mime_type', 'file_extension', 'as_attachment')),
|
||||
FieldSet('name', 'object_types', 'description', 'template_code', name=_('Export Template')),
|
||||
FieldSet('data_source', 'data_file', 'auto_sync_enabled', name=_('Data Source')),
|
||||
FieldSet('mime_type', 'file_extension', 'as_attachment', name=_('Rendering')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -199,8 +206,8 @@ class SavedFilterForm(forms.ModelForm):
|
|||
parameters = JSONField()
|
||||
|
||||
fieldsets = (
|
||||
(_('Saved Filter'), ('name', 'slug', 'object_types', 'description', 'weight', 'enabled', 'shared')),
|
||||
(_('Parameters'), ('parameters',)),
|
||||
FieldSet('name', 'slug', 'object_types', 'description', 'weight', 'enabled', 'shared', name=_('Saved Filter')),
|
||||
FieldSet('parameters', name=_('Parameters')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -231,11 +238,12 @@ class BookmarkForm(forms.ModelForm):
|
|||
class WebhookForm(NetBoxModelForm):
|
||||
|
||||
fieldsets = (
|
||||
(_('Webhook'), ('name', 'description', 'tags',)),
|
||||
(_('HTTP Request'), (
|
||||
FieldSet('name', 'description', 'tags', name=_('Webhook')),
|
||||
FieldSet(
|
||||
'payload_url', 'http_method', 'http_content_type', 'additional_headers', 'body_template', 'secret',
|
||||
)),
|
||||
(_('SSL'), ('ssl_verification', 'ca_file_path')),
|
||||
name=_('HTTP Request')
|
||||
),
|
||||
FieldSet('ssl_verification', 'ca_file_path', name=_('SSL')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -266,12 +274,13 @@ class EventRuleForm(NetBoxModelForm):
|
|||
)
|
||||
|
||||
fieldsets = (
|
||||
(_('Event Rule'), ('name', 'description', 'object_types', 'enabled', 'tags')),
|
||||
(_('Events'), ('type_create', 'type_update', 'type_delete', 'type_job_start', 'type_job_end')),
|
||||
(_('Conditions'), ('conditions',)),
|
||||
(_('Action'), (
|
||||
FieldSet('name', 'description', 'object_types', 'enabled', 'tags', name=_('Event Rule')),
|
||||
FieldSet('type_create', 'type_update', 'type_delete', 'type_job_start', 'type_job_end', name=_('Events')),
|
||||
FieldSet('conditions', name=_('Conditions')),
|
||||
FieldSet(
|
||||
'action_type', 'action_choice', 'action_object_type', 'action_object_id', 'action_data',
|
||||
)),
|
||||
name=_('Action')
|
||||
),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -360,7 +369,7 @@ class TagForm(forms.ModelForm):
|
|||
)
|
||||
|
||||
fieldsets = (
|
||||
('Tag', ('name', 'slug', 'color', 'description', 'object_types')),
|
||||
FieldSet('name', 'slug', 'color', 'description', 'object_types', name=_('Tag')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -442,12 +451,13 @@ class ConfigContextForm(SyncedDataMixin, forms.ModelForm):
|
|||
)
|
||||
|
||||
fieldsets = (
|
||||
(_('Config Context'), ('name', 'weight', 'description', 'data', 'is_active')),
|
||||
(_('Data Source'), ('data_source', 'data_file', 'auto_sync_enabled')),
|
||||
(_('Assignment'), (
|
||||
FieldSet('name', 'weight', 'description', 'data', 'is_active', name=_('Config Context')),
|
||||
FieldSet('data_source', 'data_file', 'auto_sync_enabled', name=_('Data Source')),
|
||||
FieldSet(
|
||||
'regions', 'site_groups', 'sites', 'locations', 'device_types', 'roles', 'platforms', 'cluster_types',
|
||||
'cluster_groups', 'clusters', 'tenant_groups', 'tenants', 'tags',
|
||||
)),
|
||||
name=_('Assignment')
|
||||
),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -494,9 +504,9 @@ class ConfigTemplateForm(SyncedDataMixin, forms.ModelForm):
|
|||
)
|
||||
|
||||
fieldsets = (
|
||||
(_('Config Template'), ('name', 'description', 'environment_params', 'tags')),
|
||||
(_('Content'), ('template_code',)),
|
||||
(_('Data Source'), ('data_source', 'data_file', 'auto_sync_enabled')),
|
||||
FieldSet('name', 'description', 'environment_params', 'tags', name=_('Config Template')),
|
||||
FieldSet('template_code', name=_('Content')),
|
||||
FieldSet('data_source', 'data_file', 'auto_sync_enabled', name=_('Data Source')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -526,6 +536,9 @@ class ConfigTemplateForm(SyncedDataMixin, forms.ModelForm):
|
|||
|
||||
|
||||
class ImageAttachmentForm(forms.ModelForm):
|
||||
fieldsets = (
|
||||
FieldSet(ObjectAttribute('parent'), 'name', 'image'),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = ImageAttachment
|
||||
|
|
|
@ -3,7 +3,7 @@ from django.utils.translation import gettext_lazy as _
|
|||
|
||||
from extras.choices import DurationChoices
|
||||
from utilities.forms.widgets import DateTimePicker, NumberWithOptions
|
||||
from utilities.utils import local_now
|
||||
from utilities.datetime import local_now
|
||||
|
||||
__all__ = (
|
||||
'ReportForm',
|
||||
|
|
|
@ -3,7 +3,7 @@ from django.utils.translation import gettext_lazy as _
|
|||
|
||||
from extras.choices import DurationChoices
|
||||
from utilities.forms.widgets import DateTimePicker, NumberWithOptions
|
||||
from utilities.utils import local_now
|
||||
from utilities.datetime import local_now
|
||||
|
||||
__all__ = (
|
||||
'ScriptForm',
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
import strawberry_django
|
||||
|
||||
from extras import filtersets, models
|
||||
from netbox.graphql.filter_mixins import autotype_decorator, BaseFilterMixin
|
||||
|
||||
__all__ = (
|
||||
'ConfigContextFilter',
|
||||
'ConfigTemplateFilter',
|
||||
'CustomFieldFilter',
|
||||
'CustomFieldChoiceSetFilter',
|
||||
'CustomLinkFilter',
|
||||
'EventRuleFilter',
|
||||
'ExportTemplateFilter',
|
||||
'ImageAttachmentFilter',
|
||||
'JournalEntryFilter',
|
||||
'ObjectChangeFilter',
|
||||
'SavedFilterFilter',
|
||||
'TagFilter',
|
||||
'WebhookFilter',
|
||||
)
|
||||
|
||||
|
||||
@strawberry_django.filter(models.ConfigContext, lookups=True)
|
||||
@autotype_decorator(filtersets.ConfigContextFilterSet)
|
||||
class ConfigContextFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.ConfigTemplate, lookups=True)
|
||||
@autotype_decorator(filtersets.ConfigTemplateFilterSet)
|
||||
class ConfigTemplateFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.CustomField, lookups=True)
|
||||
@autotype_decorator(filtersets.CustomFieldFilterSet)
|
||||
class CustomFieldFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.CustomFieldChoiceSet, lookups=True)
|
||||
@autotype_decorator(filtersets.CustomFieldChoiceSetFilterSet)
|
||||
class CustomFieldChoiceSetFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.CustomLink, lookups=True)
|
||||
@autotype_decorator(filtersets.CustomLinkFilterSet)
|
||||
class CustomLinkFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.ExportTemplate, lookups=True)
|
||||
@autotype_decorator(filtersets.ExportTemplateFilterSet)
|
||||
class ExportTemplateFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.ImageAttachment, lookups=True)
|
||||
@autotype_decorator(filtersets.ImageAttachmentFilterSet)
|
||||
class ImageAttachmentFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.JournalEntry, lookups=True)
|
||||
@autotype_decorator(filtersets.JournalEntryFilterSet)
|
||||
class JournalEntryFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.ObjectChange, lookups=True)
|
||||
@autotype_decorator(filtersets.ObjectChangeFilterSet)
|
||||
class ObjectChangeFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.SavedFilter, lookups=True)
|
||||
@autotype_decorator(filtersets.SavedFilterFilterSet)
|
||||
class SavedFilterFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.Tag, lookups=True)
|
||||
@autotype_decorator(filtersets.TagFilterSet)
|
||||
class TagFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.Webhook, lookups=True)
|
||||
@autotype_decorator(filtersets.WebhookFilterSet)
|
||||
class WebhookFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.EventRule, lookups=True)
|
||||
@autotype_decorator(filtersets.EventRuleFilterSet)
|
||||
class EventRuleFilter(BaseFilterMixin):
|
||||
pass
|
|
@ -1,6 +1,8 @@
|
|||
import graphene
|
||||
from typing import TYPE_CHECKING, Annotated, List
|
||||
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from graphene.types.generic import GenericScalar
|
||||
|
||||
from extras.models import ObjectChange
|
||||
|
||||
|
@ -14,56 +16,67 @@ __all__ = (
|
|||
'TagsMixin',
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .types import ImageAttachmentType, JournalEntryType, ObjectChangeType, TagType
|
||||
from tenancy.graphql.types import ContactAssignmentType
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class ChangelogMixin:
|
||||
changelog = graphene.List('extras.graphql.types.ObjectChangeType')
|
||||
|
||||
def resolve_changelog(self, info):
|
||||
@strawberry_django.field
|
||||
def changelog(self, info) -> List[Annotated["ObjectChangeType", strawberry.lazy('.types')]]:
|
||||
content_type = ContentType.objects.get_for_model(self)
|
||||
object_changes = ObjectChange.objects.filter(
|
||||
changed_object_type=content_type,
|
||||
changed_object_id=self.pk
|
||||
)
|
||||
return object_changes.restrict(info.context.user, 'view')
|
||||
return object_changes.restrict(info.context.request.user, 'view')
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class ConfigContextMixin:
|
||||
config_context = GenericScalar()
|
||||
|
||||
def resolve_config_context(self, info):
|
||||
@strawberry_django.field
|
||||
def config_context(self) -> strawberry.scalars.JSON:
|
||||
return self.get_config_context()
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class CustomFieldsMixin:
|
||||
custom_fields = GenericScalar()
|
||||
|
||||
def resolve_custom_fields(self, info):
|
||||
@strawberry_django.field
|
||||
def custom_fields(self) -> strawberry.scalars.JSON:
|
||||
return self.custom_field_data
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class ImageAttachmentsMixin:
|
||||
image_attachments = graphene.List('extras.graphql.types.ImageAttachmentType')
|
||||
|
||||
def resolve_image_attachments(self, info):
|
||||
return self.images.restrict(info.context.user, 'view')
|
||||
@strawberry_django.field
|
||||
def image_attachments(self, info) -> List[Annotated["ImageAttachmentType", strawberry.lazy('.types')]]:
|
||||
return self.images.restrict(info.context.request.user, 'view')
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class JournalEntriesMixin:
|
||||
journal_entries = graphene.List('extras.graphql.types.JournalEntryType')
|
||||
|
||||
def resolve_journal_entries(self, info):
|
||||
return self.journal_entries.restrict(info.context.user, 'view')
|
||||
@strawberry_django.field
|
||||
def journal_entries(self, info) -> List[Annotated["JournalEntryType", strawberry.lazy('.types')]]:
|
||||
return self.journal_entries.all()
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class TagsMixin:
|
||||
tags = graphene.List('extras.graphql.types.TagType')
|
||||
|
||||
def resolve_tags(self, info):
|
||||
@strawberry_django.field
|
||||
def tags(self) -> List[Annotated["TagType", strawberry.lazy('.types')]]:
|
||||
return self.tags.all()
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class ContactsMixin:
|
||||
contacts = graphene.List('tenancy.graphql.types.ContactAssignmentType')
|
||||
|
||||
def resolve_contacts(self, info):
|
||||
@strawberry_django.field
|
||||
def contacts(self) -> List[Annotated["ContactAssignmentType", strawberry.lazy('tenancy.graphql.types')]]:
|
||||
return list(self.contacts.all())
|
||||
|
|
|
@ -1,80 +1,70 @@
|
|||
import graphene
|
||||
from typing import List
|
||||
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
|
||||
from extras import models
|
||||
from netbox.graphql.fields import ObjectField, ObjectListField
|
||||
from .types import *
|
||||
from utilities.graphql_optimizer import gql_query_optimizer
|
||||
|
||||
|
||||
class ExtrasQuery(graphene.ObjectType):
|
||||
config_context = ObjectField(ConfigContextType)
|
||||
config_context_list = ObjectListField(ConfigContextType)
|
||||
@strawberry.type
|
||||
class ExtrasQuery:
|
||||
@strawberry.field
|
||||
def config_context(self, id: int) -> ConfigContextType:
|
||||
return models.ConfigContext.objects.get(pk=id)
|
||||
config_context_list: List[ConfigContextType] = strawberry_django.field()
|
||||
|
||||
def resolve_config_context_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.ConfigContext.objects.all(), info)
|
||||
@strawberry.field
|
||||
def config_template(self, id: int) -> ConfigTemplateType:
|
||||
return models.ConfigTemplate.objects.get(pk=id)
|
||||
config_template_list: List[ConfigTemplateType] = strawberry_django.field()
|
||||
|
||||
config_template = ObjectField(ConfigTemplateType)
|
||||
config_template_list = ObjectListField(ConfigTemplateType)
|
||||
@strawberry.field
|
||||
def custom_field(self, id: int) -> CustomFieldType:
|
||||
return models.CustomField.objects.get(pk=id)
|
||||
custom_field_list: List[CustomFieldType] = strawberry_django.field()
|
||||
|
||||
def resolve_config_template_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.ConfigTemplate.objects.all(), info)
|
||||
@strawberry.field
|
||||
def custom_field_choice_set(self, id: int) -> CustomFieldChoiceSetType:
|
||||
return models.CustomFieldChoiceSet.objects.get(pk=id)
|
||||
custom_field_choice_set_list: List[CustomFieldChoiceSetType] = strawberry_django.field()
|
||||
|
||||
custom_field = ObjectField(CustomFieldType)
|
||||
custom_field_list = ObjectListField(CustomFieldType)
|
||||
@strawberry.field
|
||||
def custom_link(self, id: int) -> CustomLinkType:
|
||||
return models.CustomLink.objects.get(pk=id)
|
||||
custom_link_list: List[CustomLinkType] = strawberry_django.field()
|
||||
|
||||
def resolve_custom_field_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.CustomField.objects.all(), info)
|
||||
@strawberry.field
|
||||
def export_template(self, id: int) -> ExportTemplateType:
|
||||
return models.ExportTemplate.objects.get(pk=id)
|
||||
export_template_list: List[ExportTemplateType] = strawberry_django.field()
|
||||
|
||||
custom_field_choice_set = ObjectField(CustomFieldChoiceSetType)
|
||||
custom_field_choice_set_list = ObjectListField(CustomFieldChoiceSetType)
|
||||
@strawberry.field
|
||||
def image_attachment(self, id: int) -> ImageAttachmentType:
|
||||
return models.ImageAttachment.objects.get(pk=id)
|
||||
image_attachment_list: List[ImageAttachmentType] = strawberry_django.field()
|
||||
|
||||
def resolve_custom_field_choices_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.CustomFieldChoiceSet.objects.all(), info)
|
||||
@strawberry.field
|
||||
def saved_filter(self, id: int) -> SavedFilterType:
|
||||
return models.SavedFilter.objects.get(pk=id)
|
||||
saved_filter_list: List[SavedFilterType] = strawberry_django.field()
|
||||
|
||||
custom_link = ObjectField(CustomLinkType)
|
||||
custom_link_list = ObjectListField(CustomLinkType)
|
||||
@strawberry.field
|
||||
def journal_entry(self, id: int) -> JournalEntryType:
|
||||
return models.JournalEntry.objects.get(pk=id)
|
||||
journal_entry_list: List[JournalEntryType] = strawberry_django.field()
|
||||
|
||||
def resolve_custom_link_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.CustomLink.objects.all(), info)
|
||||
@strawberry.field
|
||||
def tag(self, id: int) -> TagType:
|
||||
return models.Tag.objects.get(pk=id)
|
||||
tag_list: List[TagType] = strawberry_django.field()
|
||||
|
||||
export_template = ObjectField(ExportTemplateType)
|
||||
export_template_list = ObjectListField(ExportTemplateType)
|
||||
@strawberry.field
|
||||
def webhook(self, id: int) -> WebhookType:
|
||||
return models.Webhook.objects.get(pk=id)
|
||||
webhook_list: List[WebhookType] = strawberry_django.field()
|
||||
|
||||
def resolve_export_template_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.ExportTemplate.objects.all(), info)
|
||||
|
||||
image_attachment = ObjectField(ImageAttachmentType)
|
||||
image_attachment_list = ObjectListField(ImageAttachmentType)
|
||||
|
||||
def resolve_image_attachment_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.ImageAttachment.objects.all(), info)
|
||||
|
||||
saved_filter = ObjectField(SavedFilterType)
|
||||
saved_filter_list = ObjectListField(SavedFilterType)
|
||||
|
||||
def resolve_saved_filter_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.SavedFilter.objects.all(), info)
|
||||
|
||||
journal_entry = ObjectField(JournalEntryType)
|
||||
journal_entry_list = ObjectListField(JournalEntryType)
|
||||
|
||||
def resolve_journal_entry_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.JournalEntry.objects.all(), info)
|
||||
|
||||
tag = ObjectField(TagType)
|
||||
tag_list = ObjectListField(TagType)
|
||||
|
||||
def resolve_tag_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.Tag.objects.all(), info)
|
||||
|
||||
webhook = ObjectField(WebhookType)
|
||||
webhook_list = ObjectListField(WebhookType)
|
||||
|
||||
def resolve_webhook_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.Webhook.objects.all(), info)
|
||||
|
||||
event_rule = ObjectField(EventRuleType)
|
||||
event_rule_list = ObjectListField(EventRuleType)
|
||||
|
||||
def resolve_eventrule_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.EventRule.objects.all(), info)
|
||||
@strawberry.field
|
||||
def event_rule(self, id: int) -> EventRuleType:
|
||||
return models.EventRule.objects.get(pk=id)
|
||||
event_rule_list: List[EventRuleType] = strawberry_django.field()
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
from extras import filtersets, models
|
||||
from typing import Annotated, List
|
||||
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
|
||||
from extras import models
|
||||
from extras.graphql.mixins import CustomFieldsMixin, TagsMixin
|
||||
from netbox.graphql.types import BaseObjectType, ObjectType, OrganizationalObjectType
|
||||
from netbox.graphql.types import BaseObjectType, ContentTypeType, ObjectType, OrganizationalObjectType
|
||||
from .filters import *
|
||||
|
||||
__all__ = (
|
||||
'ConfigContextType',
|
||||
|
@ -19,104 +25,202 @@ __all__ = (
|
|||
)
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.ConfigContext,
|
||||
fields='__all__',
|
||||
filters=ConfigContextFilter
|
||||
)
|
||||
class ConfigContextType(ObjectType):
|
||||
data_source: Annotated["DataSourceType", strawberry.lazy('core.graphql.types')] | None
|
||||
data_file: Annotated["DataFileType", strawberry.lazy('core.graphql.types')] | None
|
||||
|
||||
class Meta:
|
||||
model = models.ConfigContext
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.ConfigContextFilterSet
|
||||
@strawberry_django.field
|
||||
def roles(self) -> List[Annotated["DeviceRoleType", strawberry.lazy('dcim.graphql.types')]]:
|
||||
return self.roles.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def device_types(self) -> List[Annotated["DeviceTypeType", strawberry.lazy('dcim.graphql.types')]]:
|
||||
return self.device_types.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def tags(self) -> List[Annotated["TagType", strawberry.lazy('extras.graphql.types')]]:
|
||||
return self.tags.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def platforms(self) -> List[Annotated["PlatformType", strawberry.lazy('dcim.graphql.types')]]:
|
||||
return self.platforms.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def regions(self) -> List[Annotated["RegionType", strawberry.lazy('dcim.graphql.types')]]:
|
||||
return self.regions.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def cluster_groups(self) -> List[Annotated["ClusterGroupType", strawberry.lazy('virtualization.graphql.types')]]:
|
||||
return self.cluster_groups.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def tenant_groups(self) -> List[Annotated["TenantGroupType", strawberry.lazy('tenancy.graphql.types')]]:
|
||||
return self.tenant_groups.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def cluster_types(self) -> List[Annotated["ClusterTypeType", strawberry.lazy('virtualization.graphql.types')]]:
|
||||
return self.cluster_types.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def clusters(self) -> List[Annotated["ClusterType", strawberry.lazy('virtualization.graphql.types')]]:
|
||||
return self.clusters.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def locations(self) -> List[Annotated["LocationType", strawberry.lazy('dcim.graphql.types')]]:
|
||||
return self.locations.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def sites(self) -> List[Annotated["SiteType", strawberry.lazy('dcim.graphql.types')]]:
|
||||
return self.sites.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def tenants(self) -> List[Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')]]:
|
||||
return self.tenants.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def site_groups(self) -> List[Annotated["SiteGroupType", strawberry.lazy('dcim.graphql.types')]]:
|
||||
return self.site_groups.all()
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.ConfigTemplate,
|
||||
fields='__all__',
|
||||
filters=ConfigTemplateFilter
|
||||
)
|
||||
class ConfigTemplateType(TagsMixin, ObjectType):
|
||||
data_source: Annotated["DataSourceType", strawberry.lazy('core.graphql.types')] | None
|
||||
data_file: Annotated["DataFileType", strawberry.lazy('core.graphql.types')] | None
|
||||
|
||||
class Meta:
|
||||
model = models.ConfigTemplate
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.ConfigTemplateFilterSet
|
||||
@strawberry_django.field
|
||||
def virtualmachines(self) -> List[Annotated["VirtualMachineType", strawberry.lazy('virtualization.graphql.types')]]:
|
||||
return self.virtualmachines.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def devices(self) -> List[Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')]]:
|
||||
return self.devices.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def platforms(self) -> List[Annotated["PlatformType", strawberry.lazy('dcim.graphql.types')]]:
|
||||
return self.platforms.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def device_roles(self) -> List[Annotated["DeviceRoleType", strawberry.lazy('dcim.graphql.types')]]:
|
||||
return self.device_roles.all()
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.CustomField,
|
||||
fields='__all__',
|
||||
filters=CustomFieldFilter
|
||||
)
|
||||
class CustomFieldType(ObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.CustomField
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.CustomFieldFilterSet
|
||||
related_object_type: Annotated["ContentTypeType", strawberry.lazy('netbox.graphql.types')] | None
|
||||
choice_set: Annotated["CustomFieldChoiceSetType", strawberry.lazy('extras.graphql.types')] | None
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.CustomFieldChoiceSet,
|
||||
exclude=('extra_choices', ),
|
||||
filters=CustomFieldChoiceSetFilter
|
||||
)
|
||||
class CustomFieldChoiceSetType(ObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.CustomFieldChoiceSet
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.CustomFieldChoiceSetFilterSet
|
||||
@strawberry_django.field
|
||||
def choices_for(self) -> List[Annotated["CustomFieldType", strawberry.lazy('extras.graphql.types')]]:
|
||||
return self.choices_for.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def extra_choices(self) -> List[str] | None:
|
||||
return list(self.extra_choices)
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.CustomLink,
|
||||
fields='__all__',
|
||||
filters=CustomLinkFilter
|
||||
)
|
||||
class CustomLinkType(ObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.CustomLink
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.CustomLinkFilterSet
|
||||
|
||||
|
||||
class EventRuleType(OrganizationalObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.EventRule
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.EventRuleFilterSet
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.ExportTemplate,
|
||||
fields='__all__',
|
||||
filters=ExportTemplateFilter
|
||||
)
|
||||
class ExportTemplateType(ObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.ExportTemplate
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.ExportTemplateFilterSet
|
||||
data_source: Annotated["DataSourceType", strawberry.lazy('core.graphql.types')] | None
|
||||
data_file: Annotated["DataFileType", strawberry.lazy('core.graphql.types')] | None
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.ImageAttachment,
|
||||
fields='__all__',
|
||||
filters=ImageAttachmentFilter
|
||||
)
|
||||
class ImageAttachmentType(BaseObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.ImageAttachment
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.ImageAttachmentFilterSet
|
||||
object_type: Annotated["ContentTypeType", strawberry.lazy('netbox.graphql.types')] | None
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.JournalEntry,
|
||||
fields='__all__',
|
||||
filters=JournalEntryFilter
|
||||
)
|
||||
class JournalEntryType(CustomFieldsMixin, TagsMixin, ObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.JournalEntry
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.JournalEntryFilterSet
|
||||
assigned_object_type: Annotated["ContentTypeType", strawberry.lazy('netbox.graphql.types')] | None
|
||||
created_by: Annotated["UserType", strawberry.lazy('users.graphql.types')] | None
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.ObjectChange,
|
||||
fields='__all__',
|
||||
filters=ObjectChangeFilter
|
||||
)
|
||||
class ObjectChangeType(BaseObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.ObjectChange
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.ObjectChangeFilterSet
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.SavedFilter,
|
||||
exclude=['content_types',],
|
||||
filters=SavedFilterFilter
|
||||
)
|
||||
class SavedFilterType(ObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.SavedFilter
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.SavedFilterFilterSet
|
||||
user: Annotated["UserType", strawberry.lazy('users.graphql.types')] | None
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.Tag,
|
||||
exclude=['extras_taggeditem_items', ],
|
||||
filters=TagFilter
|
||||
)
|
||||
class TagType(ObjectType):
|
||||
color: str
|
||||
|
||||
class Meta:
|
||||
model = models.Tag
|
||||
exclude = ('extras_taggeditem_items',)
|
||||
filterset_class = filtersets.TagFilterSet
|
||||
@strawberry_django.field
|
||||
def object_types(self) -> List[ContentTypeType]:
|
||||
return self.object_types.all()
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.Webhook,
|
||||
exclude=['content_types',],
|
||||
filters=WebhookFilter
|
||||
)
|
||||
class WebhookType(OrganizationalObjectType):
|
||||
pass
|
||||
|
||||
class Meta:
|
||||
model = models.Webhook
|
||||
filterset_class = filtersets.WebhookFilterSet
|
||||
|
||||
@strawberry_django.type(
|
||||
models.EventRule,
|
||||
exclude=['content_types',],
|
||||
filters=EventRuleFilter
|
||||
)
|
||||
class EventRuleType(OrganizationalObjectType):
|
||||
action_object_type: Annotated["ContentTypeType", strawberry.lazy('netbox.graphql.types')] | None
|
||||
|
|
|
@ -14,7 +14,7 @@ from extras.context_managers import event_tracking
|
|||
from extras.scripts import get_module_and_script
|
||||
from extras.signals import clear_events
|
||||
from utilities.exceptions import AbortTransaction
|
||||
from utilities.utils import NetBoxFakeRequest
|
||||
from utilities.request import NetBoxFakeRequest
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
|
|
|
@ -9,11 +9,11 @@ from jinja2.sandbox import SandboxedEnvironment
|
|||
|
||||
from extras.querysets import ConfigContextQuerySet
|
||||
from netbox.config import get_config
|
||||
from netbox.registry import registry
|
||||
from netbox.models import ChangeLoggedModel
|
||||
from netbox.models.features import CloningMixin, CustomLinksMixin, ExportTemplatesMixin, SyncedDataMixin, TagsMixin
|
||||
from utilities.jinja2 import ConfigTemplateLoader
|
||||
from utilities.utils import deepmerge
|
||||
from netbox.registry import registry
|
||||
from utilities.data import deepmerge
|
||||
from utilities.jinja2 import DataFileLoader
|
||||
|
||||
__all__ = (
|
||||
'ConfigContext',
|
||||
|
@ -290,7 +290,7 @@ class ConfigTemplate(SyncedDataMixin, CustomLinksMixin, ExportTemplatesMixin, Ta
|
|||
"""
|
||||
# Initialize the template loader & cache the base template code (if applicable)
|
||||
if self.data_file:
|
||||
loader = ConfigTemplateLoader(data_source=self.data_source)
|
||||
loader = DataFileLoader(data_source=self.data_source)
|
||||
loader.cache_templates({
|
||||
self.data_file.path: self.template_code
|
||||
})
|
||||
|
|
|
@ -22,8 +22,10 @@ from netbox.models import ChangeLoggedModel
|
|||
from netbox.models.features import (
|
||||
CloningMixin, CustomFieldsMixin, CustomLinksMixin, ExportTemplatesMixin, SyncedDataMixin, TagsMixin,
|
||||
)
|
||||
from utilities.html import clean_html
|
||||
from utilities.querydict import dict_to_querydict
|
||||
from utilities.querysets import RestrictedQuerySet
|
||||
from utilities.utils import clean_html, dict_to_querydict, render_jinja2
|
||||
from utilities.jinja2 import render_jinja2
|
||||
|
||||
__all__ = (
|
||||
'Bookmark',
|
||||
|
|
|
@ -4,9 +4,7 @@ from django.db import models
|
|||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from netbox.search.utils import get_indexer
|
||||
from netbox.registry import registry
|
||||
from utilities.fields import RestrictedGenericForeignKey
|
||||
from utilities.utils import content_type_identifier
|
||||
from ..fields import CachedValueField
|
||||
|
||||
__all__ = (
|
||||
|
|
|
@ -8,7 +8,7 @@ from django.utils.translation import gettext_lazy as _
|
|||
from extras.choices import ChangeActionChoices
|
||||
from netbox.models import ChangeLoggedModel
|
||||
from netbox.models.features import *
|
||||
from utilities.utils import deserialize_object
|
||||
from utilities.serialization import deserialize_object
|
||||
|
||||
__all__ = (
|
||||
'Branch',
|
||||
|
|
|
@ -5,9 +5,9 @@ from django.utils.text import slugify
|
|||
from django.utils.translation import gettext_lazy as _
|
||||
from taggit.models import TagBase, GenericTaggedItemBase
|
||||
|
||||
from netbox.choices import ColorChoices
|
||||
from netbox.models import ChangeLoggedModel
|
||||
from netbox.models.features import CloningMixin, ExportTemplatesMixin
|
||||
from utilities.choices import ColorChoices
|
||||
from utilities.fields import ColorField
|
||||
|
||||
__all__ = (
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import importlib
|
||||
import logging
|
||||
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.exceptions import ImproperlyConfigured, ValidationError
|
||||
from django.db.models.fields.reverse_related import ManyToManyRel
|
||||
from django.db.models.signals import m2m_changed, post_save, pre_delete
|
||||
from django.dispatch import receiver, Signal
|
||||
|
@ -13,7 +14,6 @@ from core.signals import job_end, job_start
|
|||
from extras.constants import EVENT_JOB_END, EVENT_JOB_START
|
||||
from extras.events import process_event_rules
|
||||
from extras.models import EventRule
|
||||
from extras.validators import run_validators
|
||||
from netbox.config import get_config
|
||||
from netbox.context import current_request, events_queue
|
||||
from netbox.models.features import ChangeLoggingMixin
|
||||
|
@ -22,6 +22,30 @@ from utilities.exceptions import AbortRequest
|
|||
from .choices import ObjectChangeActionChoices
|
||||
from .events import enqueue_object, get_snapshots, serialize_for_event
|
||||
from .models import CustomField, ObjectChange, TaggedItem
|
||||
from .validators import CustomValidator
|
||||
|
||||
|
||||
def run_validators(instance, validators):
|
||||
"""
|
||||
Run the provided iterable of validators for the instance.
|
||||
"""
|
||||
request = current_request.get()
|
||||
for validator in validators:
|
||||
|
||||
# Loading a validator class by dotted path
|
||||
if type(validator) is str:
|
||||
module, cls = validator.rsplit('.', 1)
|
||||
validator = getattr(importlib.import_module(module), cls)()
|
||||
|
||||
# Constructing a new instance on the fly from a ruleset
|
||||
elif type(validator) is dict:
|
||||
validator = CustomValidator(validator)
|
||||
|
||||
elif not issubclass(validator.__class__, CustomValidator):
|
||||
raise ImproperlyConfigured(f"Invalid value for custom validator: {validator}")
|
||||
|
||||
validator(instance, request)
|
||||
|
||||
|
||||
#
|
||||
# Change logging/webhooks
|
||||
|
|
|
@ -5,7 +5,7 @@ from circuits.api.serializers import ProviderSerializer
|
|||
from circuits.forms import ProviderForm
|
||||
from circuits.models import Provider
|
||||
from ipam.models import ASN, RIR
|
||||
from utilities.choices import CSVDelimiterChoices, ImportFormatChoices
|
||||
from netbox.choices import CSVDelimiterChoices, ImportFormatChoices
|
||||
from utilities.testing import APITestCase, ModelViewTestCase, create_tags, post_data
|
||||
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ from dcim.models import Manufacturer, Rack, Site
|
|||
from extras.choices import *
|
||||
from extras.models import CustomField, CustomFieldChoiceSet
|
||||
from ipam.models import VLAN
|
||||
from utilities.choices import CSVDelimiterChoices, ImportFormatChoices
|
||||
from netbox.choices import CSVDelimiterChoices, ImportFormatChoices
|
||||
from utilities.testing import APITestCase, TestCase
|
||||
from virtualization.models import VirtualMachine
|
||||
|
||||
|
|
|
@ -3,11 +3,13 @@ from django.core.exceptions import ValidationError
|
|||
from django.db import transaction
|
||||
from django.test import TestCase, override_settings
|
||||
|
||||
from ipam.models import ASN, RIR
|
||||
from dcim.choices import SiteStatusChoices
|
||||
from dcim.models import Site
|
||||
from extras.validators import CustomValidator
|
||||
from ipam.models import ASN, RIR
|
||||
from users.models import User
|
||||
from utilities.exceptions import AbortRequest
|
||||
from utilities.request import NetBoxFakeRequest
|
||||
|
||||
|
||||
class MyValidator(CustomValidator):
|
||||
|
@ -79,6 +81,13 @@ prohibited_validator = CustomValidator({
|
|||
}
|
||||
})
|
||||
|
||||
|
||||
request_validator = CustomValidator({
|
||||
'request.user.username': {
|
||||
'eq': 'Bob'
|
||||
}
|
||||
})
|
||||
|
||||
custom_validator = MyValidator()
|
||||
|
||||
|
||||
|
@ -154,6 +163,28 @@ class CustomValidatorTest(TestCase):
|
|||
def test_custom_valid(self):
|
||||
Site(name='foo', slug='foo').clean()
|
||||
|
||||
@override_settings(CUSTOM_VALIDATORS={'dcim.site': [request_validator]})
|
||||
def test_request_validation(self):
|
||||
alice = User.objects.create(username='Alice')
|
||||
bob = User.objects.create(username='Bob')
|
||||
request = NetBoxFakeRequest({
|
||||
'META': {},
|
||||
'POST': {},
|
||||
'GET': {},
|
||||
'FILES': {},
|
||||
'user': alice,
|
||||
'path': '',
|
||||
})
|
||||
site = Site(name='abc', slug='abc')
|
||||
|
||||
# Attempt to create the Site as Alice
|
||||
with self.assertRaises(ValidationError):
|
||||
request_validator(site, request)
|
||||
|
||||
# Creating the Site as Bob should succeed
|
||||
request.user = bob
|
||||
request_validator(site, request)
|
||||
|
||||
|
||||
class CustomValidatorConfigTest(TestCase):
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import importlib
|
||||
import inspect
|
||||
import operator
|
||||
|
||||
from django.core import validators
|
||||
from django.core.exceptions import ValidationError
|
||||
|
@ -74,6 +75,8 @@ class CustomValidator:
|
|||
|
||||
:param validation_rules: A dictionary mapping object attributes to validation rules
|
||||
"""
|
||||
REQUEST_TOKEN = 'request'
|
||||
|
||||
VALIDATORS = {
|
||||
'eq': IsEqualValidator,
|
||||
'neq': IsNotEqualValidator,
|
||||
|
@ -88,25 +91,56 @@ class CustomValidator:
|
|||
|
||||
def __init__(self, validation_rules=None):
|
||||
self.validation_rules = validation_rules or {}
|
||||
assert type(self.validation_rules) is dict, "Validation rules must be passed as a dictionary"
|
||||
if type(self.validation_rules) is not dict:
|
||||
raise ValueError(_("Validation rules must be passed as a dictionary"))
|
||||
|
||||
def __call__(self, instance):
|
||||
# Validate instance attributes per validation rules
|
||||
for attr_name, rules in self.validation_rules.items():
|
||||
attr = self._getattr(instance, attr_name)
|
||||
def __call__(self, instance, request=None):
|
||||
"""
|
||||
Validate the instance and (optional) request against the validation rule(s).
|
||||
"""
|
||||
for attr_path, rules in self.validation_rules.items():
|
||||
|
||||
# The rule applies to the current request
|
||||
if attr_path.split('.')[0] == self.REQUEST_TOKEN:
|
||||
# Skip if no request has been provided (we can't validate)
|
||||
if request is None:
|
||||
continue
|
||||
attr = self._get_request_attr(request, attr_path)
|
||||
# The rule applies to the instance
|
||||
else:
|
||||
attr = self._get_instance_attr(instance, attr_path)
|
||||
|
||||
# Validate the attribute's value against each of the rules defined for it
|
||||
for descriptor, value in rules.items():
|
||||
validator = self.get_validator(descriptor, value)
|
||||
try:
|
||||
validator(attr)
|
||||
except ValidationError as exc:
|
||||
# Re-package the raised ValidationError to associate it with the specific attr
|
||||
raise ValidationError({attr_name: exc})
|
||||
raise ValidationError(
|
||||
_("Custom validation failed for {attribute}: {exception}").format(
|
||||
attribute=attr_path, exception=exc
|
||||
)
|
||||
)
|
||||
|
||||
# Execute custom validation logic (if any)
|
||||
self.validate(instance)
|
||||
# TODO: Remove in v4.1
|
||||
# Inspect the validate() method, which may have been overridden, to determine
|
||||
# whether we should pass the request (maintains backward compatibility for pre-v4.0)
|
||||
if 'request' in inspect.signature(self.validate).parameters:
|
||||
self.validate(instance, request)
|
||||
else:
|
||||
self.validate(instance)
|
||||
|
||||
@staticmethod
|
||||
def _getattr(instance, name):
|
||||
def _get_request_attr(request, name):
|
||||
name = name.split('.', maxsplit=1)[1] # Remove token
|
||||
try:
|
||||
return operator.attrgetter(name)(request)
|
||||
except AttributeError:
|
||||
raise ValidationError(_('Invalid attribute "{name}" for request').format(name=name))
|
||||
|
||||
@staticmethod
|
||||
def _get_instance_attr(instance, name):
|
||||
# Attempt to resolve many-to-many fields to their stored values
|
||||
m2m_fields = [f.name for f in instance._meta.local_many_to_many]
|
||||
if name in m2m_fields:
|
||||
|
@ -137,7 +171,7 @@ class CustomValidator:
|
|||
validator_cls = self.VALIDATORS.get(descriptor)
|
||||
return validator_cls(value)
|
||||
|
||||
def validate(self, instance):
|
||||
def validate(self, instance, request):
|
||||
"""
|
||||
Custom validation method, to be overridden by the user. Validation failures should
|
||||
raise a ValidationError exception.
|
||||
|
@ -151,21 +185,3 @@ class CustomValidator:
|
|||
if field is not None:
|
||||
raise ValidationError({field: message})
|
||||
raise ValidationError(message)
|
||||
|
||||
|
||||
def run_validators(instance, validators):
|
||||
"""
|
||||
Run the provided iterable of validators for the instance.
|
||||
"""
|
||||
for validator in validators:
|
||||
|
||||
# Loading a validator class by dotted path
|
||||
if type(validator) is str:
|
||||
module, cls = validator.rsplit('.', 1)
|
||||
validator = getattr(importlib.import_module(module), cls)()
|
||||
|
||||
# Constructing a new instance on the fly from a ruleset
|
||||
elif type(validator) is dict:
|
||||
validator = CustomValidator(validator)
|
||||
|
||||
validator(instance)
|
||||
|
|
|
@ -18,13 +18,16 @@ from extras.dashboard.utils import get_widget_class
|
|||
from netbox.constants import DEFAULT_ACTION_PERMISSIONS
|
||||
from netbox.views import generic
|
||||
from netbox.views.generic.mixins import TableMixin
|
||||
from utilities.data import shallow_compare_dict
|
||||
from utilities.forms import ConfirmationForm, get_field_value
|
||||
from utilities.htmx import render_partial
|
||||
from utilities.paginator import EnhancedPaginator, get_paginate_count
|
||||
from utilities.query import count_related
|
||||
from utilities.querydict import normalize_querydict
|
||||
from utilities.request import copy_safe_request
|
||||
from utilities.rqworker import get_workers_for_queue
|
||||
from utilities.templatetags.builtins.filters import render_markdown
|
||||
from utilities.utils import copy_safe_request, count_related, get_viewname, normalize_querydict, shallow_compare_dict
|
||||
from utilities.views import ContentTypePermissionRequiredMixin, register_model_view
|
||||
from utilities.views import ContentTypePermissionRequiredMixin, get_viewname, register_model_view
|
||||
from . import filtersets, forms, tables
|
||||
from .models import *
|
||||
from .scripts import run_script
|
||||
|
@ -760,7 +763,6 @@ class ImageAttachmentListView(generic.ObjectListView):
|
|||
class ImageAttachmentEditView(generic.ObjectEditView):
|
||||
queryset = ImageAttachment.objects.all()
|
||||
form = forms.ImageAttachmentForm
|
||||
template_name = 'extras/imageattachment_edit.html'
|
||||
|
||||
def alter_object(self, instance, request, args, kwargs):
|
||||
if not instance.pk:
|
||||
|
|
|
@ -13,6 +13,7 @@ from utilities.forms import add_blank_choice
|
|||
from utilities.forms.fields import (
|
||||
CommentField, ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, NumericArrayField,
|
||||
)
|
||||
from utilities.forms.rendering import FieldSet
|
||||
from utilities.forms.widgets import BulkEditNullBooleanSelect
|
||||
from virtualization.models import Cluster, ClusterGroup
|
||||
|
||||
|
@ -55,7 +56,7 @@ class VRFBulkEditForm(NetBoxModelBulkEditForm):
|
|||
|
||||
model = VRF
|
||||
fieldsets = (
|
||||
(None, ('tenant', 'enforce_unique', 'description')),
|
||||
FieldSet('tenant', 'enforce_unique', 'description'),
|
||||
)
|
||||
nullable_fields = ('tenant', 'description', 'comments')
|
||||
|
||||
|
@ -75,7 +76,7 @@ class RouteTargetBulkEditForm(NetBoxModelBulkEditForm):
|
|||
|
||||
model = RouteTarget
|
||||
fieldsets = (
|
||||
(None, ('tenant', 'description')),
|
||||
FieldSet('tenant', 'description'),
|
||||
)
|
||||
nullable_fields = ('tenant', 'description', 'comments')
|
||||
|
||||
|
@ -94,7 +95,7 @@ class RIRBulkEditForm(NetBoxModelBulkEditForm):
|
|||
|
||||
model = RIR
|
||||
fieldsets = (
|
||||
(None, ('is_private', 'description')),
|
||||
FieldSet('is_private', 'description'),
|
||||
)
|
||||
nullable_fields = ('is_private', 'description')
|
||||
|
||||
|
@ -118,7 +119,7 @@ class ASNRangeBulkEditForm(NetBoxModelBulkEditForm):
|
|||
|
||||
model = ASNRange
|
||||
fieldsets = (
|
||||
(None, ('rir', 'tenant', 'description')),
|
||||
FieldSet('rir', 'tenant', 'description'),
|
||||
)
|
||||
nullable_fields = ('description',)
|
||||
|
||||
|
@ -148,7 +149,7 @@ class ASNBulkEditForm(NetBoxModelBulkEditForm):
|
|||
|
||||
model = ASN
|
||||
fieldsets = (
|
||||
(None, ('sites', 'rir', 'tenant', 'description')),
|
||||
FieldSet('sites', 'rir', 'tenant', 'description'),
|
||||
)
|
||||
nullable_fields = ('tenant', 'description', 'comments')
|
||||
|
||||
|
@ -177,7 +178,7 @@ class AggregateBulkEditForm(NetBoxModelBulkEditForm):
|
|||
|
||||
model = Aggregate
|
||||
fieldsets = (
|
||||
(None, ('rir', 'tenant', 'date_added', 'description')),
|
||||
FieldSet('rir', 'tenant', 'date_added', 'description'),
|
||||
)
|
||||
nullable_fields = ('date_added', 'description', 'comments')
|
||||
|
||||
|
@ -195,7 +196,7 @@ class RoleBulkEditForm(NetBoxModelBulkEditForm):
|
|||
|
||||
model = Role
|
||||
fieldsets = (
|
||||
(None, ('weight', 'description')),
|
||||
FieldSet('weight', 'description'),
|
||||
)
|
||||
nullable_fields = ('description',)
|
||||
|
||||
|
@ -265,9 +266,9 @@ class PrefixBulkEditForm(NetBoxModelBulkEditForm):
|
|||
|
||||
model = Prefix
|
||||
fieldsets = (
|
||||
(None, ('tenant', 'status', 'role', 'description')),
|
||||
(_('Site'), ('region', 'site_group', 'site')),
|
||||
(_('Addressing'), ('vrf', 'prefix_length', 'is_pool', 'mark_utilized')),
|
||||
FieldSet('tenant', 'status', 'role', 'description'),
|
||||
FieldSet('region', 'site_group', 'site', name=_('Site')),
|
||||
FieldSet('vrf', 'prefix_length', 'is_pool', 'mark_utilized', name=_('Addressing')),
|
||||
)
|
||||
nullable_fields = (
|
||||
'site', 'vrf', 'tenant', 'role', 'description', 'comments',
|
||||
|
@ -309,7 +310,7 @@ class IPRangeBulkEditForm(NetBoxModelBulkEditForm):
|
|||
|
||||
model = IPRange
|
||||
fieldsets = (
|
||||
(None, ('status', 'role', 'vrf', 'tenant', 'mark_utilized', 'description')),
|
||||
FieldSet('status', 'role', 'vrf', 'tenant', 'mark_utilized', 'description'),
|
||||
)
|
||||
nullable_fields = (
|
||||
'vrf', 'tenant', 'role', 'description', 'comments',
|
||||
|
@ -357,8 +358,8 @@ class IPAddressBulkEditForm(NetBoxModelBulkEditForm):
|
|||
|
||||
model = IPAddress
|
||||
fieldsets = (
|
||||
(None, ('status', 'role', 'tenant', 'description')),
|
||||
(_('Addressing'), ('vrf', 'mask_length', 'dns_name')),
|
||||
FieldSet('status', 'role', 'tenant', 'description'),
|
||||
FieldSet('vrf', 'mask_length', 'dns_name', name=_('Addressing')),
|
||||
)
|
||||
nullable_fields = (
|
||||
'vrf', 'role', 'tenant', 'dns_name', 'description', 'comments',
|
||||
|
@ -400,8 +401,8 @@ class FHRPGroupBulkEditForm(NetBoxModelBulkEditForm):
|
|||
|
||||
model = FHRPGroup
|
||||
fieldsets = (
|
||||
(None, ('protocol', 'group_id', 'name', 'description')),
|
||||
(_('Authentication'), ('auth_type', 'auth_key')),
|
||||
FieldSet('protocol', 'group_id', 'name', 'description'),
|
||||
FieldSet('auth_type', 'auth_key', name=_('Authentication')),
|
||||
)
|
||||
nullable_fields = ('auth_type', 'auth_key', 'name', 'description', 'comments')
|
||||
|
||||
|
@ -485,8 +486,10 @@ class VLANGroupBulkEditForm(NetBoxModelBulkEditForm):
|
|||
|
||||
model = VLANGroup
|
||||
fieldsets = (
|
||||
(None, ('site', 'min_vid', 'max_vid', 'description')),
|
||||
(_('Scope'), ('scope_type', 'region', 'sitegroup', 'site', 'location', 'rack', 'clustergroup', 'cluster')),
|
||||
FieldSet('site', 'min_vid', 'max_vid', 'description'),
|
||||
FieldSet(
|
||||
'scope_type', 'region', 'sitegroup', 'site', 'location', 'rack', 'clustergroup', 'cluster', name=_('Scope')
|
||||
),
|
||||
)
|
||||
nullable_fields = ('description',)
|
||||
|
||||
|
@ -556,8 +559,8 @@ class VLANBulkEditForm(NetBoxModelBulkEditForm):
|
|||
|
||||
model = VLAN
|
||||
fieldsets = (
|
||||
(None, ('status', 'role', 'tenant', 'description')),
|
||||
(_('Site & Group'), ('region', 'site_group', 'site', 'group')),
|
||||
FieldSet('status', 'role', 'tenant', 'description'),
|
||||
FieldSet('region', 'site_group', 'site', 'group', name=_('Site & Group')),
|
||||
)
|
||||
nullable_fields = (
|
||||
'site', 'group', 'tenant', 'role', 'description', 'comments',
|
||||
|
@ -587,7 +590,7 @@ class ServiceTemplateBulkEditForm(NetBoxModelBulkEditForm):
|
|||
|
||||
model = ServiceTemplate
|
||||
fieldsets = (
|
||||
(None, ('protocol', 'ports', 'description')),
|
||||
FieldSet('protocol', 'ports', 'description'),
|
||||
)
|
||||
nullable_fields = ('description', 'comments')
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ from netbox.forms import NetBoxModelFilterSetForm
|
|||
from tenancy.forms import TenancyFilterForm
|
||||
from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES, add_blank_choice
|
||||
from utilities.forms.fields import DynamicModelChoiceField, DynamicModelMultipleChoiceField, TagFilterField
|
||||
from utilities.forms.rendering import FieldSet
|
||||
from virtualization.models import VirtualMachine
|
||||
from vpn.models import L2VPN
|
||||
|
||||
|
@ -42,9 +43,9 @@ IPADDRESS_MASK_LENGTH_CHOICES = add_blank_choice([
|
|||
class VRFFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
model = VRF
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id', 'tag')),
|
||||
(_('Route Targets'), ('import_target_id', 'export_target_id')),
|
||||
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('import_target_id', 'export_target_id', name=_('Route Targets')),
|
||||
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||
)
|
||||
import_target_id = DynamicModelMultipleChoiceField(
|
||||
queryset=RouteTarget.objects.all(),
|
||||
|
@ -62,9 +63,9 @@ class VRFFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
|||
class RouteTargetFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
model = RouteTarget
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id', 'tag')),
|
||||
(_('VRF'), ('importing_vrf_id', 'exporting_vrf_id')),
|
||||
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('importing_vrf_id', 'exporting_vrf_id', name=_('VRF')),
|
||||
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||
)
|
||||
importing_vrf_id = DynamicModelMultipleChoiceField(
|
||||
queryset=VRF.objects.all(),
|
||||
|
@ -94,9 +95,9 @@ class RIRFilterForm(NetBoxModelFilterSetForm):
|
|||
class AggregateFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
model = Aggregate
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id', 'tag')),
|
||||
(_('Attributes'), ('family', 'rir_id')),
|
||||
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('family', 'rir_id', name=_('Attributes')),
|
||||
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||
)
|
||||
family = forms.ChoiceField(
|
||||
required=False,
|
||||
|
@ -114,9 +115,9 @@ class AggregateFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
|||
class ASNRangeFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
model = ASNRange
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id', 'tag')),
|
||||
(_('Range'), ('rir_id', 'start', 'end')),
|
||||
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('rir_id', 'start', 'end', name=_('Range')),
|
||||
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||
)
|
||||
rir_id = DynamicModelMultipleChoiceField(
|
||||
queryset=RIR.objects.all(),
|
||||
|
@ -137,9 +138,9 @@ class ASNRangeFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
|||
class ASNFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
model = ASN
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id', 'tag')),
|
||||
(_('Assignment'), ('rir_id', 'site_id')),
|
||||
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('rir_id', 'site_id', name=_('Assignment')),
|
||||
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||
)
|
||||
rir_id = DynamicModelMultipleChoiceField(
|
||||
queryset=RIR.objects.all(),
|
||||
|
@ -162,11 +163,14 @@ class RoleFilterForm(NetBoxModelFilterSetForm):
|
|||
class PrefixFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
model = Prefix
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id', 'tag')),
|
||||
(_('Addressing'), ('within_include', 'family', 'status', 'role_id', 'mask_length', 'is_pool', 'mark_utilized')),
|
||||
(_('VRF'), ('vrf_id', 'present_in_vrf_id')),
|
||||
(_('Location'), ('region_id', 'site_group_id', 'site_id')),
|
||||
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet(
|
||||
'within_include', 'family', 'status', 'role_id', 'mask_length', 'is_pool', 'mark_utilized',
|
||||
name=_('Addressing')
|
||||
),
|
||||
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(
|
||||
widget=forms.HiddenInput()
|
||||
|
@ -251,9 +255,9 @@ class PrefixFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
|||
class IPRangeFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
model = IPRange
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id', 'tag')),
|
||||
(_('Attributes'), ('family', 'vrf_id', 'status', 'role_id', 'mark_utilized')),
|
||||
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('family', 'vrf_id', 'status', 'role_id', 'mark_utilized', name=_('Attributes')),
|
||||
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||
)
|
||||
family = forms.ChoiceField(
|
||||
required=False,
|
||||
|
@ -290,11 +294,14 @@ class IPRangeFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
|||
class IPAddressFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
model = IPAddress
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id', 'tag')),
|
||||
(_('Attributes'), ('parent', 'family', 'status', 'role', 'mask_length', 'assigned_to_interface', 'dns_name')),
|
||||
(_('VRF'), ('vrf_id', 'present_in_vrf_id')),
|
||||
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
|
||||
(_('Device/VM'), ('device_id', 'virtual_machine_id')),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet(
|
||||
'parent', 'family', 'status', 'role', 'mask_length', 'assigned_to_interface', 'dns_name',
|
||||
name=_('Attributes')
|
||||
),
|
||||
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')
|
||||
parent = forms.CharField(
|
||||
|
@ -364,9 +371,9 @@ class IPAddressFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
|||
class FHRPGroupFilterForm(NetBoxModelFilterSetForm):
|
||||
model = FHRPGroup
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id', 'tag')),
|
||||
(_('Attributes'), ('name', 'protocol', 'group_id')),
|
||||
(_('Authentication'), ('auth_type', 'auth_key')),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('name', 'protocol', 'group_id', name=_('Attributes')),
|
||||
FieldSet('auth_type', 'auth_key', name=_('Authentication')),
|
||||
)
|
||||
name = forms.CharField(
|
||||
label=_('Name'),
|
||||
|
@ -396,9 +403,9 @@ class FHRPGroupFilterForm(NetBoxModelFilterSetForm):
|
|||
|
||||
class VLANGroupFilterForm(NetBoxModelFilterSetForm):
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id', 'tag')),
|
||||
(_('Location'), ('region', 'sitegroup', 'site', 'location', 'rack')),
|
||||
(_('VLAN ID'), ('min_vid', 'max_vid')),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('region', 'sitegroup', 'site', 'location', 'rack', name=_('Location')),
|
||||
FieldSet('min_vid', 'max_vid', name=_('VLAN ID')),
|
||||
)
|
||||
model = VLANGroup
|
||||
region = DynamicModelMultipleChoiceField(
|
||||
|
@ -444,10 +451,10 @@ class VLANGroupFilterForm(NetBoxModelFilterSetForm):
|
|||
class VLANFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
model = VLAN
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id', 'tag')),
|
||||
(_('Location'), ('region_id', 'site_group_id', 'site_id')),
|
||||
(_('Attributes'), ('group_id', 'status', 'role_id', 'vid', 'l2vpn_id')),
|
||||
(_('Tenant'), ('tenant_group_id', 'tenant_id')),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('region_id', 'site_group_id', 'site_id', name=_('Location')),
|
||||
FieldSet('group_id', 'status', 'role_id', 'vid', 'l2vpn_id', name=_('Attributes')),
|
||||
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
|
||||
)
|
||||
selector_fields = ('filter_id', 'q', 'site_id')
|
||||
region_id = DynamicModelMultipleChoiceField(
|
||||
|
@ -504,8 +511,8 @@ class VLANFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
|||
class ServiceTemplateFilterForm(NetBoxModelFilterSetForm):
|
||||
model = ServiceTemplate
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id', 'tag')),
|
||||
(_('Attributes'), ('protocol', 'port')),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('protocol', 'port', name=_('Attributes')),
|
||||
)
|
||||
protocol = forms.ChoiceField(
|
||||
label=_('Protocol'),
|
||||
|
@ -522,9 +529,9 @@ class ServiceTemplateFilterForm(NetBoxModelFilterSetForm):
|
|||
class ServiceFilterForm(ServiceTemplateFilterForm):
|
||||
model = Service
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter_id', 'tag')),
|
||||
(_('Attributes'), ('protocol', 'port')),
|
||||
(_('Assignment'), ('device_id', 'virtual_machine_id')),
|
||||
FieldSet('q', 'filter_id', 'tag'),
|
||||
FieldSet('protocol', 'port', name=_('Attributes')),
|
||||
FieldSet('device_id', 'virtual_machine_id', name=_('Assignment')),
|
||||
)
|
||||
device_id = DynamicModelMultipleChoiceField(
|
||||
queryset=Device.objects.all(),
|
||||
|
|
|
@ -16,6 +16,7 @@ from utilities.forms.fields import (
|
|||
CommentField, ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, NumericArrayField,
|
||||
SlugField,
|
||||
)
|
||||
from utilities.forms.rendering import FieldSet, InlineFields, ObjectAttribute, TabbedGroups
|
||||
from utilities.forms.widgets import DatePicker
|
||||
from virtualization.models import Cluster, ClusterGroup, VirtualMachine, VMInterface
|
||||
|
||||
|
@ -56,9 +57,9 @@ class VRFForm(TenancyForm, NetBoxModelForm):
|
|||
comments = CommentField()
|
||||
|
||||
fieldsets = (
|
||||
(_('VRF'), ('name', 'rd', 'enforce_unique', 'description', 'tags')),
|
||||
(_('Route Targets'), ('import_targets', 'export_targets')),
|
||||
(_('Tenancy'), ('tenant_group', 'tenant')),
|
||||
FieldSet('name', 'rd', 'enforce_unique', 'description', 'tags', name=_('VRF')),
|
||||
FieldSet('import_targets', 'export_targets', name=_('Route Targets')),
|
||||
FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -74,8 +75,8 @@ class VRFForm(TenancyForm, NetBoxModelForm):
|
|||
|
||||
class RouteTargetForm(TenancyForm, NetBoxModelForm):
|
||||
fieldsets = (
|
||||
('Route Target', ('name', 'description', 'tags')),
|
||||
('Tenancy', ('tenant_group', 'tenant')),
|
||||
FieldSet('name', 'description', 'tags', name=_('Route Target')),
|
||||
FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
|
@ -90,9 +91,7 @@ class RIRForm(NetBoxModelForm):
|
|||
slug = SlugField()
|
||||
|
||||
fieldsets = (
|
||||
(_('RIR'), (
|
||||
'name', 'slug', 'is_private', 'description', 'tags',
|
||||
)),
|
||||
FieldSet('name', 'slug', 'is_private', 'description', 'tags', name=_('RIR')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -110,8 +109,8 @@ class AggregateForm(TenancyForm, NetBoxModelForm):
|
|||
comments = CommentField()
|
||||
|
||||
fieldsets = (
|
||||
(_('Aggregate'), ('prefix', 'rir', 'date_added', 'description', 'tags')),
|
||||
(_('Tenancy'), ('tenant_group', 'tenant')),
|
||||
FieldSet('prefix', 'rir', 'date_added', 'description', 'tags', name=_('Aggregate')),
|
||||
FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -131,8 +130,8 @@ class ASNRangeForm(TenancyForm, NetBoxModelForm):
|
|||
)
|
||||
slug = SlugField()
|
||||
fieldsets = (
|
||||
(_('ASN Range'), ('name', 'slug', 'rir', 'start', 'end', 'description', 'tags')),
|
||||
(_('Tenancy'), ('tenant_group', 'tenant')),
|
||||
FieldSet('name', 'slug', 'rir', 'start', 'end', 'description', 'tags', name=_('ASN Range')),
|
||||
FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -155,8 +154,8 @@ class ASNForm(TenancyForm, NetBoxModelForm):
|
|||
comments = CommentField()
|
||||
|
||||
fieldsets = (
|
||||
(_('ASN'), ('asn', 'rir', 'sites', 'description', 'tags')),
|
||||
(_('Tenancy'), ('tenant_group', 'tenant')),
|
||||
FieldSet('asn', 'rir', 'sites', 'description', 'tags', name=_('ASN')),
|
||||
FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -184,9 +183,7 @@ class RoleForm(NetBoxModelForm):
|
|||
slug = SlugField()
|
||||
|
||||
fieldsets = (
|
||||
(_('Role'), (
|
||||
'name', 'slug', 'weight', 'description', 'tags',
|
||||
)),
|
||||
FieldSet('name', 'slug', 'weight', 'description', 'tags', name=_('Role')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -226,9 +223,11 @@ class PrefixForm(TenancyForm, NetBoxModelForm):
|
|||
comments = CommentField()
|
||||
|
||||
fieldsets = (
|
||||
(_('Prefix'), ('prefix', 'status', 'vrf', 'role', 'is_pool', 'mark_utilized', 'description', 'tags')),
|
||||
(_('Site/VLAN Assignment'), ('site', 'vlan')),
|
||||
(_('Tenancy'), ('tenant_group', 'tenant')),
|
||||
FieldSet(
|
||||
'prefix', 'status', 'vrf', 'role', 'is_pool', 'mark_utilized', 'description', 'tags', name=_('Prefix')
|
||||
),
|
||||
FieldSet('site', 'vlan', name=_('Site/VLAN Assignment')),
|
||||
FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -253,8 +252,11 @@ class IPRangeForm(TenancyForm, NetBoxModelForm):
|
|||
comments = CommentField()
|
||||
|
||||
fieldsets = (
|
||||
(_('IP Range'), ('vrf', 'start_address', 'end_address', 'role', 'status', 'mark_utilized', 'description', 'tags')),
|
||||
(_('Tenancy'), ('tenant_group', 'tenant')),
|
||||
FieldSet(
|
||||
'vrf', 'start_address', 'end_address', 'role', 'status', 'mark_utilized', 'description', 'tags',
|
||||
name=_('IP Range')
|
||||
),
|
||||
FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -307,6 +309,20 @@ class IPAddressForm(TenancyForm, NetBoxModelForm):
|
|||
)
|
||||
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:
|
||||
model = IPAddress
|
||||
fields = [
|
||||
|
@ -443,9 +459,9 @@ class FHRPGroupForm(NetBoxModelForm):
|
|||
comments = CommentField()
|
||||
|
||||
fieldsets = (
|
||||
(_('FHRP Group'), ('protocol', 'group_id', 'name', 'description', 'tags')),
|
||||
(_('Authentication'), ('auth_type', 'auth_key')),
|
||||
(_('Virtual IP Address'), ('ip_vrf', 'ip_address', 'ip_status'))
|
||||
FieldSet('protocol', 'group_id', 'name', 'description', 'tags', name=_('FHRP Group')),
|
||||
FieldSet('auth_type', 'auth_key', name=_('Authentication')),
|
||||
FieldSet('ip_vrf', 'ip_address', 'ip_status', name=_('Virtual IP Address'))
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -502,6 +518,10 @@ class FHRPGroupAssignmentForm(forms.ModelForm):
|
|||
queryset=FHRPGroup.objects.all()
|
||||
)
|
||||
|
||||
fieldsets = (
|
||||
FieldSet(ObjectAttribute('interface'), 'group', 'priority'),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = FHRPGroupAssignment
|
||||
fields = ('group', 'priority')
|
||||
|
@ -587,9 +607,12 @@ class VLANGroupForm(NetBoxModelForm):
|
|||
slug = SlugField()
|
||||
|
||||
fieldsets = (
|
||||
(_('VLAN Group'), ('name', 'slug', 'description', 'tags')),
|
||||
(_('Child VLANs'), ('min_vid', 'max_vid')),
|
||||
(_('Scope'), ('scope_type', 'region', 'sitegroup', 'site', 'location', 'rack', 'clustergroup', 'cluster')),
|
||||
FieldSet('name', 'slug', 'description', 'tags', name=_('VLAN Group')),
|
||||
FieldSet('min_vid', 'max_vid', name=_('Child VLANs')),
|
||||
FieldSet(
|
||||
'scope_type', 'region', 'sitegroup', 'site', 'location', 'rack', 'clustergroup', 'cluster',
|
||||
name=_('Scope')
|
||||
),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -662,9 +685,7 @@ class ServiceTemplateForm(NetBoxModelForm):
|
|||
comments = CommentField()
|
||||
|
||||
fieldsets = (
|
||||
(_('Service Template'), (
|
||||
'name', 'protocol', 'ports', 'description', 'tags',
|
||||
)),
|
||||
FieldSet('name', 'protocol', 'ports', 'description', 'tags', name=_('Service Template')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -704,6 +725,18 @@ class ServiceForm(NetBoxModelForm):
|
|||
)
|
||||
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:
|
||||
model = Service
|
||||
fields = [
|
||||
|
@ -718,6 +751,20 @@ class ServiceCreateForm(ServiceForm):
|
|||
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):
|
||||
fields = [
|
||||
'device', 'virtual_machine', 'service_template', 'name', 'protocol', 'ports', 'ipaddresses', 'description',
|
||||
|
|
|
@ -0,0 +1,119 @@
|
|||
import strawberry_django
|
||||
|
||||
from ipam import filtersets, models
|
||||
from netbox.graphql.filter_mixins import autotype_decorator, BaseFilterMixin
|
||||
|
||||
__all__ = (
|
||||
'ASNFilter',
|
||||
'ASNRangeFilter',
|
||||
'AggregateFilter',
|
||||
'FHRPGroupFilter',
|
||||
'FHRPGroupAssignmentFilter',
|
||||
'IPAddressFilter',
|
||||
'IPRangeFilter',
|
||||
'PrefixFilter',
|
||||
'RIRFilter',
|
||||
'RoleFilter',
|
||||
'RouteTargetFilter',
|
||||
'ServiceFilter',
|
||||
'ServiceTemplateFilter',
|
||||
'VLANFilter',
|
||||
'VLANGroupFilter',
|
||||
'VRFFilter',
|
||||
)
|
||||
|
||||
|
||||
@strawberry_django.filter(models.ASN, lookups=True)
|
||||
@autotype_decorator(filtersets.ASNFilterSet)
|
||||
class ASNFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.ASNRange, lookups=True)
|
||||
@autotype_decorator(filtersets.ASNRangeFilterSet)
|
||||
class ASNRangeFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.Aggregate, lookups=True)
|
||||
@autotype_decorator(filtersets.AggregateFilterSet)
|
||||
class AggregateFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.FHRPGroup, lookups=True)
|
||||
@autotype_decorator(filtersets.FHRPGroupFilterSet)
|
||||
class FHRPGroupFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.FHRPGroupAssignment, lookups=True)
|
||||
@autotype_decorator(filtersets.FHRPGroupAssignmentFilterSet)
|
||||
class FHRPGroupAssignmentFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.IPAddress, lookups=True)
|
||||
@autotype_decorator(filtersets.IPAddressFilterSet)
|
||||
class IPAddressFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.IPRange, lookups=True)
|
||||
@autotype_decorator(filtersets.IPRangeFilterSet)
|
||||
class IPRangeFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.Prefix, lookups=True)
|
||||
@autotype_decorator(filtersets.PrefixFilterSet)
|
||||
class PrefixFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.RIR, lookups=True)
|
||||
@autotype_decorator(filtersets.RIRFilterSet)
|
||||
class RIRFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.Role, lookups=True)
|
||||
@autotype_decorator(filtersets.RoleFilterSet)
|
||||
class RoleFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.RouteTarget, lookups=True)
|
||||
@autotype_decorator(filtersets.RouteTargetFilterSet)
|
||||
class RouteTargetFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.Service, lookups=True)
|
||||
@autotype_decorator(filtersets.ServiceFilterSet)
|
||||
class ServiceFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.ServiceTemplate, lookups=True)
|
||||
@autotype_decorator(filtersets.ServiceTemplateFilterSet)
|
||||
class ServiceTemplateFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.VLAN, lookups=True)
|
||||
@autotype_decorator(filtersets.VLANFilterSet)
|
||||
class VLANFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.VLANGroup, lookups=True)
|
||||
@autotype_decorator(filtersets.VLANGroupFilterSet)
|
||||
class VLANGroupFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
|
||||
@strawberry_django.filter(models.VRF, lookups=True)
|
||||
@autotype_decorator(filtersets.VRFFilterSet)
|
||||
class VRFFilter(BaseFilterMixin):
|
||||
pass
|
|
@ -1,95 +0,0 @@
|
|||
import graphene
|
||||
from dcim.graphql.types import (
|
||||
InterfaceType,
|
||||
LocationType,
|
||||
RackType,
|
||||
RegionType,
|
||||
SiteGroupType,
|
||||
SiteType,
|
||||
)
|
||||
from dcim.models import Interface, Location, Rack, Region, Site, SiteGroup
|
||||
from ipam.graphql.types import FHRPGroupType, VLANType
|
||||
from ipam.models import VLAN, FHRPGroup
|
||||
from virtualization.graphql.types import ClusterGroupType, ClusterType, VMInterfaceType
|
||||
from virtualization.models import Cluster, ClusterGroup, VMInterface
|
||||
|
||||
|
||||
class IPAddressAssignmentType(graphene.Union):
|
||||
class Meta:
|
||||
types = (
|
||||
InterfaceType,
|
||||
FHRPGroupType,
|
||||
VMInterfaceType,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def resolve_type(cls, instance, info):
|
||||
if type(instance) is Interface:
|
||||
return InterfaceType
|
||||
if type(instance) is FHRPGroup:
|
||||
return FHRPGroupType
|
||||
if type(instance) is VMInterface:
|
||||
return VMInterfaceType
|
||||
|
||||
|
||||
class L2VPNAssignmentType(graphene.Union):
|
||||
class Meta:
|
||||
types = (
|
||||
InterfaceType,
|
||||
VLANType,
|
||||
VMInterfaceType,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def resolve_type(cls, instance, info):
|
||||
if type(instance) is Interface:
|
||||
return InterfaceType
|
||||
if type(instance) is VLAN:
|
||||
return VLANType
|
||||
if type(instance) is VMInterface:
|
||||
return VMInterfaceType
|
||||
|
||||
|
||||
class FHRPGroupInterfaceType(graphene.Union):
|
||||
class Meta:
|
||||
types = (
|
||||
InterfaceType,
|
||||
VMInterfaceType,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def resolve_type(cls, instance, info):
|
||||
if type(instance) is Interface:
|
||||
return InterfaceType
|
||||
if type(instance) is VMInterface:
|
||||
return VMInterfaceType
|
||||
|
||||
|
||||
class VLANGroupScopeType(graphene.Union):
|
||||
class Meta:
|
||||
types = (
|
||||
ClusterType,
|
||||
ClusterGroupType,
|
||||
LocationType,
|
||||
RackType,
|
||||
RegionType,
|
||||
SiteType,
|
||||
SiteGroupType,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def resolve_type(cls, instance, info):
|
||||
if type(instance) is Cluster:
|
||||
return ClusterType
|
||||
if type(instance) is ClusterGroup:
|
||||
return ClusterGroupType
|
||||
if type(instance) is Location:
|
||||
return LocationType
|
||||
if type(instance) is Rack:
|
||||
return RackType
|
||||
if type(instance) is Region:
|
||||
return RegionType
|
||||
if type(instance) is Site:
|
||||
return SiteType
|
||||
if type(instance) is SiteGroup:
|
||||
return SiteGroupType
|
|
@ -1,4 +1,7 @@
|
|||
import graphene
|
||||
from typing import Annotated, List
|
||||
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
|
||||
__all__ = (
|
||||
'IPAddressesMixin',
|
||||
|
@ -6,15 +9,15 @@ __all__ = (
|
|||
)
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class IPAddressesMixin:
|
||||
ip_addresses = graphene.List('ipam.graphql.types.IPAddressType')
|
||||
|
||||
def resolve_ip_addresses(self, info):
|
||||
return self.ip_addresses.restrict(info.context.user, 'view')
|
||||
@strawberry_django.field
|
||||
def ip_addresses(self) -> List[Annotated["IPAddressType", strawberry.lazy('ipam.graphql.types')]]:
|
||||
return self.ip_addresses.all()
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class VLANGroupsMixin:
|
||||
vlan_groups = graphene.List('ipam.graphql.types.VLANGroupType')
|
||||
|
||||
def resolve_vlan_groups(self, info):
|
||||
return self.vlan_groups.restrict(info.context.user, 'view')
|
||||
@strawberry_django.field
|
||||
def vlan_groups(self) -> List[Annotated["VLANGroupType", strawberry.lazy('ipam.graphql.types')]]:
|
||||
return self.vlan_groups.all()
|
||||
|
|
|
@ -1,104 +1,90 @@
|
|||
import graphene
|
||||
from typing import List
|
||||
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
|
||||
from ipam import models
|
||||
from netbox.graphql.fields import ObjectField, ObjectListField
|
||||
from utilities.graphql_optimizer import gql_query_optimizer
|
||||
from .types import *
|
||||
|
||||
|
||||
class IPAMQuery(graphene.ObjectType):
|
||||
asn = ObjectField(ASNType)
|
||||
asn_list = ObjectListField(ASNType)
|
||||
@strawberry.type
|
||||
class IPAMQuery:
|
||||
@strawberry.field
|
||||
def asn(self, id: int) -> ASNType:
|
||||
return models.ASN.objects.get(pk=id)
|
||||
asn_list: List[ASNType] = strawberry_django.field()
|
||||
|
||||
def resolve_asn_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.ASN.objects.all(), info)
|
||||
@strawberry.field
|
||||
def asn_range(self, id: int) -> ASNRangeType:
|
||||
return models.ASNRange.objects.get(pk=id)
|
||||
asn_range_list: List[ASNRangeType] = strawberry_django.field()
|
||||
|
||||
asn_range = ObjectField(ASNRangeType)
|
||||
asn_range_list = ObjectListField(ASNRangeType)
|
||||
@strawberry.field
|
||||
def aggregate(self, id: int) -> AggregateType:
|
||||
return models.Aggregate.objects.get(pk=id)
|
||||
aggregate_list: List[AggregateType] = strawberry_django.field()
|
||||
|
||||
def resolve_asn_range_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.ASNRange.objects.all(), info)
|
||||
@strawberry.field
|
||||
def ip_address(self, id: int) -> IPAddressType:
|
||||
return models.IPAddress.objects.get(pk=id)
|
||||
ip_address_list: List[IPAddressType] = strawberry_django.field()
|
||||
|
||||
aggregate = ObjectField(AggregateType)
|
||||
aggregate_list = ObjectListField(AggregateType)
|
||||
@strawberry.field
|
||||
def ip_range(self, id: int) -> IPRangeType:
|
||||
return models.IPRange.objects.get(pk=id)
|
||||
ip_range_list: List[IPRangeType] = strawberry_django.field()
|
||||
|
||||
def resolve_aggregate_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.Aggregate.objects.all(), info)
|
||||
@strawberry.field
|
||||
def prefix(self, id: int) -> PrefixType:
|
||||
return models.Prefix.objects.get(pk=id)
|
||||
prefix_list: List[PrefixType] = strawberry_django.field()
|
||||
|
||||
ip_address = ObjectField(IPAddressType)
|
||||
ip_address_list = ObjectListField(IPAddressType)
|
||||
@strawberry.field
|
||||
def rir(self, id: int) -> RIRType:
|
||||
return models.RIR.objects.get(pk=id)
|
||||
rir_list: List[RIRType] = strawberry_django.field()
|
||||
|
||||
def resolve_ip_address_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.IPAddress.objects.all(), info)
|
||||
@strawberry.field
|
||||
def role(self, id: int) -> RoleType:
|
||||
return models.Role.objects.get(pk=id)
|
||||
role_list: List[RoleType] = strawberry_django.field()
|
||||
|
||||
ip_range = ObjectField(IPRangeType)
|
||||
ip_range_list = ObjectListField(IPRangeType)
|
||||
@strawberry.field
|
||||
def route_target(self, id: int) -> RouteTargetType:
|
||||
return models.RouteTarget.objects.get(pk=id)
|
||||
route_target_list: List[RouteTargetType] = strawberry_django.field()
|
||||
|
||||
def resolve_ip_range_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.IPRange.objects.all(), info)
|
||||
@strawberry.field
|
||||
def service(self, id: int) -> ServiceType:
|
||||
return models.Service.objects.get(pk=id)
|
||||
service_list: List[ServiceType] = strawberry_django.field()
|
||||
|
||||
prefix = ObjectField(PrefixType)
|
||||
prefix_list = ObjectListField(PrefixType)
|
||||
@strawberry.field
|
||||
def service_template(self, id: int) -> ServiceTemplateType:
|
||||
return models.ServiceTemplate.objects.get(pk=id)
|
||||
service_template_list: List[ServiceTemplateType] = strawberry_django.field()
|
||||
|
||||
def resolve_prefix_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.Prefix.objects.all(), info)
|
||||
@strawberry.field
|
||||
def fhrp_group(self, id: int) -> FHRPGroupType:
|
||||
return models.FHRPGroup.objects.get(pk=id)
|
||||
fhrp_group_list: List[FHRPGroupType] = strawberry_django.field()
|
||||
|
||||
rir = ObjectField(RIRType)
|
||||
rir_list = ObjectListField(RIRType)
|
||||
@strawberry.field
|
||||
def fhrp_group_assignment(self, id: int) -> FHRPGroupAssignmentType:
|
||||
return models.FHRPGroupAssignment.objects.get(pk=id)
|
||||
fhrp_group_assignment_list: List[FHRPGroupAssignmentType] = strawberry_django.field()
|
||||
|
||||
def resolve_rir_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.RIR.objects.all(), info)
|
||||
@strawberry.field
|
||||
def vlan(self, id: int) -> VLANType:
|
||||
return models.VLAN.objects.get(pk=id)
|
||||
vlan_list: List[VLANType] = strawberry_django.field()
|
||||
|
||||
role = ObjectField(RoleType)
|
||||
role_list = ObjectListField(RoleType)
|
||||
@strawberry.field
|
||||
def vlan_group(self, id: int) -> VLANGroupType:
|
||||
return models.VLANGroup.objects.get(pk=id)
|
||||
vlan_group_list: List[VLANGroupType] = strawberry_django.field()
|
||||
|
||||
def resolve_role_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.Role.objects.all(), info)
|
||||
|
||||
route_target = ObjectField(RouteTargetType)
|
||||
route_target_list = ObjectListField(RouteTargetType)
|
||||
|
||||
def resolve_route_target_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.RouteTarget.objects.all(), info)
|
||||
|
||||
service = ObjectField(ServiceType)
|
||||
service_list = ObjectListField(ServiceType)
|
||||
|
||||
def resolve_service_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.Service.objects.all(), info)
|
||||
|
||||
service_template = ObjectField(ServiceTemplateType)
|
||||
service_template_list = ObjectListField(ServiceTemplateType)
|
||||
|
||||
def resolve_service_template_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.ServiceTemplate.objects.all(), info)
|
||||
|
||||
fhrp_group = ObjectField(FHRPGroupType)
|
||||
fhrp_group_list = ObjectListField(FHRPGroupType)
|
||||
|
||||
def resolve_fhrp_group_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.FHRPGroup.objects.all(), info)
|
||||
|
||||
fhrp_group_assignment = ObjectField(FHRPGroupAssignmentType)
|
||||
fhrp_group_assignment_list = ObjectListField(FHRPGroupAssignmentType)
|
||||
|
||||
def resolve_fhrp_group_assignment_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.FHRPGroupAssignment.objects.all(), info)
|
||||
|
||||
vlan = ObjectField(VLANType)
|
||||
vlan_list = ObjectListField(VLANType)
|
||||
|
||||
def resolve_vlan_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.VLAN.objects.all(), info)
|
||||
|
||||
vlan_group = ObjectField(VLANGroupType)
|
||||
vlan_group_list = ObjectListField(VLANGroupType)
|
||||
|
||||
def resolve_vlan_group_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.VLANGroup.objects.all(), info)
|
||||
|
||||
vrf = ObjectField(VRFType)
|
||||
vrf_list = ObjectListField(VRFType)
|
||||
|
||||
def resolve_vrf_list(root, info, **kwargs):
|
||||
return gql_query_optimizer(models.VRF.objects.all(), info)
|
||||
@strawberry.field
|
||||
def vrf(self, id: int) -> VRFType:
|
||||
return models.VRF.objects.get(pk=id)
|
||||
vrf_list: List[VRFType] = strawberry_django.field()
|
||||
|
|
|
@ -1,9 +1,15 @@
|
|||
import graphene
|
||||
from typing import Annotated, List, Union
|
||||
|
||||
from ipam import filtersets, models
|
||||
from .mixins import IPAddressesMixin
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
|
||||
from circuits.graphql.types import ProviderType
|
||||
from dcim.graphql.types import SiteType
|
||||
from ipam import models
|
||||
from netbox.graphql.scalars import BigInt
|
||||
from netbox.graphql.types import BaseObjectType, OrganizationalObjectType, NetBoxObjectType
|
||||
from netbox.graphql.types import BaseObjectType, NetBoxObjectType, OrganizationalObjectType
|
||||
from .filters import *
|
||||
from .mixins import IPAddressesMixin
|
||||
|
||||
__all__ = (
|
||||
'ASNType',
|
||||
|
@ -25,164 +31,335 @@ __all__ = (
|
|||
)
|
||||
|
||||
|
||||
class IPAddressFamilyType(graphene.ObjectType):
|
||||
|
||||
value = graphene.Int()
|
||||
label = graphene.String()
|
||||
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
self.label = f'IPv{value}'
|
||||
@strawberry.type
|
||||
class IPAddressFamilyType:
|
||||
value: int
|
||||
label: str
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class BaseIPAddressFamilyType:
|
||||
"""
|
||||
Base type for models that need to expose their IPAddress family type.
|
||||
"""
|
||||
family = graphene.Field(IPAddressFamilyType)
|
||||
|
||||
def resolve_family(self, _):
|
||||
@strawberry.field
|
||||
def family(self) -> IPAddressFamilyType:
|
||||
# Note that self, is an instance of models.IPAddress
|
||||
# thus resolves to the address family value.
|
||||
return IPAddressFamilyType(self.family)
|
||||
return IPAddressFamilyType(value=self.family, label=f'IPv{self.family}')
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.ASN,
|
||||
fields='__all__',
|
||||
filters=ASNFilter
|
||||
)
|
||||
class ASNType(NetBoxObjectType):
|
||||
asn = graphene.Field(BigInt)
|
||||
asn: BigInt
|
||||
rir: Annotated["RIRType", strawberry.lazy('ipam.graphql.types')] | None
|
||||
tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
|
||||
|
||||
class Meta:
|
||||
model = models.ASN
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.ASNFilterSet
|
||||
@strawberry_django.field
|
||||
def sites(self) -> List[SiteType]:
|
||||
return self.sites.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def providers(self) -> List[ProviderType]:
|
||||
return self.providers.all()
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.ASNRange,
|
||||
fields='__all__',
|
||||
filters=ASNRangeFilter
|
||||
)
|
||||
class ASNRangeType(NetBoxObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.ASNRange
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.ASNRangeFilterSet
|
||||
start: BigInt
|
||||
end: BigInt
|
||||
rir: Annotated["RIRType", strawberry.lazy('ipam.graphql.types')] | None
|
||||
tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.Aggregate,
|
||||
fields='__all__',
|
||||
filters=AggregateFilter
|
||||
)
|
||||
class AggregateType(NetBoxObjectType, BaseIPAddressFamilyType):
|
||||
|
||||
class Meta:
|
||||
model = models.Aggregate
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.AggregateFilterSet
|
||||
prefix: str
|
||||
rir: Annotated["RIRType", strawberry.lazy('ipam.graphql.types')] | None
|
||||
tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.FHRPGroup,
|
||||
fields='__all__',
|
||||
filters=FHRPGroupFilter
|
||||
)
|
||||
class FHRPGroupType(NetBoxObjectType, IPAddressesMixin):
|
||||
|
||||
class Meta:
|
||||
model = models.FHRPGroup
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.FHRPGroupFilterSet
|
||||
|
||||
def resolve_auth_type(self, info):
|
||||
return self.auth_type or None
|
||||
@strawberry_django.field
|
||||
def fhrpgroupassignment_set(self) -> List[Annotated["FHRPGroupAssignmentType", strawberry.lazy('ipam.graphql.types')]]:
|
||||
return self.fhrpgroupassignment_set.all()
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.FHRPGroupAssignment,
|
||||
exclude=('interface_type', 'interface_id'),
|
||||
filters=FHRPGroupAssignmentFilter
|
||||
)
|
||||
class FHRPGroupAssignmentType(BaseObjectType):
|
||||
interface = graphene.Field('ipam.graphql.gfk_mixins.FHRPGroupInterfaceType')
|
||||
group: Annotated["FHRPGroupType", strawberry.lazy('ipam.graphql.types')]
|
||||
|
||||
class Meta:
|
||||
model = models.FHRPGroupAssignment
|
||||
exclude = ('interface_type', 'interface_id')
|
||||
filterset_class = filtersets.FHRPGroupAssignmentFilterSet
|
||||
@strawberry_django.field
|
||||
def interface(self) -> Annotated[Union[
|
||||
Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')],
|
||||
Annotated["VMInterfaceType", strawberry.lazy('virtualization.graphql.types')],
|
||||
], strawberry.union("FHRPGroupInterfaceType")]:
|
||||
return self.interface
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.IPAddress,
|
||||
exclude=('assigned_object_type', 'assigned_object_id', 'address'),
|
||||
filters=IPAddressFilter
|
||||
)
|
||||
class IPAddressType(NetBoxObjectType, BaseIPAddressFamilyType):
|
||||
assigned_object = graphene.Field('ipam.graphql.gfk_mixins.IPAddressAssignmentType')
|
||||
address: str
|
||||
vrf: Annotated["VRFType", strawberry.lazy('ipam.graphql.types')] | None
|
||||
tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
|
||||
nat_inside: Annotated["IPAddressType", strawberry.lazy('ipam.graphql.types')] | None
|
||||
|
||||
class Meta:
|
||||
model = models.IPAddress
|
||||
exclude = ('assigned_object_type', 'assigned_object_id')
|
||||
filterset_class = filtersets.IPAddressFilterSet
|
||||
@strawberry_django.field
|
||||
def nat_outside(self) -> List[Annotated["IPAddressType", strawberry.lazy('ipam.graphql.types')]]:
|
||||
return self.nat_outside.all()
|
||||
|
||||
def resolve_role(self, info):
|
||||
return self.role or None
|
||||
@strawberry_django.field
|
||||
def tunnel_terminations(self) -> List[Annotated["TunnelTerminationType", strawberry.lazy('vpn.graphql.types')]]:
|
||||
return self.tunnel_terminations.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def services(self) -> List[Annotated["ServiceType", strawberry.lazy('ipam.graphql.types')]]:
|
||||
return self.services.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def assigned_object(self) -> Annotated[Union[
|
||||
Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')],
|
||||
Annotated["FHRPGroupType", strawberry.lazy('ipam.graphql.types')],
|
||||
Annotated["VMInterfaceType", strawberry.lazy('virtualization.graphql.types')],
|
||||
], strawberry.union("IPAddressAssignmentType")]:
|
||||
return self.assigned_object
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.IPRange,
|
||||
fields='__all__',
|
||||
filters=IPRangeFilter
|
||||
)
|
||||
class IPRangeType(NetBoxObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.IPRange
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.IPRangeFilterSet
|
||||
|
||||
def resolve_role(self, info):
|
||||
return self.role or None
|
||||
start_address: str
|
||||
end_address: str
|
||||
vrf: Annotated["VRFType", strawberry.lazy('ipam.graphql.types')] | None
|
||||
tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
|
||||
role: Annotated["RoleType", strawberry.lazy('ipam.graphql.types')] | None
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.Prefix,
|
||||
fields='__all__',
|
||||
filters=PrefixFilter
|
||||
)
|
||||
class PrefixType(NetBoxObjectType, BaseIPAddressFamilyType):
|
||||
|
||||
class Meta:
|
||||
model = models.Prefix
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.PrefixFilterSet
|
||||
prefix: str
|
||||
site: Annotated["SiteType", strawberry.lazy('dcim.graphql.types')] | None
|
||||
vrf: Annotated["VRFType", strawberry.lazy('ipam.graphql.types')] | None
|
||||
tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
|
||||
vlan: Annotated["VLANType", strawberry.lazy('ipam.graphql.types')] | None
|
||||
role: Annotated["RoleType", strawberry.lazy('ipam.graphql.types')] | None
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.RIR,
|
||||
fields='__all__',
|
||||
filters=RIRFilter
|
||||
)
|
||||
class RIRType(OrganizationalObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.RIR
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.RIRFilterSet
|
||||
@strawberry_django.field
|
||||
def asn_ranges(self) -> List[Annotated["ASNRangeType", strawberry.lazy('ipam.graphql.types')]]:
|
||||
return self.asn_ranges.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def asns(self) -> List[Annotated["ASNType", strawberry.lazy('ipam.graphql.types')]]:
|
||||
return self.asns.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def aggregates(self) -> List[Annotated["AggregateType", strawberry.lazy('ipam.graphql.types')]]:
|
||||
return self.aggregates.all()
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.Role,
|
||||
fields='__all__',
|
||||
filters=RoleFilter
|
||||
)
|
||||
class RoleType(OrganizationalObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.Role
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.RoleFilterSet
|
||||
@strawberry_django.field
|
||||
def prefixes(self) -> List[Annotated["PrefixType", strawberry.lazy('ipam.graphql.types')]]:
|
||||
return self.prefixes.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def ip_ranges(self) -> List[Annotated["IPRangeType", strawberry.lazy('ipam.graphql.types')]]:
|
||||
return self.ip_ranges.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def vlans(self) -> List[Annotated["VLANType", strawberry.lazy('ipam.graphql.types')]]:
|
||||
return self.vlans.all()
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.RouteTarget,
|
||||
fields='__all__',
|
||||
filters=RouteTargetFilter
|
||||
)
|
||||
class RouteTargetType(NetBoxObjectType):
|
||||
tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
|
||||
|
||||
class Meta:
|
||||
model = models.RouteTarget
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.RouteTargetFilterSet
|
||||
@strawberry_django.field
|
||||
def exporting_l2vpns(self) -> List[Annotated["L2VPNType", strawberry.lazy('vpn.graphql.types')]]:
|
||||
return self.exporting_l2vpns.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def exporting_vrfs(self) -> List[Annotated["VRFType", strawberry.lazy('ipam.graphql.types')]]:
|
||||
return self.exporting_vrfs.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def importing_vrfs(self) -> List[Annotated["VRFType", strawberry.lazy('ipam.graphql.types')]]:
|
||||
return self.importing_vrfs.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def importing_l2vpns(self) -> List[Annotated["L2VPNType", strawberry.lazy('vpn.graphql.types')]]:
|
||||
return self.importing_l2vpns.all()
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.Service,
|
||||
fields='__all__',
|
||||
filters=ServiceFilter
|
||||
)
|
||||
class ServiceType(NetBoxObjectType):
|
||||
ports: List[int]
|
||||
device: Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')] | None
|
||||
virtual_machine: Annotated["VirtualMachineType", strawberry.lazy('virtualization.graphql.types')] | None
|
||||
|
||||
class Meta:
|
||||
model = models.Service
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.ServiceFilterSet
|
||||
@strawberry_django.field
|
||||
def ipaddresses(self) -> List[Annotated["IPAddressType", strawberry.lazy('ipam.graphql.types')]]:
|
||||
return self.ipaddresses.all()
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.ServiceTemplate,
|
||||
fields='__all__',
|
||||
filters=ServiceTemplateFilter
|
||||
)
|
||||
class ServiceTemplateType(NetBoxObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.ServiceTemplate
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.ServiceTemplateFilterSet
|
||||
ports: List[int]
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.VLAN,
|
||||
fields='__all__',
|
||||
filters=VLANFilter
|
||||
)
|
||||
class VLANType(NetBoxObjectType):
|
||||
site: Annotated["SiteType", strawberry.lazy('ipam.graphql.types')] | None
|
||||
group: Annotated["VLANGroupType", strawberry.lazy('ipam.graphql.types')] | None
|
||||
tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
|
||||
role: Annotated["RoleType", strawberry.lazy('ipam.graphql.types')] | None
|
||||
|
||||
class Meta:
|
||||
model = models.VLAN
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.VLANFilterSet
|
||||
@strawberry_django.field
|
||||
def interfaces_as_untagged(self) -> List[Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]]:
|
||||
return self.interfaces_as_untagged.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def vminterfaces_as_untagged(self) -> List[Annotated["VMInterfaceType", strawberry.lazy('virtualization.graphql.types')]]:
|
||||
return self.vminterfaces_as_untagged.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def wirelesslan_set(self) -> List[Annotated["WirelessLANType", strawberry.lazy('wireless.graphql.types')]]:
|
||||
return self.wirelesslan_set.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def prefixes(self) -> List[Annotated["PrefixType", strawberry.lazy('ipam.graphql.types')]]:
|
||||
return self.prefixes.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def interfaces_as_tagged(self) -> List[Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]]:
|
||||
return self.interfaces_as_tagged.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def vminterfaces_as_tagged(self) -> List[Annotated["VMInterfaceType", strawberry.lazy('virtualization.graphql.types')]]:
|
||||
return self.vminterfaces_as_tagged.all()
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.VLANGroup,
|
||||
exclude=('scope_type', 'scope_id'),
|
||||
filters=VLANGroupFilter
|
||||
)
|
||||
class VLANGroupType(OrganizationalObjectType):
|
||||
scope = graphene.Field('ipam.graphql.gfk_mixins.VLANGroupScopeType')
|
||||
|
||||
class Meta:
|
||||
model = models.VLANGroup
|
||||
exclude = ('scope_type', 'scope_id')
|
||||
filterset_class = filtersets.VLANGroupFilterSet
|
||||
@strawberry_django.field
|
||||
def vlans(self) -> List[VLANType]:
|
||||
return self.vlans.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def scope(self) -> Annotated[Union[
|
||||
Annotated["ClusterType", strawberry.lazy('virtualization.graphql.types')],
|
||||
Annotated["ClusterGroupType", strawberry.lazy('virtualization.graphql.types')],
|
||||
Annotated["LocationType", strawberry.lazy('dcim.graphql.types')],
|
||||
Annotated["RackType", strawberry.lazy('dcim.graphql.types')],
|
||||
Annotated["RegionType", strawberry.lazy('dcim.graphql.types')],
|
||||
Annotated["SiteType", strawberry.lazy('dcim.graphql.types')],
|
||||
Annotated["SiteGroupType", strawberry.lazy('dcim.graphql.types')],
|
||||
], strawberry.union("VLANGroupScopeType")]:
|
||||
return self.scope
|
||||
|
||||
|
||||
@strawberry_django.type(
|
||||
models.VRF,
|
||||
fields='__all__',
|
||||
filters=VRFFilter
|
||||
)
|
||||
class VRFType(NetBoxObjectType):
|
||||
tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
|
||||
|
||||
class Meta:
|
||||
model = models.VRF
|
||||
fields = '__all__'
|
||||
filterset_class = filtersets.VRFFilterSet
|
||||
@strawberry_django.field
|
||||
def interfaces(self) -> List[Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]]:
|
||||
return self.interfaces.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def ip_addresses(self) -> List[Annotated["IPAddressType", strawberry.lazy('ipam.graphql.types')]]:
|
||||
return self.ip_addresses.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def vminterfaces(self) -> List[Annotated["VMInterfaceType", strawberry.lazy('virtualization.graphql.types')]]:
|
||||
return self.vminterfaces.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def ip_ranges(self) -> List[Annotated["IPRangeType", strawberry.lazy('ipam.graphql.types')]]:
|
||||
return self.ip_ranges.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def export_targets(self) -> List[Annotated["RouteTargetType", strawberry.lazy('ipam.graphql.types')]]:
|
||||
return self.export_targets.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def import_targets(self) -> List[Annotated["RouteTargetType", strawberry.lazy('ipam.graphql.types')]]:
|
||||
return self.import_targets.all()
|
||||
|
||||
@strawberry_django.field
|
||||
def prefixes(self) -> List[Annotated["PrefixType", strawberry.lazy('ipam.graphql.types')]]:
|
||||
return self.prefixes.all()
|
||||
|
|
|
@ -8,8 +8,7 @@ from django.utils.translation import gettext_lazy as _
|
|||
from ipam.choices import *
|
||||
from ipam.constants import *
|
||||
from netbox.models import PrimaryModel
|
||||
from utilities.utils import array_to_string
|
||||
|
||||
from utilities.data import array_to_string
|
||||
|
||||
__all__ = (
|
||||
'Service',
|
||||
|
|
|
@ -3,8 +3,8 @@ from django.db.models import Count, F, OuterRef, Q, Subquery, Value
|
|||
from django.db.models.expressions import RawSQL
|
||||
from django.db.models.functions import Round
|
||||
|
||||
from utilities.query import count_related
|
||||
from utilities.querysets import RestrictedQuerySet
|
||||
from utilities.utils import count_related
|
||||
|
||||
__all__ = (
|
||||
'ASNRangeQuerySet',
|
||||
|
|
|
@ -9,8 +9,8 @@ from circuits.models import Provider
|
|||
from dcim.filtersets import InterfaceFilterSet
|
||||
from dcim.models import Interface, Site
|
||||
from netbox.views import generic
|
||||
from utilities.query import count_related
|
||||
from utilities.tables import get_table_ordering
|
||||
from utilities.utils import count_related
|
||||
from utilities.views import ViewTab, register_model_view
|
||||
from virtualization.filtersets import VMInterfaceFilterSet
|
||||
from virtualization.models import VMInterface
|
||||
|
@ -781,7 +781,6 @@ class IPAddressView(generic.ObjectView):
|
|||
class IPAddressEditView(generic.ObjectEditView):
|
||||
queryset = IPAddress.objects.all()
|
||||
form = forms.IPAddressForm
|
||||
template_name = 'ipam/ipaddress_edit.html'
|
||||
|
||||
def alter_object(self, obj, request, url_args, url_kwargs):
|
||||
|
||||
|
@ -1059,7 +1058,6 @@ class FHRPGroupBulkDeleteView(generic.BulkDeleteView):
|
|||
class FHRPGroupAssignmentEditView(generic.ObjectEditView):
|
||||
queryset = FHRPGroupAssignment.objects.all()
|
||||
form = forms.FHRPGroupAssignmentForm
|
||||
template_name = 'ipam/fhrpgroupassignment_edit.html'
|
||||
|
||||
def alter_object(self, instance, request, args, kwargs):
|
||||
if not instance.pk:
|
||||
|
@ -1236,14 +1234,12 @@ class ServiceView(generic.ObjectView):
|
|||
class ServiceCreateView(generic.ObjectEditView):
|
||||
queryset = Service.objects.all()
|
||||
form = forms.ServiceCreateForm
|
||||
template_name = 'ipam/service_create.html'
|
||||
|
||||
|
||||
@register_model_view(Service, 'edit')
|
||||
class ServiceEditView(generic.ObjectEditView):
|
||||
queryset = Service.objects.all()
|
||||
form = forms.ServiceForm
|
||||
template_name = 'ipam/service_edit.html'
|
||||
|
||||
|
||||
@register_model_view(Service, 'delete')
|
||||
|
|
|
@ -2,9 +2,10 @@ from django.contrib.contenttypes.models import ContentType
|
|||
from drf_spectacular.utils import extend_schema_field
|
||||
from rest_framework import serializers
|
||||
|
||||
from core.models import ObjectType
|
||||
from netbox.api.fields import ContentTypeField
|
||||
from utilities.api import get_serializer_for_model
|
||||
from utilities.utils import content_type_identifier
|
||||
from utilities.object_types import object_type_identifier
|
||||
|
||||
__all__ = (
|
||||
'GenericObjectSerializer',
|
||||
|
@ -27,9 +28,9 @@ class GenericObjectSerializer(serializers.Serializer):
|
|||
return model.objects.get(pk=data['object_id'])
|
||||
|
||||
def to_representation(self, instance):
|
||||
ct = ContentType.objects.get_for_model(instance)
|
||||
object_type = ObjectType.objects.get_for_model(instance)
|
||||
data = {
|
||||
'object_type': content_type_identifier(ct),
|
||||
'object_type': object_type_identifier(object_type),
|
||||
'object_id': instance.pk,
|
||||
}
|
||||
if 'request' in self.context:
|
||||
|
|
|
@ -12,7 +12,7 @@ from django.utils.translation import gettext_lazy as _
|
|||
from users.constants import CONSTRAINT_TOKEN_USER
|
||||
from users.models import Group, ObjectPermission
|
||||
from utilities.permissions import (
|
||||
permission_is_exempt, qs_filter_from_constraints, resolve_permission, resolve_permission_ct,
|
||||
permission_is_exempt, qs_filter_from_constraints, resolve_permission, resolve_permission_type,
|
||||
)
|
||||
|
||||
UserModel = get_user_model()
|
||||
|
@ -284,11 +284,9 @@ class RemoteUserBackend(_RemoteUserBackend):
|
|||
permissions_list = []
|
||||
for permission_name, constraints in settings.REMOTE_AUTH_DEFAULT_PERMISSIONS.items():
|
||||
try:
|
||||
object_type, action = resolve_permission_ct(
|
||||
permission_name)
|
||||
# TODO: Merge multiple actions into a single ObjectPermission per content type
|
||||
obj_perm = ObjectPermission(
|
||||
actions=[action], constraints=constraints)
|
||||
object_type, action = resolve_permission_type(permission_name)
|
||||
# TODO: Merge multiple actions into a single ObjectPermission per object type
|
||||
obj_perm = ObjectPermission(actions=[action], constraints=constraints)
|
||||
obj_perm.save()
|
||||
obj_perm.users.add(user)
|
||||
obj_perm.object_types.add(object_type)
|
||||
|
@ -303,7 +301,9 @@ class RemoteUserBackend(_RemoteUserBackend):
|
|||
f"Assigned permissions to remotely-authenticated user {user}: {permissions_list}")
|
||||
else:
|
||||
logger.debug(
|
||||
f"Skipped initial assignment of permissions and groups to remotely-authenticated user {user} as Group sync is enabled")
|
||||
f"Skipped initial assignment of permissions and groups to remotely-authenticated user {user} as "
|
||||
f"Group sync is enabled"
|
||||
)
|
||||
|
||||
return user
|
||||
|
||||
|
|
|
@ -0,0 +1,162 @@
|
|||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from utilities.choices import ChoiceSet
|
||||
from utilities.constants import CSV_DELIMITERS
|
||||
|
||||
__all__ = (
|
||||
'ButtonColorChoices',
|
||||
'ColorChoices',
|
||||
'CSVDelimiterChoices',
|
||||
'ImportFormatChoices',
|
||||
'ImportMethodChoices',
|
||||
)
|
||||
|
||||
|
||||
#
|
||||
# Generic color choices
|
||||
#
|
||||
|
||||
class ColorChoices(ChoiceSet):
|
||||
COLOR_DARK_RED = 'aa1409'
|
||||
COLOR_RED = 'f44336'
|
||||
COLOR_PINK = 'e91e63'
|
||||
COLOR_ROSE = 'ffe4e1'
|
||||
COLOR_FUCHSIA = 'ff66ff'
|
||||
COLOR_PURPLE = '9c27b0'
|
||||
COLOR_DARK_PURPLE = '673ab7'
|
||||
COLOR_INDIGO = '3f51b5'
|
||||
COLOR_BLUE = '2196f3'
|
||||
COLOR_LIGHT_BLUE = '03a9f4'
|
||||
COLOR_CYAN = '00bcd4'
|
||||
COLOR_TEAL = '009688'
|
||||
COLOR_AQUA = '00ffff'
|
||||
COLOR_DARK_GREEN = '2f6a31'
|
||||
COLOR_GREEN = '4caf50'
|
||||
COLOR_LIGHT_GREEN = '8bc34a'
|
||||
COLOR_LIME = 'cddc39'
|
||||
COLOR_YELLOW = 'ffeb3b'
|
||||
COLOR_AMBER = 'ffc107'
|
||||
COLOR_ORANGE = 'ff9800'
|
||||
COLOR_DARK_ORANGE = 'ff5722'
|
||||
COLOR_BROWN = '795548'
|
||||
COLOR_LIGHT_GREY = 'c0c0c0'
|
||||
COLOR_GREY = '9e9e9e'
|
||||
COLOR_DARK_GREY = '607d8b'
|
||||
COLOR_BLACK = '111111'
|
||||
COLOR_WHITE = 'ffffff'
|
||||
|
||||
CHOICES = (
|
||||
(COLOR_DARK_RED, _('Dark Red')),
|
||||
(COLOR_RED, _('Red')),
|
||||
(COLOR_PINK, _('Pink')),
|
||||
(COLOR_ROSE, _('Rose')),
|
||||
(COLOR_FUCHSIA, _('Fuchsia')),
|
||||
(COLOR_PURPLE, _('Purple')),
|
||||
(COLOR_DARK_PURPLE, _('Dark Purple')),
|
||||
(COLOR_INDIGO, _('Indigo')),
|
||||
(COLOR_BLUE, _('Blue')),
|
||||
(COLOR_LIGHT_BLUE, _('Light Blue')),
|
||||
(COLOR_CYAN, _('Cyan')),
|
||||
(COLOR_TEAL, _('Teal')),
|
||||
(COLOR_AQUA, _('Aqua')),
|
||||
(COLOR_DARK_GREEN, _('Dark Green')),
|
||||
(COLOR_GREEN, _('Green')),
|
||||
(COLOR_LIGHT_GREEN, _('Light Green')),
|
||||
(COLOR_LIME, _('Lime')),
|
||||
(COLOR_YELLOW, _('Yellow')),
|
||||
(COLOR_AMBER, _('Amber')),
|
||||
(COLOR_ORANGE, _('Orange')),
|
||||
(COLOR_DARK_ORANGE, _('Dark Orange')),
|
||||
(COLOR_BROWN, _('Brown')),
|
||||
(COLOR_LIGHT_GREY, _('Light Grey')),
|
||||
(COLOR_GREY, _('Grey')),
|
||||
(COLOR_DARK_GREY, _('Dark Grey')),
|
||||
(COLOR_BLACK, _('Black')),
|
||||
(COLOR_WHITE, _('White')),
|
||||
)
|
||||
|
||||
|
||||
#
|
||||
# Button color choices
|
||||
#
|
||||
|
||||
class ButtonColorChoices(ChoiceSet):
|
||||
"""
|
||||
Map standard button color choices to Bootstrap 3 button classes
|
||||
"""
|
||||
DEFAULT = 'outline-dark'
|
||||
BLUE = 'blue'
|
||||
INDIGO = 'indigo'
|
||||
PURPLE = 'purple'
|
||||
PINK = 'pink'
|
||||
RED = 'red'
|
||||
ORANGE = 'orange'
|
||||
YELLOW = 'yellow'
|
||||
GREEN = 'green'
|
||||
TEAL = 'teal'
|
||||
CYAN = 'cyan'
|
||||
GRAY = 'gray'
|
||||
GREY = 'gray' # Backward compatability for <3.2
|
||||
BLACK = 'black'
|
||||
WHITE = 'white'
|
||||
|
||||
CHOICES = (
|
||||
(DEFAULT, _('Default')),
|
||||
(BLUE, _('Blue')),
|
||||
(INDIGO, _('Indigo')),
|
||||
(PURPLE, _('Purple')),
|
||||
(PINK, _('Pink')),
|
||||
(RED, _('Red')),
|
||||
(ORANGE, _('Orange')),
|
||||
(YELLOW, _('Yellow')),
|
||||
(GREEN, _('Green')),
|
||||
(TEAL, _('Teal')),
|
||||
(CYAN, _('Cyan')),
|
||||
(GRAY, _('Gray')),
|
||||
(BLACK, _('Black')),
|
||||
(WHITE, _('White')),
|
||||
)
|
||||
|
||||
|
||||
#
|
||||
# Import Choices
|
||||
#
|
||||
|
||||
class ImportMethodChoices(ChoiceSet):
|
||||
DIRECT = 'direct'
|
||||
UPLOAD = 'upload'
|
||||
DATA_FILE = 'datafile'
|
||||
|
||||
CHOICES = [
|
||||
(DIRECT, _('Direct')),
|
||||
(UPLOAD, _('Upload')),
|
||||
(DATA_FILE, _('Data file')),
|
||||
]
|
||||
|
||||
|
||||
class ImportFormatChoices(ChoiceSet):
|
||||
AUTO = 'auto'
|
||||
CSV = 'csv'
|
||||
JSON = 'json'
|
||||
YAML = 'yaml'
|
||||
|
||||
CHOICES = [
|
||||
(AUTO, _('Auto-detect')),
|
||||
(CSV, 'CSV'),
|
||||
(JSON, 'JSON'),
|
||||
(YAML, 'YAML'),
|
||||
]
|
||||
|
||||
|
||||
class CSVDelimiterChoices(ChoiceSet):
|
||||
AUTO = 'auto'
|
||||
COMMA = CSV_DELIMITERS['comma']
|
||||
SEMICOLON = CSV_DELIMITERS['semicolon']
|
||||
TAB = CSV_DELIMITERS['tab']
|
||||
|
||||
CHOICES = [
|
||||
(AUTO, _('Auto-detect')),
|
||||
(COMMA, _('Comma')),
|
||||
(SEMICOLON, _('Semicolon')),
|
||||
(TAB, _('Tab')),
|
||||
]
|
|
@ -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.
|
||||
|
||||
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.
|
||||
"""
|
||||
fieldsets = ()
|
||||
|
|
|
@ -1,69 +0,0 @@
|
|||
import graphene
|
||||
from dcim.fields import MACAddressField, WWNField
|
||||
from django.db import models
|
||||
from graphene import Dynamic
|
||||
from graphene_django.converter import convert_django_field, get_django_field_description
|
||||
from graphene_django.fields import DjangoConnectionField
|
||||
from ipam.fields import IPAddressField, IPNetworkField
|
||||
from taggit.managers import TaggableManager
|
||||
|
||||
from .fields import ObjectListField
|
||||
|
||||
|
||||
@convert_django_field.register(TaggableManager)
|
||||
def convert_field_to_tags_list(field, registry=None):
|
||||
"""
|
||||
Register conversion handler for django-taggit's TaggableManager
|
||||
"""
|
||||
return graphene.List(graphene.String)
|
||||
|
||||
|
||||
@convert_django_field.register(IPAddressField)
|
||||
@convert_django_field.register(IPNetworkField)
|
||||
@convert_django_field.register(MACAddressField)
|
||||
@convert_django_field.register(WWNField)
|
||||
def convert_field_to_string(field, registry=None):
|
||||
# TODO: Update to use get_django_field_description under django_graphene v3.0
|
||||
return graphene.String(description=field.help_text, required=not field.null)
|
||||
|
||||
|
||||
@convert_django_field.register(models.ManyToManyField)
|
||||
@convert_django_field.register(models.ManyToManyRel)
|
||||
@convert_django_field.register(models.ManyToOneRel)
|
||||
def convert_field_to_list_or_connection(field, registry=None):
|
||||
"""
|
||||
From graphene_django.converter.py we need to monkey-patch this to return
|
||||
our ObjectListField with filtering support instead of DjangoListField
|
||||
"""
|
||||
model = field.related_model
|
||||
|
||||
def dynamic_type():
|
||||
_type = registry.get_type_for_model(model)
|
||||
if not _type:
|
||||
return
|
||||
|
||||
if isinstance(field, models.ManyToManyField):
|
||||
description = get_django_field_description(field)
|
||||
else:
|
||||
description = get_django_field_description(field.field)
|
||||
|
||||
# If there is a connection, we should transform the field
|
||||
# into a DjangoConnectionField
|
||||
if _type._meta.connection:
|
||||
# Use a DjangoFilterConnectionField if there are
|
||||
# defined filter_fields or a filterset_class in the
|
||||
# DjangoObjectType Meta
|
||||
if _type._meta.filter_fields or _type._meta.filterset_class:
|
||||
from .filter.fields import DjangoFilterConnectionField
|
||||
|
||||
return DjangoFilterConnectionField(_type, required=True, description=description)
|
||||
|
||||
return DjangoConnectionField(_type, required=True, description=description)
|
||||
|
||||
return ObjectListField(
|
||||
_type,
|
||||
required=True, # A Set is always returned, never None.
|
||||
description=description,
|
||||
)
|
||||
|
||||
return Dynamic(dynamic_type)
|
|
@ -1,70 +0,0 @@
|
|||
from functools import partial
|
||||
|
||||
import graphene
|
||||
from graphene_django import DjangoListField
|
||||
from .utils import get_graphene_type
|
||||
|
||||
__all__ = (
|
||||
'ObjectField',
|
||||
'ObjectListField',
|
||||
)
|
||||
|
||||
|
||||
class ObjectField(graphene.Field):
|
||||
"""
|
||||
Retrieve a single object, identified by its numeric ID.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
if 'id' not in kwargs:
|
||||
kwargs['id'] = graphene.Int(required=True)
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def object_resolver(django_object_type, root, info, **args):
|
||||
"""
|
||||
Return an object given its numeric ID.
|
||||
"""
|
||||
manager = django_object_type._meta.model._default_manager
|
||||
queryset = django_object_type.get_queryset(manager, info)
|
||||
|
||||
return queryset.get(**args)
|
||||
|
||||
def get_resolver(self, parent_resolver):
|
||||
return partial(self.object_resolver, self._type)
|
||||
|
||||
|
||||
class ObjectListField(DjangoListField):
|
||||
"""
|
||||
Retrieve a list of objects, optionally filtered by one or more FilterSet filters.
|
||||
"""
|
||||
def __init__(self, _type, *args, **kwargs):
|
||||
filter_kwargs = {}
|
||||
|
||||
# Get FilterSet kwargs
|
||||
filterset_class = getattr(_type._meta, 'filterset_class', None)
|
||||
if filterset_class:
|
||||
for filter_name, filter_field in filterset_class.get_filters().items():
|
||||
field_type = get_graphene_type(type(filter_field))
|
||||
filter_kwargs[filter_name] = graphene.Argument(field_type)
|
||||
|
||||
super().__init__(_type, args=filter_kwargs, *args, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def list_resolver(django_object_type, resolver, default_manager, root, info, **args):
|
||||
queryset = super(ObjectListField, ObjectListField).list_resolver(django_object_type, resolver, default_manager, root, info, **args)
|
||||
|
||||
# if there are no filter params then don't need to filter
|
||||
if not args:
|
||||
return queryset
|
||||
|
||||
filterset_class = django_object_type._meta.filterset_class
|
||||
if filterset_class:
|
||||
filterset = filterset_class(data=args if args else None, queryset=queryset, request=info.context)
|
||||
|
||||
if not filterset.is_valid():
|
||||
return queryset.none()
|
||||
return filterset.qs
|
||||
|
||||
return queryset
|
|
@ -0,0 +1,198 @@
|
|||
from functools import partial, partialmethod, wraps
|
||||
from typing import List
|
||||
|
||||
import django_filters
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
from strawberry import auto
|
||||
from ipam.fields import ASNField
|
||||
from netbox.graphql.scalars import BigInt
|
||||
from utilities.fields import ColorField, CounterCacheField
|
||||
from utilities.filters import *
|
||||
|
||||
|
||||
def map_strawberry_type(field):
|
||||
should_create_function = False
|
||||
attr_type = None
|
||||
|
||||
# NetBox Filter types - put base classes after derived classes
|
||||
if isinstance(field, ContentTypeFilter):
|
||||
should_create_function = True
|
||||
attr_type = str | None
|
||||
elif isinstance(field, MultiValueArrayFilter):
|
||||
pass
|
||||
elif isinstance(field, MultiValueCharFilter):
|
||||
should_create_function = True
|
||||
attr_type = List[str] | None
|
||||
elif isinstance(field, MultiValueDateFilter):
|
||||
attr_type = auto
|
||||
elif isinstance(field, MultiValueDateTimeFilter):
|
||||
attr_type = auto
|
||||
elif isinstance(field, MultiValueDecimalFilter):
|
||||
pass
|
||||
elif isinstance(field, MultiValueMACAddressFilter):
|
||||
should_create_function = True
|
||||
attr_type = List[str] | None
|
||||
elif isinstance(field, MultiValueNumberFilter):
|
||||
should_create_function = True
|
||||
attr_type = List[str] | None
|
||||
elif isinstance(field, MultiValueTimeFilter):
|
||||
pass
|
||||
elif isinstance(field, MultiValueWWNFilter):
|
||||
should_create_function = True
|
||||
attr_type = List[str] | None
|
||||
elif isinstance(field, NullableCharFieldFilter):
|
||||
pass
|
||||
elif isinstance(field, NumericArrayFilter):
|
||||
should_create_function = True
|
||||
attr_type = int
|
||||
elif isinstance(field, TreeNodeMultipleChoiceFilter):
|
||||
should_create_function = True
|
||||
attr_type = List[str] | None
|
||||
|
||||
# From django_filters - ordering of these matters as base classes must
|
||||
# come after derived classes so the base class doesn't get matched first
|
||||
# a pass for the check (no attr_type) means we don't currently handle
|
||||
# or use that type
|
||||
elif issubclass(type(field), django_filters.OrderingFilter):
|
||||
pass
|
||||
elif issubclass(type(field), django_filters.BaseRangeFilter):
|
||||
pass
|
||||
elif issubclass(type(field), django_filters.BaseInFilter):
|
||||
pass
|
||||
elif issubclass(type(field), django_filters.LookupChoiceFilter):
|
||||
pass
|
||||
elif issubclass(type(field), django_filters.AllValuesMultipleFilter):
|
||||
pass
|
||||
elif issubclass(type(field), django_filters.AllValuesFilter):
|
||||
pass
|
||||
elif issubclass(type(field), django_filters.TimeRangeFilter):
|
||||
pass
|
||||
elif issubclass(type(field), django_filters.IsoDateTimeFromToRangeFilter):
|
||||
should_create_function = True
|
||||
attr_type = str | None
|
||||
elif issubclass(type(field), django_filters.DateTimeFromToRangeFilter):
|
||||
should_create_function = True
|
||||
attr_type = str | None
|
||||
elif issubclass(type(field), django_filters.DateFromToRangeFilter):
|
||||
should_create_function = True
|
||||
attr_type = str | None
|
||||
elif issubclass(type(field), django_filters.DateRangeFilter):
|
||||
should_create_function = True
|
||||
attr_type = str | None
|
||||
elif issubclass(type(field), django_filters.RangeFilter):
|
||||
pass
|
||||
elif issubclass(type(field), django_filters.NumericRangeFilter):
|
||||
pass
|
||||
elif issubclass(type(field), django_filters.NumberFilter):
|
||||
should_create_function = True
|
||||
attr_type = int
|
||||
elif issubclass(type(field), django_filters.ModelMultipleChoiceFilter):
|
||||
should_create_function = True
|
||||
attr_type = List[str] | None
|
||||
elif issubclass(type(field), django_filters.ModelChoiceFilter):
|
||||
should_create_function = True
|
||||
attr_type = str | None
|
||||
elif issubclass(type(field), django_filters.DurationFilter):
|
||||
pass
|
||||
elif issubclass(type(field), django_filters.IsoDateTimeFilter):
|
||||
pass
|
||||
elif issubclass(type(field), django_filters.DateTimeFilter):
|
||||
attr_type = auto
|
||||
elif issubclass(type(field), django_filters.TimeFilter):
|
||||
attr_type = auto
|
||||
elif issubclass(type(field), django_filters.DateFilter):
|
||||
attr_type = auto
|
||||
elif issubclass(type(field), django_filters.TypedMultipleChoiceFilter):
|
||||
pass
|
||||
elif issubclass(type(field), django_filters.MultipleChoiceFilter):
|
||||
should_create_function = True
|
||||
attr_type = List[str] | None
|
||||
elif issubclass(type(field), django_filters.TypedChoiceFilter):
|
||||
pass
|
||||
elif issubclass(type(field), django_filters.ChoiceFilter):
|
||||
pass
|
||||
elif issubclass(type(field), django_filters.BooleanFilter):
|
||||
should_create_function = True
|
||||
attr_type = bool | None
|
||||
elif issubclass(type(field), django_filters.UUIDFilter):
|
||||
should_create_function = True
|
||||
attr_type = str | None
|
||||
elif issubclass(type(field), django_filters.CharFilter):
|
||||
# looks like only used by 'q'
|
||||
should_create_function = True
|
||||
attr_type = str | None
|
||||
|
||||
return should_create_function, attr_type
|
||||
|
||||
|
||||
def autotype_decorator(filterset):
|
||||
"""
|
||||
Decorator used to auto creates a dataclass used by Strawberry based on a filterset.
|
||||
Must go after the Strawberry decorator as follows:
|
||||
|
||||
@strawberry_django.filter(models.Example, lookups=True)
|
||||
@autotype_decorator(filtersets.ExampleFilterSet)
|
||||
class ExampleFilter(BaseFilterMixin):
|
||||
pass
|
||||
|
||||
The Filter itself must be derived from BaseFilterMixin. For items listed in meta.fields
|
||||
of the filterset, usually just a type specifier is generated, so for
|
||||
`fields = [created, ]` the dataclass would be:
|
||||
|
||||
class ExampleFilter(BaseFilterMixin):
|
||||
created: auto
|
||||
|
||||
For other filter fields a function needs to be created for Strawberry with the
|
||||
naming convention `filter_{fieldname}` which is auto detected and called by
|
||||
Strawberry, this function uses the filterset to handle the query.
|
||||
"""
|
||||
def create_attribute_and_function(cls, fieldname, attr_type, should_create_function):
|
||||
if fieldname not in cls.__annotations__ and attr_type:
|
||||
cls.__annotations__[fieldname] = attr_type
|
||||
|
||||
filter_name = f"filter_{fieldname}"
|
||||
if should_create_function and not hasattr(cls, filter_name):
|
||||
filter_by_filterset = getattr(cls, 'filter_by_filterset')
|
||||
setattr(cls, filter_name, partialmethod(filter_by_filterset, key=fieldname))
|
||||
|
||||
def wrapper(cls):
|
||||
cls.filterset = filterset
|
||||
fields = filterset.get_fields()
|
||||
model = filterset._meta.model
|
||||
for fieldname in fields.keys():
|
||||
should_create_function = False
|
||||
attr_type = auto
|
||||
if fieldname not in cls.__annotations__:
|
||||
field = model._meta.get_field(fieldname)
|
||||
if isinstance(field, CounterCacheField):
|
||||
should_create_function = True
|
||||
attr_type = BigInt | None
|
||||
elif isinstance(field, ASNField):
|
||||
should_create_function = True
|
||||
attr_type = List[str] | None
|
||||
elif isinstance(field, ColorField):
|
||||
should_create_function = True
|
||||
attr_type = List[str] | None
|
||||
|
||||
create_attribute_and_function(cls, fieldname, attr_type, should_create_function)
|
||||
|
||||
declared_filters = filterset.declared_filters
|
||||
for fieldname, field in declared_filters.items():
|
||||
|
||||
should_create_function, attr_type = map_strawberry_type(field)
|
||||
if attr_type is None:
|
||||
raise NotImplementedError(f"GraphQL Filter field unknown: {fieldname}: {field}")
|
||||
|
||||
create_attribute_and_function(cls, fieldname, attr_type, should_create_function)
|
||||
|
||||
return cls
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
@strawberry.input
|
||||
class BaseFilterMixin:
|
||||
|
||||
def filter_by_filterset(self, queryset, key):
|
||||
return self.filterset(data={key: getattr(self, key)}, queryset=queryset).qs
|
|
@ -1,23 +1,10 @@
|
|||
from graphene import Scalar
|
||||
from graphql.language import ast
|
||||
from graphene.types.scalars import MAX_INT, MIN_INT
|
||||
from typing import Union
|
||||
|
||||
import strawberry
|
||||
|
||||
class BigInt(Scalar):
|
||||
"""
|
||||
Handle any BigInts
|
||||
"""
|
||||
@staticmethod
|
||||
def to_float(value):
|
||||
num = int(value)
|
||||
if num > MAX_INT or num < MIN_INT:
|
||||
return float(num)
|
||||
return num
|
||||
|
||||
serialize = to_float
|
||||
parse_value = to_float
|
||||
|
||||
@staticmethod
|
||||
def parse_literal(node):
|
||||
if isinstance(node, ast.IntValue):
|
||||
return BigInt.to_float(node.value)
|
||||
BigInt = strawberry.scalar(
|
||||
Union[int, str], # type: ignore
|
||||
serialize=lambda v: int(v),
|
||||
parse_value=lambda v: str(v),
|
||||
description="BigInt field",
|
||||
)
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import graphene
|
||||
import strawberry
|
||||
from strawberry_django.optimizer import DjangoOptimizerExtension
|
||||
from strawberry.schema.config import StrawberryConfig
|
||||
|
||||
from circuits.graphql.schema import CircuitsQuery
|
||||
from core.graphql.schema import CoreQuery
|
||||
|
@ -13,6 +15,7 @@ from vpn.graphql.schema import VPNQuery
|
|||
from wireless.graphql.schema import WirelessQuery
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class Query(
|
||||
UsersQuery,
|
||||
CircuitsQuery,
|
||||
|
@ -25,9 +28,14 @@ class Query(
|
|||
VPNQuery,
|
||||
WirelessQuery,
|
||||
*registry['plugins']['graphql_schemas'], # Append plugin schemas
|
||||
graphene.ObjectType
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
schema = graphene.Schema(query=Query, auto_camelcase=False)
|
||||
schema = strawberry.Schema(
|
||||
query=Query,
|
||||
config=StrawberryConfig(auto_camel_case=False),
|
||||
extensions=[
|
||||
DjangoOptimizerExtension,
|
||||
]
|
||||
)
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
import graphene
|
||||
from typing import Annotated, List
|
||||
|
||||
import strawberry
|
||||
from strawberry import auto
|
||||
import strawberry_django
|
||||
|
||||
from core.models import ObjectType as ObjectType_
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
@ -8,13 +12,10 @@ from extras.graphql.mixins import (
|
|||
JournalEntriesMixin,
|
||||
TagsMixin,
|
||||
)
|
||||
from graphene_django import DjangoObjectType
|
||||
|
||||
__all__ = (
|
||||
'BaseObjectType',
|
||||
'ContentTypeType',
|
||||
'ObjectType',
|
||||
'ObjectTypeType',
|
||||
'OrganizationalObjectType',
|
||||
'NetBoxObjectType',
|
||||
)
|
||||
|
@ -24,26 +25,27 @@ __all__ = (
|
|||
# Base types
|
||||
#
|
||||
|
||||
class BaseObjectType(DjangoObjectType):
|
||||
@strawberry.type
|
||||
class BaseObjectType:
|
||||
"""
|
||||
Base GraphQL object type for all NetBox objects. Restricts the model queryset to enforce object permissions.
|
||||
"""
|
||||
display = graphene.String()
|
||||
class_type = graphene.String()
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
@classmethod
|
||||
def get_queryset(cls, queryset, info):
|
||||
def get_queryset(cls, queryset, info, **kwargs):
|
||||
# Enforce object permissions on the queryset
|
||||
return queryset.restrict(info.context.user, 'view')
|
||||
if hasattr(queryset, 'restrict'):
|
||||
return queryset.restrict(info.context.request.user, 'view')
|
||||
else:
|
||||
return queryset
|
||||
|
||||
def resolve_display(parent, info, **kwargs):
|
||||
return str(parent)
|
||||
@strawberry_django.field
|
||||
def display(self) -> str:
|
||||
return str(self)
|
||||
|
||||
def resolve_class_type(parent, info, **kwargs):
|
||||
return parent.__class__.__name__
|
||||
@strawberry_django.field
|
||||
def class_type(self) -> str:
|
||||
return self.__class__.__name__
|
||||
|
||||
|
||||
class ObjectType(
|
||||
|
@ -53,8 +55,7 @@ class ObjectType(
|
|||
"""
|
||||
Base GraphQL object type for unclassified models which support change logging
|
||||
"""
|
||||
class Meta:
|
||||
abstract = True
|
||||
pass
|
||||
|
||||
|
||||
class OrganizationalObjectType(
|
||||
|
@ -66,8 +67,7 @@ class OrganizationalObjectType(
|
|||
"""
|
||||
Base type for organizational models
|
||||
"""
|
||||
class Meta:
|
||||
abstract = True
|
||||
pass
|
||||
|
||||
|
||||
class NetBoxObjectType(
|
||||
|
@ -80,23 +80,24 @@ class NetBoxObjectType(
|
|||
"""
|
||||
GraphQL type for most NetBox models. Includes support for custom fields, change logging, journaling, and tags.
|
||||
"""
|
||||
class Meta:
|
||||
abstract = True
|
||||
pass
|
||||
|
||||
|
||||
#
|
||||
# Miscellaneous types
|
||||
#
|
||||
|
||||
class ContentTypeType(DjangoObjectType):
|
||||
|
||||
class Meta:
|
||||
model = ContentType
|
||||
fields = ('id', 'app_label', 'model')
|
||||
@strawberry_django.type(
|
||||
ContentType,
|
||||
fields=['id', 'app_label', 'model'],
|
||||
)
|
||||
class ContentTypeType:
|
||||
pass
|
||||
|
||||
|
||||
class ObjectTypeType(DjangoObjectType):
|
||||
|
||||
class Meta:
|
||||
model = ObjectType_
|
||||
fields = ('id', 'app_label', 'model')
|
||||
@strawberry_django.type(
|
||||
ObjectType_,
|
||||
fields=['id', 'app_label', 'model'],
|
||||
)
|
||||
class ObjectTypeType:
|
||||
pass
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
import graphene
|
||||
from django_filters import filters
|
||||
|
||||
|
||||
def get_graphene_type(filter_cls):
|
||||
"""
|
||||
Return the appropriate Graphene scalar type for a django_filters Filter
|
||||
"""
|
||||
if issubclass(filter_cls, filters.BooleanFilter):
|
||||
field_type = graphene.Boolean
|
||||
elif issubclass(filter_cls, filters.NumberFilter):
|
||||
# TODO: Floats? BigInts?
|
||||
field_type = graphene.Int
|
||||
elif issubclass(filter_cls, filters.DateFilter):
|
||||
field_type = graphene.Date
|
||||
elif issubclass(filter_cls, filters.DateTimeFilter):
|
||||
field_type = graphene.DateTime
|
||||
else:
|
||||
field_type = graphene.String
|
||||
|
||||
# Multi-value filters should be handled as lists
|
||||
if issubclass(filter_cls, filters.MultipleChoiceFilter):
|
||||
return graphene.List(field_type)
|
||||
|
||||
return field_type
|
|
@ -1,20 +1,26 @@
|
|||
import json
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.views import redirect_to_login
|
||||
from django.http import HttpResponseNotFound, HttpResponseForbidden
|
||||
from django.http import HttpResponse
|
||||
from django.template import loader
|
||||
from django.urls import reverse
|
||||
from graphene_django.views import GraphQLView as GraphQLView_
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from rest_framework.exceptions import AuthenticationFailed
|
||||
from strawberry.django.views import GraphQLView
|
||||
|
||||
from netbox.api.authentication import TokenAuthentication
|
||||
from netbox.config import get_config
|
||||
|
||||
|
||||
class GraphQLView(GraphQLView_):
|
||||
class NetBoxGraphQLView(GraphQLView):
|
||||
"""
|
||||
Extends graphene_django's GraphQLView to support DRF's token-based authentication.
|
||||
Extends strawberry's GraphQLView to support DRF's token-based authentication.
|
||||
"""
|
||||
graphiql_template = 'graphiql.html'
|
||||
|
||||
@csrf_exempt
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
config = get_config()
|
||||
|
||||
|
@ -34,11 +40,15 @@ class GraphQLView(GraphQLView_):
|
|||
|
||||
# Enforce LOGIN_REQUIRED
|
||||
if settings.LOGIN_REQUIRED and not request.user.is_authenticated:
|
||||
|
||||
# If this is a human user, send a redirect to the login page
|
||||
if self.request_wants_html(request):
|
||||
if request.accepts("text/html"):
|
||||
return redirect_to_login(reverse('graphql'))
|
||||
|
||||
return HttpResponseForbidden("No credentials provided.")
|
||||
else:
|
||||
return HttpResponseForbidden("No credentials provided.")
|
||||
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def render_graphql_ide(self, request):
|
||||
template = loader.get_template("graphiql.html")
|
||||
context = {"SUBSCRIPTION_ENABLED": json.dumps(self.subscriptions_enabled)}
|
||||
|
||||
return HttpResponse(template.render(context, request))
|
||||
|
|
|
@ -13,7 +13,8 @@ from django.http import Http404, HttpResponseRedirect
|
|||
from extras.context_managers import event_tracking
|
||||
from netbox.config import clear_config, get_config
|
||||
from netbox.views import handler_500
|
||||
from utilities.api import is_api_request, rest_api_server_error
|
||||
from utilities.api import is_api_request
|
||||
from utilities.error_handlers import handle_rest_api_exception
|
||||
|
||||
__all__ = (
|
||||
'CoreMiddleware',
|
||||
|
@ -71,7 +72,7 @@ class CoreMiddleware:
|
|||
|
||||
# Cleanly handle exceptions that occur from REST API requests
|
||||
if is_api_request(request):
|
||||
return rest_api_server_error(request)
|
||||
return handle_rest_api_exception(request)
|
||||
|
||||
# Ignore Http404s (defer to Django's built-in 404 handling)
|
||||
if isinstance(exception, Http404):
|
||||
|
@ -211,7 +212,7 @@ class MaintenanceModeMiddleware:
|
|||
'operations. Please try again later.'
|
||||
|
||||
if is_api_request(request):
|
||||
return rest_api_server_error(request, error=error_message)
|
||||
return handle_rest_api_exception(request, error=error_message)
|
||||
|
||||
messages.error(request, error_message)
|
||||
return HttpResponseRedirect(request.path_info)
|
||||
|
|
|
@ -17,7 +17,7 @@ from netbox.config import get_config
|
|||
from netbox.registry import registry
|
||||
from netbox.signals import post_clean
|
||||
from utilities.json import CustomFieldJSONEncoder
|
||||
from utilities.utils import serialize_object
|
||||
from utilities.serialization import serialize_object
|
||||
from utilities.views import register_model_view
|
||||
|
||||
__all__ = (
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
from dataclasses import dataclass
|
||||
from typing import Sequence, Optional
|
||||
|
||||
from utilities.choices import ButtonColorChoices
|
||||
|
||||
|
||||
__all__ = (
|
||||
'get_model_item',
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue