mirror of
https://github.com/netbox-community/netbox.git
synced 2025-08-25 00:36:11 -06:00
Merge branch '7854-vdc' of https://github.com/netbox-community/netbox into 7854-vdc
This commit is contained in:
commit
c9afb2895e
@ -1,5 +1,13 @@
|
||||
# Security & Authentication Parameters
|
||||
|
||||
## ALLOW_TOKEN_RETRIEVAL
|
||||
|
||||
Default: True
|
||||
|
||||
If disabled, the values of API tokens will not be displayed after each token's initial creation. A user **must** record the value of a token immediately upon its creation, or it will be lost. Note that this affects _all_ users, regardless of assigned permissions.
|
||||
|
||||
---
|
||||
|
||||
## ALLOWED_URL_SCHEMES
|
||||
|
||||
!!! tip "Dynamic Configuration Parameter"
|
||||
|
@ -1,5 +1,5 @@
|
||||
# Journaling
|
||||
|
||||
All primary objects in NetBox support journaling. A journal is a collection of human-generated notes and comments about an object maintained for historical context. It supplements NetBox's change log to provide additional information about why changes have been made or to convey events which occur outside NetBox. Unlike the change log, in which records typically expire after a configurable period of time, journal entries persist for the life of their associated object.
|
||||
All primary and organizational models in NetBox support journaling. A journal is a collection of human-generated notes and comments about an object maintained for historical context. It supplements NetBox's change log to provide additional information about why changes have been made or to convey events which occur outside NetBox. Unlike the change log, in which records typically expire after a configurable period of time, journal entries persist for the life of their associated object.
|
||||
|
||||
Each journal entry has a selectable kind (info, success, warning, or danger) and a user-populated `comments` field. Each entry automatically records the date, time, and associated user upon being created.
|
||||
|
@ -579,6 +579,9 @@ By default, a token can be used to perform all actions via the API that a user w
|
||||
|
||||
Additionally, a token can be set to expire at a specific time. This can be useful if an external client needs to be granted temporary access to NetBox.
|
||||
|
||||
!!! warning "Restricting Token Retrieval"
|
||||
The ability to retrieve the key value of a previously-created API token can be restricted by disabling the [`ALLOW_TOKEN_RETRIEVAL`](../configuration/security.md#allow_token_retrieval) configuration parameter.
|
||||
|
||||
#### Client IP Restriction
|
||||
|
||||
!!! note
|
||||
|
@ -33,7 +33,7 @@ Each site can have multiple [AS numbers](../ipam/asn.md) assigned to it.
|
||||
|
||||
### Time Zone
|
||||
|
||||
The site's local time zone. (Time zones are provided by the [pytz](https://pypi.org/project/pytz/) package.)
|
||||
The site's local time zone. (Time zones are provided by the [zoneinfo](https://docs.python.org/3/library/zoneinfo.html) library.)
|
||||
|
||||
### Physical Address
|
||||
|
||||
|
@ -117,11 +117,11 @@ NetBox looks for the `config` variable within a plugin's `__init__.py` to load i
|
||||
All required settings must be configured by the user. If a configuration parameter is listed in both `required_settings` and `default_settings`, the default setting will be ignored.
|
||||
|
||||
!!! tip "Accessing Config Parameters"
|
||||
Plugin configuration parameters can be accessed in `settings.PLUGINS_CONFIG`, mapped by plugin name. For example:
|
||||
Plugin configuration parameters can be accessed using the `get_plugin_config()` function. For example:
|
||||
|
||||
```python
|
||||
from django.conf import settings
|
||||
settings.PLUGINS_CONFIG['myplugin']['verbose_name']
|
||||
from extras.plugins import get_plugin_config
|
||||
get_plugin_config('my_plugin', 'verbose_name')
|
||||
```
|
||||
|
||||
#### Important Notes About `django_apps`
|
||||
|
@ -28,6 +28,8 @@ A new `PluginMenu` class has been introduced, which enables a plugin to inject a
|
||||
|
||||
* [#8245](https://github.com/netbox-community/netbox/issues/8245) - Enable GraphQL filtering of related objects
|
||||
* [#8274](https://github.com/netbox-community/netbox/issues/8274) - Enable associating a custom link with multiple object types
|
||||
* [#8485](https://github.com/netbox-community/netbox/issues/8485) - Enable journaling for all organizational models
|
||||
* [#8853](https://github.com/netbox-community/netbox/issues/8853) - Introduce the `ALLOW_TOKEN_RETRIEVAL` config parameter to restrict the display of API tokens
|
||||
* [#9249](https://github.com/netbox-community/netbox/issues/9249) - Device and virtual machine names are no longer case-sensitive
|
||||
* [#9478](https://github.com/netbox-community/netbox/issues/9478) - Add `link_peers` field to GraphQL types for cabled objects
|
||||
* [#9654](https://github.com/netbox-community/netbox/issues/9654) - Add `weight` field to racks, device types, and module types
|
||||
@ -37,6 +39,7 @@ A new `PluginMenu` class has been introduced, which enables a plugin to inject a
|
||||
* [#10348](https://github.com/netbox-community/netbox/issues/10348) - Add decimal custom field type
|
||||
* [#10556](https://github.com/netbox-community/netbox/issues/10556) - Include a `display` field in all GraphQL object types
|
||||
* [#10595](https://github.com/netbox-community/netbox/issues/10595) - Add GraphQL relationships for additional generic foreign key fields
|
||||
* [#10698](https://github.com/netbox-community/netbox/issues/10698) - Omit app label from content type in table columns
|
||||
* [#10761](https://github.com/netbox-community/netbox/issues/10761) - Enable associating an export template with multiple object types
|
||||
* [#10781](https://github.com/netbox-community/netbox/issues/10781) - Add support for Python v3.11
|
||||
|
||||
@ -48,6 +51,7 @@ A new `PluginMenu` class has been introduced, which enables a plugin to inject a
|
||||
* [#9880](https://github.com/netbox-community/netbox/issues/9880) - Introduce `django_apps` plugin configuration parameter
|
||||
* [#9887](https://github.com/netbox-community/netbox/issues/9887) - Inspect `docs_url` property to determine link to model documentation
|
||||
* [#10314](https://github.com/netbox-community/netbox/issues/10314) - Move `clone()` method from NetBoxModel to CloningMixin
|
||||
* [#10543](https://github.com/netbox-community/netbox/issues/10543) - Introduce `get_plugin_config()` utility function
|
||||
* [#10739](https://github.com/netbox-community/netbox/issues/10739) - Introduce `get_queryset()` method on generic views
|
||||
|
||||
### Other Changes
|
||||
@ -55,24 +59,79 @@ A new `PluginMenu` class has been introduced, which enables a plugin to inject a
|
||||
* [#9045](https://github.com/netbox-community/netbox/issues/9045) - Remove legacy ASN field from provider model
|
||||
* [#9046](https://github.com/netbox-community/netbox/issues/9046) - Remove legacy contact fields from provider model
|
||||
* [#10358](https://github.com/netbox-community/netbox/issues/10358) - Raise minimum required PostgreSQL version from 10 to 11
|
||||
* [#10697](https://github.com/netbox-community/netbox/issues/10697) - Move application registry into core app
|
||||
* [#10699](https://github.com/netbox-community/netbox/issues/10699) - Remove custom `import_object()` function
|
||||
* [#10816](https://github.com/netbox-community/netbox/issues/10816) - Pass the current request when instantiating a FilterSet within UI views
|
||||
* [#10820](https://github.com/netbox-community/netbox/issues/10820) - Switch timezone library from pytz to zoneinfo
|
||||
* [#10821](https://github.com/netbox-community/netbox/issues/10821) - Enable data localization
|
||||
|
||||
### REST API Changes
|
||||
|
||||
* circuits.provider
|
||||
* Removed the `asn`, `noc_contact`, `admin_contact`, and `portal_url` fields
|
||||
* Added a `description` field
|
||||
* dcim.Cable
|
||||
* Added `description` and `comments` fields
|
||||
* dcim.Device
|
||||
* Added a `description` field
|
||||
* dcim.DeviceType
|
||||
* Added a `description` field
|
||||
* Added optional `weight` and `weight_unit` fields
|
||||
* dcim.Module
|
||||
* Added a `description` field
|
||||
* dcim.ModuleType
|
||||
* Added a `description` field
|
||||
* Added optional `weight` and `weight_unit` fields
|
||||
* dcim.PowerFeed
|
||||
* Added a `description` field
|
||||
* dcim.PowerPanel
|
||||
* Added `description` and `comments` fields
|
||||
* dcim.Rack
|
||||
* Added a `description` field
|
||||
* Added optional `weight` and `weight_unit` fields
|
||||
* dcim.RackReservation
|
||||
* Added a `comments` field
|
||||
* dcim.VirtualChassis
|
||||
* Added `description` and `comments` fields
|
||||
* extras.CustomLink
|
||||
* Renamed `content_type` field to `content_types`
|
||||
* extras.ExportTemplate
|
||||
* Renamed `content_type` field to `content_types`
|
||||
* ipam.Aggregate
|
||||
* Added a `comments` field
|
||||
* ipam.ASN
|
||||
* Added a `comments` field
|
||||
* ipam.FHRPGroup
|
||||
* Added a `comments` field
|
||||
* Added optional `name` field
|
||||
* ipam.IPAddress
|
||||
* Added a `comments` field
|
||||
* ipam.IPRange
|
||||
* Added a `comments` field
|
||||
* ipam.L2VPN
|
||||
* Added a `comments` field
|
||||
* ipam.Prefix
|
||||
* Added a `comments` field
|
||||
* ipam.RouteTarget
|
||||
* Added a `comments` field
|
||||
* ipam.Service
|
||||
* Added a `comments` field
|
||||
* ipam.ServiceTemplate
|
||||
* Added a `comments` field
|
||||
* ipam.VLAN
|
||||
* Added a `comments` field
|
||||
* ipam.VRF
|
||||
* Added a `comments` field
|
||||
* tenancy.Contact
|
||||
* Added a `description` field
|
||||
* virtualization.Cluster
|
||||
* Added a `description` field
|
||||
* virtualization.VirtualMachine
|
||||
* Added a `description` field
|
||||
* wireless.WirelessLAN
|
||||
* Added a `comments` field
|
||||
* wireless.WirelessLink
|
||||
* Added a `comments` field
|
||||
|
||||
### GraphQL API Changes
|
||||
|
||||
|
@ -31,8 +31,8 @@ class ProviderSerializer(NetBoxModelSerializer):
|
||||
class Meta:
|
||||
model = Provider
|
||||
fields = [
|
||||
'id', 'url', 'display', 'name', 'slug', 'account',
|
||||
'comments', 'asns', 'tags', 'custom_fields', 'created', 'last_updated', 'circuit_count',
|
||||
'id', 'url', 'display', 'name', 'slug', 'account', 'description', 'comments', 'asns', 'tags',
|
||||
'custom_fields', 'created', 'last_updated', 'circuit_count',
|
||||
]
|
||||
|
||||
|
||||
|
@ -30,6 +30,10 @@ class ProviderBulkEditForm(NetBoxModelBulkEditForm):
|
||||
required=False,
|
||||
label='Account number'
|
||||
)
|
||||
description = forms.CharField(
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
comments = CommentField(
|
||||
widget=SmallTextarea,
|
||||
label='Comments'
|
||||
@ -40,7 +44,7 @@ class ProviderBulkEditForm(NetBoxModelBulkEditForm):
|
||||
(None, ('asns', 'account', )),
|
||||
)
|
||||
nullable_fields = (
|
||||
'asns', 'account', 'comments',
|
||||
'asns', 'account', 'description', 'comments',
|
||||
)
|
||||
|
||||
|
||||
|
@ -18,7 +18,7 @@ class ProviderCSVForm(NetBoxModelCSVForm):
|
||||
class Meta:
|
||||
model = Provider
|
||||
fields = (
|
||||
'name', 'slug', 'account', 'comments',
|
||||
'name', 'slug', 'account', 'description', 'comments',
|
||||
)
|
||||
|
||||
|
||||
|
@ -20,7 +20,7 @@ __all__ = (
|
||||
class ProviderFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
|
||||
model = Provider
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
(None, ('q', 'filter', 'tag')),
|
||||
('Location', ('region_id', 'site_group_id', 'site_id')),
|
||||
('ASN', ('asn',)),
|
||||
('Contacts', ('contact', 'contact_role', 'contact_group')),
|
||||
@ -59,7 +59,7 @@ class ProviderFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
|
||||
class ProviderNetworkFilterForm(NetBoxModelFilterSetForm):
|
||||
model = ProviderNetwork
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
(None, ('q', 'filter', 'tag')),
|
||||
('Attributes', ('provider_id', 'service_id')),
|
||||
)
|
||||
provider_id = DynamicModelMultipleChoiceField(
|
||||
@ -82,7 +82,7 @@ class CircuitTypeFilterForm(NetBoxModelFilterSetForm):
|
||||
class CircuitFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilterSetForm):
|
||||
model = Circuit
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
(None, ('q', 'filter', 'tag')),
|
||||
('Provider', ('provider_id', 'provider_network_id')),
|
||||
('Attributes', ('type_id', 'status', 'install_date', 'termination_date', 'commit_rate')),
|
||||
('Location', ('region_id', 'site_group_id', 'site_id')),
|
||||
|
@ -1,4 +1,3 @@
|
||||
from django import forms
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from circuits.models import *
|
||||
@ -7,8 +6,8 @@ from ipam.models import ASN
|
||||
from netbox.forms import NetBoxModelForm
|
||||
from tenancy.forms import TenancyForm
|
||||
from utilities.forms import (
|
||||
BootstrapMixin, CommentField, DatePicker, DynamicModelChoiceField, DynamicModelMultipleChoiceField,
|
||||
SelectSpeedWidget, SmallTextarea, SlugField, StaticSelect,
|
||||
CommentField, DatePicker, DynamicModelChoiceField, DynamicModelMultipleChoiceField, SelectSpeedWidget, SlugField,
|
||||
StaticSelect,
|
||||
)
|
||||
|
||||
__all__ = (
|
||||
@ -30,14 +29,14 @@ class ProviderForm(NetBoxModelForm):
|
||||
comments = CommentField()
|
||||
|
||||
fieldsets = (
|
||||
('Provider', ('name', 'slug', 'asns', 'tags')),
|
||||
('Provider', ('name', 'slug', 'asns', 'description', 'tags')),
|
||||
('Support Info', ('account',)),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Provider
|
||||
fields = [
|
||||
'name', 'slug', 'account', 'asns', 'comments', 'tags',
|
||||
'name', 'slug', 'account', 'asns', 'description', 'comments', 'tags',
|
||||
]
|
||||
help_texts = {
|
||||
'name': "Full name of the provider",
|
||||
|
@ -65,7 +65,7 @@ class Migration(migrations.Migration):
|
||||
('description', models.CharField(blank=True, max_length=200)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['name'],
|
||||
'ordering': ('name',),
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
|
@ -0,0 +1,18 @@
|
||||
# Generated by Django 4.1.2 on 2022-11-03 18:24
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('circuits', '0040_provider_remove_deprecated_fields'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='provider',
|
||||
name='description',
|
||||
field=models.CharField(blank=True, max_length=200),
|
||||
),
|
||||
]
|
@ -7,7 +7,7 @@ from django.urls import reverse
|
||||
from circuits.choices import *
|
||||
from dcim.models import CabledObjectModel
|
||||
from netbox.models import (
|
||||
ChangeLoggedModel, CustomFieldsMixin, CustomLinksMixin, OrganizationalModel, NetBoxModel, TagsMixin,
|
||||
ChangeLoggedModel, CustomFieldsMixin, CustomLinksMixin, OrganizationalModel, PrimaryModel, TagsMixin,
|
||||
)
|
||||
from netbox.models.features import WebhooksMixin
|
||||
|
||||
@ -23,30 +23,11 @@ class CircuitType(OrganizationalModel):
|
||||
Circuits can be organized by their functional role. For example, a user might wish to define CircuitTypes named
|
||||
"Long Haul," "Metro," or "Out-of-Band".
|
||||
"""
|
||||
name = models.CharField(
|
||||
max_length=100,
|
||||
unique=True
|
||||
)
|
||||
slug = models.SlugField(
|
||||
max_length=100,
|
||||
unique=True
|
||||
)
|
||||
description = models.CharField(
|
||||
max_length=200,
|
||||
blank=True,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('circuits:circuittype', args=[self.pk])
|
||||
|
||||
|
||||
class Circuit(NetBoxModel):
|
||||
class Circuit(PrimaryModel):
|
||||
"""
|
||||
A communications circuit connects two points. Each Circuit belongs to a Provider; Providers may have multiple
|
||||
circuits. Each circuit is also assigned a CircuitType and a Site. Circuit port speed and commit rate are measured
|
||||
@ -92,13 +73,6 @@ class Circuit(NetBoxModel):
|
||||
blank=True,
|
||||
null=True,
|
||||
verbose_name='Commit rate (Kbps)')
|
||||
description = models.CharField(
|
||||
max_length=200,
|
||||
blank=True
|
||||
)
|
||||
comments = models.TextField(
|
||||
blank=True
|
||||
)
|
||||
|
||||
# Generic relations
|
||||
contacts = GenericRelation(
|
||||
|
@ -2,8 +2,7 @@ from django.contrib.contenttypes.fields import GenericRelation
|
||||
from django.db import models
|
||||
from django.urls import reverse
|
||||
|
||||
from dcim.fields import ASNField
|
||||
from netbox.models import NetBoxModel
|
||||
from netbox.models import PrimaryModel
|
||||
|
||||
__all__ = (
|
||||
'ProviderNetwork',
|
||||
@ -11,7 +10,7 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class Provider(NetBoxModel):
|
||||
class Provider(PrimaryModel):
|
||||
"""
|
||||
Each Circuit belongs to a Provider. This is usually a telecommunications company or similar organization. This model
|
||||
stores information pertinent to the user's relationship with the Provider.
|
||||
@ -34,9 +33,6 @@ class Provider(NetBoxModel):
|
||||
blank=True,
|
||||
verbose_name='Account number'
|
||||
)
|
||||
comments = models.TextField(
|
||||
blank=True
|
||||
)
|
||||
|
||||
# Generic relations
|
||||
contacts = GenericRelation(
|
||||
@ -57,7 +53,7 @@ class Provider(NetBoxModel):
|
||||
return reverse('circuits:provider', args=[self.pk])
|
||||
|
||||
|
||||
class ProviderNetwork(NetBoxModel):
|
||||
class ProviderNetwork(PrimaryModel):
|
||||
"""
|
||||
This represents a provider network which exists outside of NetBox, the details of which are unknown or
|
||||
unimportant to the user.
|
||||
@ -75,13 +71,6 @@ class ProviderNetwork(NetBoxModel):
|
||||
blank=True,
|
||||
verbose_name='Service ID'
|
||||
)
|
||||
description = models.CharField(
|
||||
max_length=200,
|
||||
blank=True
|
||||
)
|
||||
comments = models.TextField(
|
||||
blank=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ('provider', 'name')
|
||||
|
@ -39,8 +39,8 @@ class ProviderTable(ContactsColumnMixin, NetBoxTable):
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = Provider
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'asns', 'account', 'asn_count',
|
||||
'circuit_count', 'comments', 'contacts', 'tags', 'created', 'last_updated',
|
||||
'pk', 'id', 'name', 'asns', 'account', 'asn_count', 'circuit_count', 'description', 'comments', 'contacts',
|
||||
'tags', 'created', 'last_updated',
|
||||
)
|
||||
default_columns = ('pk', 'name', 'account', 'circuit_count')
|
||||
|
||||
|
@ -210,8 +210,8 @@ class RackSerializer(NetBoxModelSerializer):
|
||||
fields = [
|
||||
'id', 'url', 'display', 'name', 'facility_id', 'site', 'location', 'tenant', 'status', 'role', 'serial',
|
||||
'asset_tag', 'type', 'width', 'u_height', 'weight', 'weight_unit', 'desc_units', 'outer_width',
|
||||
'outer_depth', 'outer_unit', 'mounting_depth', 'comments', 'tags', 'custom_fields', 'created',
|
||||
'last_updated', 'device_count', 'powerfeed_count',
|
||||
'outer_depth', 'outer_unit', 'mounting_depth', 'description', 'comments', 'tags', 'custom_fields',
|
||||
'created', 'last_updated', 'device_count', 'powerfeed_count',
|
||||
]
|
||||
|
||||
|
||||
@ -243,8 +243,8 @@ class RackReservationSerializer(NetBoxModelSerializer):
|
||||
class Meta:
|
||||
model = RackReservation
|
||||
fields = [
|
||||
'id', 'url', 'display', 'rack', 'units', 'created', 'last_updated', 'user', 'tenant', 'description', 'tags',
|
||||
'custom_fields',
|
||||
'id', 'url', 'display', 'rack', 'units', 'created', 'last_updated', 'user', 'tenant', 'description',
|
||||
'comments', 'tags', 'custom_fields',
|
||||
]
|
||||
|
||||
|
||||
@ -324,8 +324,8 @@ class DeviceTypeSerializer(NetBoxModelSerializer):
|
||||
model = DeviceType
|
||||
fields = [
|
||||
'id', 'url', 'display', 'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth',
|
||||
'subdevice_role', 'airflow', 'weight', 'weight_unit', 'front_image', 'rear_image', 'comments',
|
||||
'tags', 'custom_fields', 'created', 'last_updated', 'device_count',
|
||||
'subdevice_role', 'airflow', 'weight', 'weight_unit', 'front_image', 'rear_image', 'description',
|
||||
'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'device_count',
|
||||
]
|
||||
|
||||
|
||||
@ -333,13 +333,12 @@ class ModuleTypeSerializer(NetBoxModelSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:moduletype-detail')
|
||||
manufacturer = NestedManufacturerSerializer()
|
||||
weight_unit = ChoiceField(choices=WeightUnitChoices, allow_blank=True, required=False)
|
||||
# module_count = serializers.IntegerField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = ModuleType
|
||||
fields = [
|
||||
'id', 'url', 'display', 'manufacturer', 'model', 'part_number', 'weight', 'weight_unit', 'comments', 'tags',
|
||||
'custom_fields', 'created', 'last_updated',
|
||||
'id', 'url', 'display', 'manufacturer', 'model', 'part_number', 'weight', 'weight_unit', 'description',
|
||||
'comments', 'tags', 'custom_fields', 'created', 'last_updated',
|
||||
]
|
||||
|
||||
|
||||
@ -656,8 +655,8 @@ class DeviceSerializer(NetBoxModelSerializer):
|
||||
fields = [
|
||||
'id', 'url', 'display', 'name', 'device_type', 'device_role', 'tenant', 'platform', 'serial', 'asset_tag',
|
||||
'site', 'location', 'rack', 'position', 'face', 'parent_device', 'status', 'airflow', 'primary_ip',
|
||||
'primary_ip4', 'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'comments',
|
||||
'local_context_data', 'tags', 'custom_fields', 'created', 'last_updated',
|
||||
'primary_ip4', 'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'description',
|
||||
'comments', 'local_context_data', 'tags', 'custom_fields', 'created', 'last_updated',
|
||||
]
|
||||
|
||||
@swagger_serializer_method(serializer_or_field=NestedDeviceSerializer)
|
||||
@ -697,8 +696,8 @@ class ModuleSerializer(NetBoxModelSerializer):
|
||||
class Meta:
|
||||
model = Module
|
||||
fields = [
|
||||
'id', 'url', 'display', 'device', 'module_bay', 'module_type', 'serial', 'asset_tag', 'comments', 'tags',
|
||||
'custom_fields', 'created', 'last_updated',
|
||||
'id', 'url', 'display', 'device', 'module_bay', 'module_type', 'serial', 'asset_tag', 'description',
|
||||
'comments', 'tags', 'custom_fields', 'created', 'last_updated',
|
||||
]
|
||||
|
||||
|
||||
@ -1041,7 +1040,7 @@ class CableSerializer(NetBoxModelSerializer):
|
||||
model = Cable
|
||||
fields = [
|
||||
'id', 'url', 'display', 'type', 'a_terminations', 'b_terminations', 'status', 'tenant', 'label', 'color',
|
||||
'length', 'length_unit', 'tags', 'custom_fields', 'created', 'last_updated',
|
||||
'length', 'length_unit', 'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
|
||||
]
|
||||
|
||||
|
||||
@ -1107,8 +1106,8 @@ class VirtualChassisSerializer(NetBoxModelSerializer):
|
||||
class Meta:
|
||||
model = VirtualChassis
|
||||
fields = [
|
||||
'id', 'url', 'display', 'name', 'domain', 'master', 'tags', 'custom_fields', 'member_count',
|
||||
'created', 'last_updated',
|
||||
'id', 'url', 'display', 'name', 'domain', 'master', 'description', 'comments', 'tags', 'custom_fields',
|
||||
'member_count', 'created', 'last_updated',
|
||||
]
|
||||
|
||||
|
||||
@ -1129,8 +1128,8 @@ class PowerPanelSerializer(NetBoxModelSerializer):
|
||||
class Meta:
|
||||
model = PowerPanel
|
||||
fields = [
|
||||
'id', 'url', 'display', 'site', 'location', 'name', 'tags', 'custom_fields', 'powerfeed_count',
|
||||
'created', 'last_updated',
|
||||
'id', 'url', 'display', 'site', 'location', 'name', 'description', 'comments', 'tags', 'custom_fields',
|
||||
'powerfeed_count', 'created', 'last_updated',
|
||||
]
|
||||
|
||||
|
||||
@ -1163,7 +1162,7 @@ class PowerFeedSerializer(NetBoxModelSerializer, CabledObjectSerializer, Connect
|
||||
model = PowerFeed
|
||||
fields = [
|
||||
'id', 'url', 'display', 'power_panel', 'rack', 'name', 'status', 'type', 'supply', 'phase', 'voltage',
|
||||
'amperage', 'max_utilization', 'comments', 'mark_connected', 'cable', 'cable_end', 'link_peers',
|
||||
'link_peers_type', 'connected_endpoints', 'connected_endpoints_type', 'connected_endpoints_reachable',
|
||||
'tags', 'custom_fields', 'created', 'last_updated', '_occupied',
|
||||
'amperage', 'max_utilization', 'mark_connected', 'cable', 'cable_end', 'link_peers', 'link_peers_type',
|
||||
'connected_endpoints', 'connected_endpoints_type', 'connected_endpoints_reachable', 'description',
|
||||
'comments', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied',
|
||||
]
|
||||
|
@ -128,22 +128,26 @@ class SiteBulkEditForm(NetBoxModelBulkEditForm):
|
||||
required=False,
|
||||
label='Contact E-mail'
|
||||
)
|
||||
description = forms.CharField(
|
||||
max_length=100,
|
||||
required=False
|
||||
)
|
||||
time_zone = TimeZoneFormField(
|
||||
choices=add_blank_choice(TimeZoneFormField().choices),
|
||||
required=False,
|
||||
widget=StaticSelect()
|
||||
)
|
||||
description = forms.CharField(
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
comments = CommentField(
|
||||
widget=SmallTextarea,
|
||||
label='Comments'
|
||||
)
|
||||
|
||||
model = Site
|
||||
fieldsets = (
|
||||
(None, ('status', 'region', 'group', 'tenant', 'asns', 'time_zone', 'description')),
|
||||
)
|
||||
nullable_fields = (
|
||||
'region', 'group', 'tenant', 'asns', 'description', 'time_zone',
|
||||
'region', 'group', 'tenant', 'asns', 'time_zone', 'description', 'comments',
|
||||
)
|
||||
|
||||
|
||||
@ -286,10 +290,6 @@ class RackBulkEditForm(NetBoxModelBulkEditForm):
|
||||
required=False,
|
||||
min_value=1
|
||||
)
|
||||
comments = CommentField(
|
||||
widget=SmallTextarea,
|
||||
label='Comments'
|
||||
)
|
||||
weight = forms.DecimalField(
|
||||
min_value=0,
|
||||
required=False
|
||||
@ -300,10 +300,18 @@ class RackBulkEditForm(NetBoxModelBulkEditForm):
|
||||
initial='',
|
||||
widget=StaticSelect()
|
||||
)
|
||||
description = forms.CharField(
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
comments = CommentField(
|
||||
widget=SmallTextarea,
|
||||
label='Comments'
|
||||
)
|
||||
|
||||
model = Rack
|
||||
fieldsets = (
|
||||
('Rack', ('status', 'role', 'tenant', 'serial', 'asset_tag')),
|
||||
('Rack', ('status', 'role', 'tenant', 'serial', 'asset_tag', 'description')),
|
||||
('Location', ('region', 'site_group', 'site', 'location')),
|
||||
('Hardware', (
|
||||
'type', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit', 'mounting_depth',
|
||||
@ -311,8 +319,8 @@ class RackBulkEditForm(NetBoxModelBulkEditForm):
|
||||
('Weight', ('weight', 'weight_unit')),
|
||||
)
|
||||
nullable_fields = (
|
||||
'location', 'tenant', 'role', 'serial', 'asset_tag', 'outer_width', 'outer_depth', 'outer_unit', 'comments',
|
||||
'weight', 'weight_unit'
|
||||
'location', 'tenant', 'role', 'serial', 'asset_tag', 'outer_width', 'outer_depth', 'outer_unit', 'weight',
|
||||
'weight_unit', 'description', 'comments',
|
||||
)
|
||||
|
||||
|
||||
@ -329,14 +337,19 @@ class RackReservationBulkEditForm(NetBoxModelBulkEditForm):
|
||||
required=False
|
||||
)
|
||||
description = forms.CharField(
|
||||
max_length=100,
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
comments = CommentField(
|
||||
widget=SmallTextarea,
|
||||
label='Comments'
|
||||
)
|
||||
|
||||
model = RackReservation
|
||||
fieldsets = (
|
||||
(None, ('user', 'tenant', 'description')),
|
||||
)
|
||||
nullable_fields = ('comments',)
|
||||
|
||||
|
||||
class ManufacturerBulkEditForm(NetBoxModelBulkEditForm):
|
||||
@ -384,13 +397,21 @@ class DeviceTypeBulkEditForm(NetBoxModelBulkEditForm):
|
||||
initial='',
|
||||
widget=StaticSelect()
|
||||
)
|
||||
description = forms.CharField(
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
comments = CommentField(
|
||||
widget=SmallTextarea,
|
||||
label='Comments'
|
||||
)
|
||||
|
||||
model = DeviceType
|
||||
fieldsets = (
|
||||
('Device Type', ('manufacturer', 'part_number', 'u_height', 'is_full_depth', 'airflow')),
|
||||
('Device Type', ('manufacturer', 'part_number', 'u_height', 'is_full_depth', 'airflow', 'description')),
|
||||
('Weight', ('weight', 'weight_unit')),
|
||||
)
|
||||
nullable_fields = ('part_number', 'airflow', 'weight', 'weight_unit')
|
||||
nullable_fields = ('part_number', 'airflow', 'weight', 'weight_unit', 'description', 'comments')
|
||||
|
||||
|
||||
class ModuleTypeBulkEditForm(NetBoxModelBulkEditForm):
|
||||
@ -411,13 +432,21 @@ class ModuleTypeBulkEditForm(NetBoxModelBulkEditForm):
|
||||
initial='',
|
||||
widget=StaticSelect()
|
||||
)
|
||||
description = forms.CharField(
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
comments = CommentField(
|
||||
widget=SmallTextarea,
|
||||
label='Comments'
|
||||
)
|
||||
|
||||
model = ModuleType
|
||||
fieldsets = (
|
||||
('Module Type', ('manufacturer', 'part_number')),
|
||||
('Module Type', ('manufacturer', 'part_number', 'description')),
|
||||
('Weight', ('weight', 'weight_unit')),
|
||||
)
|
||||
nullable_fields = ('part_number', 'weight', 'weight_unit')
|
||||
nullable_fields = ('part_number', 'weight', 'weight_unit', 'description', 'comments')
|
||||
|
||||
|
||||
class DeviceRoleBulkEditForm(NetBoxModelBulkEditForm):
|
||||
@ -513,15 +542,23 @@ class DeviceBulkEditForm(NetBoxModelBulkEditForm):
|
||||
required=False,
|
||||
label='Serial Number'
|
||||
)
|
||||
description = forms.CharField(
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
comments = CommentField(
|
||||
widget=SmallTextarea,
|
||||
label='Comments'
|
||||
)
|
||||
|
||||
model = Device
|
||||
fieldsets = (
|
||||
('Device', ('device_role', 'status', 'tenant', 'platform')),
|
||||
('Device', ('device_role', 'status', 'tenant', 'platform', 'description')),
|
||||
('Location', ('site', 'location')),
|
||||
('Hardware', ('manufacturer', 'device_type', 'airflow', 'serial')),
|
||||
)
|
||||
nullable_fields = (
|
||||
'location', 'tenant', 'platform', 'serial', 'airflow',
|
||||
'location', 'tenant', 'platform', 'serial', 'airflow', 'description', 'comments',
|
||||
)
|
||||
|
||||
|
||||
@ -542,12 +579,20 @@ class ModuleBulkEditForm(NetBoxModelBulkEditForm):
|
||||
required=False,
|
||||
label='Serial Number'
|
||||
)
|
||||
description = forms.CharField(
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
comments = CommentField(
|
||||
widget=SmallTextarea,
|
||||
label='Comments'
|
||||
)
|
||||
|
||||
model = Module
|
||||
fieldsets = (
|
||||
(None, ('manufacturer', 'module_type', 'serial')),
|
||||
(None, ('manufacturer', 'module_type', 'serial', 'description')),
|
||||
)
|
||||
nullable_fields = ('serial',)
|
||||
nullable_fields = ('serial', 'description', 'comments')
|
||||
|
||||
|
||||
class CableBulkEditForm(NetBoxModelBulkEditForm):
|
||||
@ -584,14 +629,22 @@ class CableBulkEditForm(NetBoxModelBulkEditForm):
|
||||
initial='',
|
||||
widget=StaticSelect()
|
||||
)
|
||||
description = forms.CharField(
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
comments = CommentField(
|
||||
widget=SmallTextarea,
|
||||
label='Comments'
|
||||
)
|
||||
|
||||
model = Cable
|
||||
fieldsets = (
|
||||
(None, ('type', 'status', 'tenant', 'label')),
|
||||
(None, ('type', 'status', 'tenant', 'label', 'description')),
|
||||
('Attributes', ('color', 'length', 'length_unit')),
|
||||
)
|
||||
nullable_fields = (
|
||||
'type', 'status', 'tenant', 'label', 'color', 'length',
|
||||
'type', 'status', 'tenant', 'label', 'color', 'length', 'description', 'comments',
|
||||
)
|
||||
|
||||
|
||||
@ -600,12 +653,20 @@ class VirtualChassisBulkEditForm(NetBoxModelBulkEditForm):
|
||||
max_length=30,
|
||||
required=False
|
||||
)
|
||||
description = forms.CharField(
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
comments = CommentField(
|
||||
widget=SmallTextarea,
|
||||
label='Comments'
|
||||
)
|
||||
|
||||
model = VirtualChassis
|
||||
fieldsets = (
|
||||
(None, ('domain',)),
|
||||
(None, ('domain', 'description')),
|
||||
)
|
||||
nullable_fields = ('domain',)
|
||||
nullable_fields = ('domain', 'description', 'comments')
|
||||
|
||||
|
||||
class PowerPanelBulkEditForm(NetBoxModelBulkEditForm):
|
||||
@ -638,12 +699,20 @@ class PowerPanelBulkEditForm(NetBoxModelBulkEditForm):
|
||||
'site_id': '$site'
|
||||
}
|
||||
)
|
||||
description = forms.CharField(
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
comments = CommentField(
|
||||
widget=SmallTextarea,
|
||||
label='Comments'
|
||||
)
|
||||
|
||||
model = PowerPanel
|
||||
fieldsets = (
|
||||
(None, ('region', 'site_group', 'site', 'location')),
|
||||
(None, ('region', 'site_group', 'site', 'location', 'description')),
|
||||
)
|
||||
nullable_fields = ('location',)
|
||||
nullable_fields = ('location', 'description', 'comments')
|
||||
|
||||
|
||||
class PowerFeedBulkEditForm(NetBoxModelBulkEditForm):
|
||||
@ -692,6 +761,10 @@ class PowerFeedBulkEditForm(NetBoxModelBulkEditForm):
|
||||
required=False,
|
||||
widget=BulkEditNullBooleanSelect
|
||||
)
|
||||
description = forms.CharField(
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
comments = CommentField(
|
||||
widget=SmallTextarea,
|
||||
label='Comments'
|
||||
@ -699,10 +772,10 @@ class PowerFeedBulkEditForm(NetBoxModelBulkEditForm):
|
||||
|
||||
model = PowerFeed
|
||||
fieldsets = (
|
||||
(None, ('power_panel', 'rack', 'status', 'type', 'mark_connected')),
|
||||
(None, ('power_panel', 'rack', 'status', 'type', 'mark_connected', 'description')),
|
||||
('Power', ('supply', 'phase', 'voltage', 'amperage', 'max_utilization'))
|
||||
)
|
||||
nullable_fields = ('location', 'comments')
|
||||
nullable_fields = ('location', 'description', 'comments')
|
||||
|
||||
|
||||
#
|
||||
|
@ -197,7 +197,8 @@ class RackCSVForm(NetBoxModelCSVForm):
|
||||
model = Rack
|
||||
fields = (
|
||||
'site', 'location', 'name', 'facility_id', 'tenant', 'status', 'role', 'type', 'serial', 'asset_tag',
|
||||
'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit', 'mounting_depth', 'comments',
|
||||
'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit', 'mounting_depth',
|
||||
'description', 'comments',
|
||||
)
|
||||
|
||||
def __init__(self, data=None, *args, **kwargs):
|
||||
@ -241,7 +242,7 @@ class RackReservationCSVForm(NetBoxModelCSVForm):
|
||||
|
||||
class Meta:
|
||||
model = RackReservation
|
||||
fields = ('site', 'location', 'rack', 'units', 'tenant', 'description')
|
||||
fields = ('site', 'location', 'rack', 'units', 'tenant', 'description', 'comments')
|
||||
|
||||
def __init__(self, data=None, *args, **kwargs):
|
||||
super().__init__(data, *args, **kwargs)
|
||||
@ -388,7 +389,7 @@ class DeviceCSVForm(BaseDeviceCSVForm):
|
||||
fields = [
|
||||
'name', 'device_role', 'tenant', 'manufacturer', 'device_type', 'platform', 'serial', 'asset_tag', 'status',
|
||||
'site', 'location', 'rack', 'position', 'face', 'airflow', 'virtual_chassis', 'vc_position', 'vc_priority',
|
||||
'cluster', 'comments',
|
||||
'cluster', 'description', 'comments',
|
||||
]
|
||||
|
||||
def __init__(self, data=None, *args, **kwargs):
|
||||
@ -425,7 +426,7 @@ class ModuleCSVForm(NetBoxModelCSVForm):
|
||||
class Meta:
|
||||
model = Module
|
||||
fields = (
|
||||
'device', 'module_bay', 'module_type', 'serial', 'asset_tag', 'comments',
|
||||
'device', 'module_bay', 'module_type', 'serial', 'asset_tag', 'description', 'comments',
|
||||
)
|
||||
|
||||
def __init__(self, data=None, *args, **kwargs):
|
||||
@ -928,7 +929,7 @@ class CableCSVForm(NetBoxModelCSVForm):
|
||||
model = Cable
|
||||
fields = [
|
||||
'side_a_device', 'side_a_type', 'side_a_name', 'side_b_device', 'side_b_type', 'side_b_name', 'type',
|
||||
'status', 'tenant', 'label', 'color', 'length', 'length_unit',
|
||||
'status', 'tenant', 'label', 'color', 'length', 'length_unit', 'description', 'comments',
|
||||
]
|
||||
help_texts = {
|
||||
'color': mark_safe('RGB color in hexadecimal (e.g. <code>00ff00</code>)'),
|
||||
@ -985,7 +986,7 @@ class VirtualChassisCSVForm(NetBoxModelCSVForm):
|
||||
|
||||
class Meta:
|
||||
model = VirtualChassis
|
||||
fields = ('name', 'domain', 'master')
|
||||
fields = ('name', 'domain', 'master', 'description')
|
||||
|
||||
|
||||
#
|
||||
@ -1006,7 +1007,7 @@ class PowerPanelCSVForm(NetBoxModelCSVForm):
|
||||
|
||||
class Meta:
|
||||
model = PowerPanel
|
||||
fields = ('site', 'location', 'name')
|
||||
fields = ('site', 'location', 'name', 'description', 'comments')
|
||||
|
||||
def __init__(self, data=None, *args, **kwargs):
|
||||
super().__init__(data, *args, **kwargs)
|
||||
@ -1062,7 +1063,7 @@ class PowerFeedCSVForm(NetBoxModelCSVForm):
|
||||
model = PowerFeed
|
||||
fields = (
|
||||
'site', 'power_panel', 'location', 'rack', 'name', 'status', 'type', 'mark_connected', 'supply', 'phase',
|
||||
'voltage', 'amperage', 'max_utilization', 'comments',
|
||||
'voltage', 'amperage', 'max_utilization', 'description', 'comments',
|
||||
)
|
||||
|
||||
def __init__(self, data=None, *args, **kwargs):
|
||||
|
@ -117,7 +117,7 @@ class DeviceComponentFilterForm(NetBoxModelFilterSetForm):
|
||||
class RegionFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
|
||||
model = Region
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag', 'parent_id')),
|
||||
(None, ('q', 'filter', 'tag', 'parent_id')),
|
||||
('Contacts', ('contact', 'contact_role', 'contact_group'))
|
||||
)
|
||||
parent_id = DynamicModelMultipleChoiceField(
|
||||
@ -131,7 +131,7 @@ class RegionFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
|
||||
class SiteGroupFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
|
||||
model = SiteGroup
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag', 'parent_id')),
|
||||
(None, ('q', 'filter', 'tag', 'parent_id')),
|
||||
('Contacts', ('contact', 'contact_role', 'contact_group'))
|
||||
)
|
||||
parent_id = DynamicModelMultipleChoiceField(
|
||||
@ -145,7 +145,7 @@ class SiteGroupFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
|
||||
class SiteFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilterSetForm):
|
||||
model = Site
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
(None, ('q', 'filter', 'tag')),
|
||||
('Attributes', ('status', 'region_id', 'group_id', 'asn_id')),
|
||||
('Tenant', ('tenant_group_id', 'tenant_id')),
|
||||
('Contacts', ('contact', 'contact_role', 'contact_group')),
|
||||
@ -175,7 +175,7 @@ class SiteFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilte
|
||||
class LocationFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilterSetForm):
|
||||
model = Location
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
(None, ('q', 'filter', 'tag')),
|
||||
('Attributes', ('region_id', 'site_group_id', 'site_id', 'parent_id', 'status')),
|
||||
('Tenant', ('tenant_group_id', 'tenant_id')),
|
||||
('Contacts', ('contact', 'contact_role', 'contact_group')),
|
||||
@ -223,7 +223,7 @@ class RackRoleFilterForm(NetBoxModelFilterSetForm):
|
||||
class RackFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilterSetForm):
|
||||
model = Rack
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
(None, ('q', 'filter', 'tag')),
|
||||
('Location', ('region_id', 'site_group_id', 'site_id', 'location_id')),
|
||||
('Function', ('status', 'role_id')),
|
||||
('Hardware', ('type', 'width', 'serial', 'asset_tag')),
|
||||
@ -307,7 +307,7 @@ class RackElevationFilterForm(RackFilterForm):
|
||||
class RackReservationFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
model = RackReservation
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
(None, ('q', 'filter', 'tag')),
|
||||
('User', ('user_id',)),
|
||||
('Rack', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id')),
|
||||
('Tenant', ('tenant_group_id', 'tenant_id')),
|
||||
@ -363,7 +363,7 @@ class RackReservationFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
class ManufacturerFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
|
||||
model = Manufacturer
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
(None, ('q', 'filter', 'tag')),
|
||||
('Contacts', ('contact', 'contact_role', 'contact_group'))
|
||||
)
|
||||
tag = TagFilterField(model)
|
||||
@ -372,7 +372,7 @@ class ManufacturerFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
|
||||
class DeviceTypeFilterForm(NetBoxModelFilterSetForm):
|
||||
model = DeviceType
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
(None, ('q', 'filter', 'tag')),
|
||||
('Hardware', ('manufacturer_id', 'part_number', 'subdevice_role', 'airflow')),
|
||||
('Images', ('has_front_image', 'has_rear_image')),
|
||||
('Components', (
|
||||
@ -487,7 +487,7 @@ class DeviceTypeFilterForm(NetBoxModelFilterSetForm):
|
||||
class ModuleTypeFilterForm(NetBoxModelFilterSetForm):
|
||||
model = ModuleType
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
(None, ('q', 'filter', 'tag')),
|
||||
('Hardware', ('manufacturer_id', 'part_number')),
|
||||
('Components', (
|
||||
'console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces',
|
||||
@ -579,7 +579,7 @@ class DeviceFilterForm(
|
||||
):
|
||||
model = Device
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
(None, ('q', 'filter', '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')),
|
||||
@ -763,7 +763,7 @@ class VirtualDeviceContextFilterForm(
|
||||
class ModuleFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
model = Module
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
(None, ('q', 'filter', 'tag')),
|
||||
('Hardware', ('manufacturer_id', 'module_type_id', 'serial', 'asset_tag')),
|
||||
)
|
||||
manufacturer_id = DynamicModelMultipleChoiceField(
|
||||
@ -793,7 +793,7 @@ class ModuleFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, NetBoxMo
|
||||
class VirtualChassisFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
model = VirtualChassis
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
(None, ('q', 'filter', 'tag')),
|
||||
('Location', ('region_id', 'site_group_id', 'site_id')),
|
||||
('Tenant', ('tenant_group_id', 'tenant_id')),
|
||||
)
|
||||
@ -822,7 +822,7 @@ class VirtualChassisFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
class CableFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
model = Cable
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
(None, ('q', 'filter', 'tag')),
|
||||
('Location', ('site_id', 'location_id', 'rack_id', 'device_id')),
|
||||
('Attributes', ('type', 'status', 'color', 'length', 'length_unit')),
|
||||
('Tenant', ('tenant_group_id', 'tenant_id')),
|
||||
@ -894,7 +894,7 @@ class CableFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
class PowerPanelFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
|
||||
model = PowerPanel
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
(None, ('q', 'filter', 'tag')),
|
||||
('Location', ('region_id', 'site_group_id', 'site_id', 'location_id')),
|
||||
('Contacts', ('contact', 'contact_role', 'contact_group')),
|
||||
)
|
||||
@ -932,7 +932,7 @@ class PowerPanelFilterForm(ContactModelFilterForm, NetBoxModelFilterSetForm):
|
||||
class PowerFeedFilterForm(NetBoxModelFilterSetForm):
|
||||
model = PowerFeed
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
(None, ('q', 'filter', 'tag')),
|
||||
('Location', ('region_id', 'site_group_id', 'site_id', 'power_panel_id', 'rack_id')),
|
||||
('Attributes', ('status', 'type', 'supply', 'phase', 'voltage', 'amperage', 'max_utilization')),
|
||||
)
|
||||
@ -1034,7 +1034,7 @@ class PathEndpointFilterForm(CabledFilterForm):
|
||||
class ConsolePortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
|
||||
model = ConsolePort
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
(None, ('q', 'filter', 'tag')),
|
||||
('Attributes', ('name', 'label', 'type', 'speed')),
|
||||
('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')),
|
||||
('Connection', ('cabled', 'connected', 'occupied')),
|
||||
@ -1053,7 +1053,7 @@ class ConsolePortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
|
||||
class ConsoleServerPortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
|
||||
model = ConsoleServerPort
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
(None, ('q', 'filter', 'tag')),
|
||||
('Attributes', ('name', 'label', 'type', 'speed')),
|
||||
('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')),
|
||||
('Connection', ('cabled', 'connected', 'occupied')),
|
||||
@ -1072,7 +1072,7 @@ class ConsoleServerPortFilterForm(PathEndpointFilterForm, DeviceComponentFilterF
|
||||
class PowerPortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
|
||||
model = PowerPort
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
(None, ('q', 'filter', 'tag')),
|
||||
('Attributes', ('name', 'label', 'type')),
|
||||
('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')),
|
||||
('Connection', ('cabled', 'connected', 'occupied')),
|
||||
@ -1087,7 +1087,7 @@ class PowerPortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
|
||||
class PowerOutletFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
|
||||
model = PowerOutlet
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
(None, ('q', 'filter', 'tag')),
|
||||
('Attributes', ('name', 'label', 'type')),
|
||||
('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')),
|
||||
('Connection', ('cabled', 'connected', 'occupied')),
|
||||
@ -1102,7 +1102,7 @@ class PowerOutletFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
|
||||
class InterfaceFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
|
||||
model = Interface
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
(None, ('q', 'filter', 'tag')),
|
||||
('Attributes', ('name', 'label', 'kind', 'type', 'speed', 'duplex', 'enabled', 'mgmt_only')),
|
||||
('Addressing', ('vrf_id', 'mac_address', 'wwn')),
|
||||
('PoE', ('poe_mode', 'poe_type')),
|
||||
@ -1200,7 +1200,7 @@ class InterfaceFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
|
||||
|
||||
class FrontPortFilterForm(CabledFilterForm, DeviceComponentFilterForm):
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
(None, ('q', 'filter', 'tag')),
|
||||
('Attributes', ('name', 'label', 'type', 'color')),
|
||||
('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')),
|
||||
('Cable', ('cabled', 'occupied')),
|
||||
@ -1219,7 +1219,7 @@ class FrontPortFilterForm(CabledFilterForm, DeviceComponentFilterForm):
|
||||
class RearPortFilterForm(CabledFilterForm, DeviceComponentFilterForm):
|
||||
model = RearPort
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
(None, ('q', 'filter', 'tag')),
|
||||
('Attributes', ('name', 'label', 'type', 'color')),
|
||||
('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')),
|
||||
('Cable', ('cabled', 'occupied')),
|
||||
@ -1237,7 +1237,7 @@ class RearPortFilterForm(CabledFilterForm, DeviceComponentFilterForm):
|
||||
class ModuleBayFilterForm(DeviceComponentFilterForm):
|
||||
model = ModuleBay
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
(None, ('q', 'filter', 'tag')),
|
||||
('Attributes', ('name', 'label', 'position')),
|
||||
('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')),
|
||||
)
|
||||
@ -1250,7 +1250,7 @@ class ModuleBayFilterForm(DeviceComponentFilterForm):
|
||||
class DeviceBayFilterForm(DeviceComponentFilterForm):
|
||||
model = DeviceBay
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
(None, ('q', 'filter', 'tag')),
|
||||
('Attributes', ('name', 'label')),
|
||||
('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')),
|
||||
)
|
||||
@ -1260,7 +1260,7 @@ class DeviceBayFilterForm(DeviceComponentFilterForm):
|
||||
class InventoryItemFilterForm(DeviceComponentFilterForm):
|
||||
model = InventoryItem
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
(None, ('q', 'filter', 'tag')),
|
||||
('Attributes', ('name', 'label', 'role_id', 'manufacturer_id', 'serial', 'asset_tag', 'discovered')),
|
||||
('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')),
|
||||
)
|
||||
|
@ -279,7 +279,7 @@ class RackForm(TenancyForm, NetBoxModelForm):
|
||||
fields = [
|
||||
'region', 'site_group', 'site', 'location', 'name', 'facility_id', 'tenant_group', 'tenant', 'status',
|
||||
'role', 'serial', 'asset_tag', 'type', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth',
|
||||
'outer_unit', 'mounting_depth', 'weight', 'weight_unit', 'comments', 'tags',
|
||||
'outer_unit', 'mounting_depth', 'weight', 'weight_unit', 'description', 'comments', 'tags',
|
||||
]
|
||||
help_texts = {
|
||||
'site': "The site at which the rack exists",
|
||||
@ -343,6 +343,7 @@ class RackReservationForm(TenancyForm, NetBoxModelForm):
|
||||
),
|
||||
widget=StaticSelect()
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
fieldsets = (
|
||||
('Reservation', ('region', 'site_group', 'site', 'location', 'rack', 'units', 'user', 'description', 'tags')),
|
||||
@ -353,7 +354,7 @@ class RackReservationForm(TenancyForm, NetBoxModelForm):
|
||||
model = RackReservation
|
||||
fields = [
|
||||
'region', 'site_group', 'site', 'location', 'rack', 'units', 'user', 'tenant_group', 'tenant',
|
||||
'description', 'tags',
|
||||
'description', 'comments', 'tags',
|
||||
]
|
||||
|
||||
|
||||
@ -384,10 +385,10 @@ class DeviceTypeForm(NetBoxModelForm):
|
||||
|
||||
fieldsets = (
|
||||
('Device Type', (
|
||||
'manufacturer', 'model', 'slug', 'part_number', 'tags',
|
||||
'manufacturer', 'model', 'slug', 'description', 'tags',
|
||||
)),
|
||||
('Chassis', (
|
||||
'u_height', 'is_full_depth', 'subdevice_role', 'airflow',
|
||||
'u_height', 'is_full_depth', 'part_number', 'subdevice_role', 'airflow',
|
||||
)),
|
||||
('Attributes', ('weight', 'weight_unit')),
|
||||
('Images', ('front_image', 'rear_image')),
|
||||
@ -397,7 +398,7 @@ class DeviceTypeForm(NetBoxModelForm):
|
||||
model = DeviceType
|
||||
fields = [
|
||||
'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role', 'airflow',
|
||||
'weight', 'weight_unit', 'front_image', 'rear_image', 'comments', 'tags',
|
||||
'weight', 'weight_unit', 'front_image', 'rear_image', 'description', 'comments', 'tags',
|
||||
]
|
||||
widgets = {
|
||||
'airflow': StaticSelect(),
|
||||
@ -419,15 +420,14 @@ class ModuleTypeForm(NetBoxModelForm):
|
||||
comments = CommentField()
|
||||
|
||||
fieldsets = (
|
||||
('Module Type', (
|
||||
'manufacturer', 'model', 'part_number', 'tags', 'weight', 'weight_unit'
|
||||
)),
|
||||
('Module Type', ('manufacturer', 'model', 'part_number', 'description', 'tags')),
|
||||
('Weight', ('weight', 'weight_unit'))
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = ModuleType
|
||||
fields = [
|
||||
'manufacturer', 'model', 'part_number', 'weight', 'weight_unit', 'comments', 'tags',
|
||||
'manufacturer', 'model', 'part_number', 'weight', 'weight_unit', 'description', 'comments', 'tags',
|
||||
]
|
||||
|
||||
widgets = {
|
||||
@ -592,7 +592,7 @@ class DeviceForm(TenancyForm, NetBoxModelForm):
|
||||
'name', 'device_role', 'device_type', 'serial', 'asset_tag', 'region', 'site_group', 'site', 'rack',
|
||||
'location', 'position', 'face', 'status', 'airflow', 'platform', 'primary_ip4', 'primary_ip6',
|
||||
'cluster_group', 'cluster', 'tenant_group', 'tenant', 'virtual_chassis', 'vc_position', 'vc_priority',
|
||||
'comments', 'tags', 'local_context_data'
|
||||
'description', 'comments', 'tags', 'local_context_data'
|
||||
]
|
||||
help_texts = {
|
||||
'device_role': "The function this device serves",
|
||||
@ -706,7 +706,7 @@ class ModuleForm(NetBoxModelForm):
|
||||
|
||||
fieldsets = (
|
||||
('Module', (
|
||||
'device', 'module_bay', 'manufacturer', 'module_type', 'tags',
|
||||
'device', 'module_bay', 'manufacturer', 'module_type', 'description', 'tags',
|
||||
)),
|
||||
('Hardware', (
|
||||
'serial', 'asset_tag', 'replicate_components', 'adopt_components',
|
||||
@ -717,7 +717,7 @@ class ModuleForm(NetBoxModelForm):
|
||||
model = Module
|
||||
fields = [
|
||||
'device', 'module_bay', 'manufacturer', 'module_type', 'serial', 'asset_tag', 'tags',
|
||||
'replicate_components', 'adopt_components', 'comments',
|
||||
'replicate_components', 'adopt_components', 'description', 'comments',
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@ -794,11 +794,13 @@ class ModuleForm(NetBoxModelForm):
|
||||
|
||||
|
||||
class CableForm(TenancyForm, NetBoxModelForm):
|
||||
comments = CommentField()
|
||||
|
||||
class Meta:
|
||||
model = Cable
|
||||
fields = [
|
||||
'type', 'status', 'tenant_group', 'tenant', 'label', 'color', 'length', 'length_unit', 'tags',
|
||||
'type', 'status', 'tenant_group', 'tenant', 'label', 'color', 'length', 'length_unit', 'description',
|
||||
'comments', 'tags',
|
||||
]
|
||||
widgets = {
|
||||
'status': StaticSelect,
|
||||
@ -841,15 +843,16 @@ class PowerPanelForm(NetBoxModelForm):
|
||||
'site_id': '$site'
|
||||
}
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
fieldsets = (
|
||||
('Power Panel', ('region', 'site_group', 'site', 'location', 'name', 'tags')),
|
||||
('Power Panel', ('region', 'site_group', 'site', 'location', 'name', 'description', 'tags')),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = PowerPanel
|
||||
fields = [
|
||||
'region', 'site_group', 'site', 'location', 'name', 'tags',
|
||||
'region', 'site_group', 'site', 'location', 'name', 'description', 'comments', 'tags',
|
||||
]
|
||||
|
||||
|
||||
@ -895,7 +898,7 @@ class PowerFeedForm(NetBoxModelForm):
|
||||
comments = CommentField()
|
||||
|
||||
fieldsets = (
|
||||
('Power Panel', ('region', 'site', 'power_panel')),
|
||||
('Power Panel', ('region', 'site', 'power_panel', 'description')),
|
||||
('Power Feed', ('rack', 'name', 'status', 'type', 'mark_connected', 'tags')),
|
||||
('Characteristics', ('supply', 'voltage', 'amperage', 'phase', 'max_utilization')),
|
||||
)
|
||||
@ -904,7 +907,7 @@ class PowerFeedForm(NetBoxModelForm):
|
||||
model = PowerFeed
|
||||
fields = [
|
||||
'region', 'site_group', 'site', 'power_panel', 'rack', 'name', 'status', 'type', 'mark_connected', 'supply',
|
||||
'phase', 'voltage', 'amperage', 'max_utilization', 'comments', 'tags',
|
||||
'phase', 'voltage', 'amperage', 'max_utilization', 'description', 'comments', 'tags',
|
||||
]
|
||||
widgets = {
|
||||
'status': StaticSelect(),
|
||||
@ -923,11 +926,12 @@ class VirtualChassisForm(NetBoxModelForm):
|
||||
queryset=Device.objects.all(),
|
||||
required=False,
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
class Meta:
|
||||
model = VirtualChassis
|
||||
fields = [
|
||||
'name', 'domain', 'master', 'tags',
|
||||
'name', 'domain', 'master', 'description', 'comments', 'tags',
|
||||
]
|
||||
widgets = {
|
||||
'master': SelectWithPK(),
|
||||
|
@ -30,7 +30,7 @@ class DeviceTypeImportForm(BootstrapMixin, forms.ModelForm):
|
||||
model = DeviceType
|
||||
fields = [
|
||||
'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role', 'airflow',
|
||||
'comments',
|
||||
'description', 'comments',
|
||||
]
|
||||
|
||||
|
||||
@ -42,7 +42,7 @@ class ModuleTypeImportForm(BootstrapMixin, forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = ModuleType
|
||||
fields = ['manufacturer', 'model', 'part_number', 'comments']
|
||||
fields = ['manufacturer', 'model', 'part_number', 'description', 'comments']
|
||||
|
||||
|
||||
#
|
||||
|
@ -195,7 +195,7 @@ class Migration(migrations.Migration):
|
||||
('description', models.CharField(blank=True, max_length=200)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['name'],
|
||||
'ordering': ('name',),
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
@ -352,7 +352,7 @@ class Migration(migrations.Migration):
|
||||
('description', models.CharField(blank=True, max_length=200)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['name'],
|
||||
'ordering': ('name',),
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
@ -369,7 +369,7 @@ class Migration(migrations.Migration):
|
||||
('description', models.CharField(blank=True, max_length=200)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['name'],
|
||||
'ordering': ('name',),
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
@ -538,7 +538,7 @@ class Migration(migrations.Migration):
|
||||
('description', models.CharField(blank=True, max_length=200)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['name'],
|
||||
'ordering': ('name',),
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
|
@ -27,7 +27,7 @@ class Migration(migrations.Migration):
|
||||
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['name'],
|
||||
'ordering': ('name',),
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
|
@ -0,0 +1,78 @@
|
||||
# Generated by Django 4.1.2 on 2022-11-03 18:24
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('dcim', '0164_rack_mounting_depth'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='cable',
|
||||
name='comments',
|
||||
field=models.TextField(blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='cable',
|
||||
name='description',
|
||||
field=models.CharField(blank=True, max_length=200),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='device',
|
||||
name='description',
|
||||
field=models.CharField(blank=True, max_length=200),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='devicetype',
|
||||
name='description',
|
||||
field=models.CharField(blank=True, max_length=200),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='module',
|
||||
name='description',
|
||||
field=models.CharField(blank=True, max_length=200),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='moduletype',
|
||||
name='description',
|
||||
field=models.CharField(blank=True, max_length=200),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='powerfeed',
|
||||
name='description',
|
||||
field=models.CharField(blank=True, max_length=200),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='powerpanel',
|
||||
name='comments',
|
||||
field=models.TextField(blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='powerpanel',
|
||||
name='description',
|
||||
field=models.CharField(blank=True, max_length=200),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='rack',
|
||||
name='description',
|
||||
field=models.CharField(blank=True, max_length=200),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='rackreservation',
|
||||
name='comments',
|
||||
field=models.TextField(blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='virtualchassis',
|
||||
name='comments',
|
||||
field=models.TextField(blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='virtualchassis',
|
||||
name='description',
|
||||
field=models.CharField(blank=True, max_length=200),
|
||||
),
|
||||
]
|
@ -1,4 +1,4 @@
|
||||
# Generated by Django 4.1.2 on 2022-11-02 13:24
|
||||
# Generated by Django 4.1.2 on 2022-11-04 12:46
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
@ -9,10 +9,10 @@ import utilities.json
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('ipam', '0062_unique_constraints'),
|
||||
('extras', '0082_exporttemplate_content_types'),
|
||||
('tenancy', '0008_unique_constraints'),
|
||||
('dcim', '0164_rack_mounting_depth'),
|
||||
('extras', '0083_savedfilter'),
|
||||
('ipam', '0063_standardize_description_comments'),
|
||||
('tenancy', '0009_standardize_description_comments'),
|
||||
('dcim', '0165_standardize_description_comments'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
@ -23,6 +23,7 @@ class Migration(migrations.Migration):
|
||||
('created', models.DateTimeField(auto_now_add=True, null=True)),
|
||||
('last_updated', models.DateTimeField(auto_now=True, null=True)),
|
||||
('custom_field_data', models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder)),
|
||||
('description', models.CharField(blank=True, max_length=200)),
|
||||
('name', models.CharField(max_length=64)),
|
||||
('status', models.CharField(blank=True, max_length=50)),
|
||||
('identifier', models.PositiveSmallIntegerField(blank=True, null=True)),
|
@ -12,8 +12,8 @@ from django.urls import reverse
|
||||
from dcim.choices import *
|
||||
from dcim.constants import *
|
||||
from dcim.fields import PathField
|
||||
from dcim.utils import decompile_path_node, object_to_path_node, path_node_to_object
|
||||
from netbox.models import NetBoxModel
|
||||
from dcim.utils import decompile_path_node, object_to_path_node
|
||||
from netbox.models import PrimaryModel
|
||||
from utilities.fields import ColorField
|
||||
from utilities.querysets import RestrictedQuerySet
|
||||
from utilities.utils import to_meters
|
||||
@ -34,7 +34,7 @@ trace_paths = Signal()
|
||||
# Cables
|
||||
#
|
||||
|
||||
class Cable(NetBoxModel):
|
||||
class Cable(PrimaryModel):
|
||||
"""
|
||||
A physical connection between two endpoints.
|
||||
"""
|
||||
|
@ -1029,27 +1029,9 @@ class InventoryItemRole(OrganizationalModel):
|
||||
"""
|
||||
Inventory items may optionally be assigned a functional role.
|
||||
"""
|
||||
name = models.CharField(
|
||||
max_length=100,
|
||||
unique=True
|
||||
)
|
||||
slug = models.SlugField(
|
||||
max_length=100,
|
||||
unique=True
|
||||
)
|
||||
color = ColorField(
|
||||
default=ColorChoices.COLOR_GREY
|
||||
)
|
||||
description = models.CharField(
|
||||
max_length=200,
|
||||
blank=True,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('dcim:inventoryitemrole', args=[self.pk])
|
||||
|
@ -18,7 +18,7 @@ from dcim.constants import *
|
||||
from extras.models import ConfigContextModel
|
||||
from extras.querysets import ConfigContextModelQuerySet
|
||||
from netbox.config import ConfigItem
|
||||
from netbox.models import OrganizationalModel, NetBoxModel
|
||||
from netbox.models import OrganizationalModel, PrimaryModel
|
||||
from utilities.choices import ColorChoices
|
||||
from utilities.fields import ColorField, NaturalOrderingField
|
||||
from .device_components import *
|
||||
@ -46,35 +46,16 @@ class Manufacturer(OrganizationalModel):
|
||||
"""
|
||||
A Manufacturer represents a company which produces hardware devices; for example, Juniper or Dell.
|
||||
"""
|
||||
name = models.CharField(
|
||||
max_length=100,
|
||||
unique=True
|
||||
)
|
||||
slug = models.SlugField(
|
||||
max_length=100,
|
||||
unique=True
|
||||
)
|
||||
description = models.CharField(
|
||||
max_length=200,
|
||||
blank=True
|
||||
)
|
||||
|
||||
# Generic relations
|
||||
contacts = GenericRelation(
|
||||
to='tenancy.ContactAssignment'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('dcim:manufacturer', args=[self.pk])
|
||||
|
||||
|
||||
class DeviceType(NetBoxModel, WeightMixin):
|
||||
class DeviceType(PrimaryModel, WeightMixin):
|
||||
"""
|
||||
A DeviceType represents a particular make (Manufacturer) and model of device. It specifies rack height and depth, as
|
||||
well as high-level functional role(s).
|
||||
@ -137,9 +118,6 @@ class DeviceType(NetBoxModel, WeightMixin):
|
||||
upload_to='devicetype-images',
|
||||
blank=True
|
||||
)
|
||||
comments = models.TextField(
|
||||
blank=True
|
||||
)
|
||||
|
||||
clone_fields = (
|
||||
'manufacturer', 'u_height', 'is_full_depth', 'subdevice_role', 'airflow', 'weight', 'weight_unit'
|
||||
@ -318,7 +296,7 @@ class DeviceType(NetBoxModel, WeightMixin):
|
||||
return self.subdevice_role == SubdeviceRoleChoices.ROLE_CHILD
|
||||
|
||||
|
||||
class ModuleType(NetBoxModel, WeightMixin):
|
||||
class ModuleType(PrimaryModel, WeightMixin):
|
||||
"""
|
||||
A ModuleType represents a hardware element that can be installed within a device and which houses additional
|
||||
components; for example, a line card within a chassis-based switch such as the Cisco Catalyst 6500. Like a
|
||||
@ -338,9 +316,6 @@ class ModuleType(NetBoxModel, WeightMixin):
|
||||
blank=True,
|
||||
help_text='Discrete part number (optional)'
|
||||
)
|
||||
comments = models.TextField(
|
||||
blank=True
|
||||
)
|
||||
|
||||
# Generic relations
|
||||
images = GenericRelation(
|
||||
@ -419,14 +394,6 @@ class DeviceRole(OrganizationalModel):
|
||||
color to be used when displaying rack elevations. The vm_role field determines whether the role is applicable to
|
||||
virtual machines as well.
|
||||
"""
|
||||
name = models.CharField(
|
||||
max_length=100,
|
||||
unique=True
|
||||
)
|
||||
slug = models.SlugField(
|
||||
max_length=100,
|
||||
unique=True
|
||||
)
|
||||
color = ColorField(
|
||||
default=ColorChoices.COLOR_GREY
|
||||
)
|
||||
@ -435,16 +402,6 @@ class DeviceRole(OrganizationalModel):
|
||||
verbose_name='VM Role',
|
||||
help_text='Virtual machines may be assigned to this role'
|
||||
)
|
||||
description = models.CharField(
|
||||
max_length=200,
|
||||
blank=True,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('dcim:devicerole', args=[self.pk])
|
||||
@ -456,14 +413,6 @@ class Platform(OrganizationalModel):
|
||||
NetBox uses Platforms to determine how to interact with devices when pulling inventory data or other information by
|
||||
specifying a NAPALM driver.
|
||||
"""
|
||||
name = models.CharField(
|
||||
max_length=100,
|
||||
unique=True
|
||||
)
|
||||
slug = models.SlugField(
|
||||
max_length=100,
|
||||
unique=True
|
||||
)
|
||||
manufacturer = models.ForeignKey(
|
||||
to='dcim.Manufacturer',
|
||||
on_delete=models.PROTECT,
|
||||
@ -484,22 +433,12 @@ class Platform(OrganizationalModel):
|
||||
verbose_name='NAPALM arguments',
|
||||
help_text='Additional arguments to pass when initiating the NAPALM driver (JSON format)'
|
||||
)
|
||||
description = models.CharField(
|
||||
max_length=200,
|
||||
blank=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('dcim:platform', args=[self.pk])
|
||||
|
||||
|
||||
class Device(NetBoxModel, ConfigContextModel):
|
||||
class Device(PrimaryModel, ConfigContextModel):
|
||||
"""
|
||||
A Device represents a piece of physical hardware mounted within a Rack. Each Device is assigned a DeviceType,
|
||||
DeviceRole, and (optionally) a Platform. Device names are not required, however if one is set it must be unique.
|
||||
@ -643,9 +582,6 @@ class Device(NetBoxModel, ConfigContextModel):
|
||||
null=True,
|
||||
validators=[MaxValueValidator(255)]
|
||||
)
|
||||
comments = models.TextField(
|
||||
blank=True
|
||||
)
|
||||
|
||||
# Generic relations
|
||||
contacts = GenericRelation(
|
||||
@ -962,7 +898,7 @@ class Device(NetBoxModel, ConfigContextModel):
|
||||
return round(total_weight / 1000, 2)
|
||||
|
||||
|
||||
class Module(NetBoxModel, ConfigContextModel):
|
||||
class Module(PrimaryModel, ConfigContextModel):
|
||||
"""
|
||||
A Module represents a field-installable component within a Device which may itself hold multiple device components
|
||||
(for example, a line card within a chassis switch). Modules are instantiated from ModuleTypes.
|
||||
@ -995,9 +931,6 @@ class Module(NetBoxModel, ConfigContextModel):
|
||||
verbose_name='Asset tag',
|
||||
help_text='A unique tag used to identify this device'
|
||||
)
|
||||
comments = models.TextField(
|
||||
blank=True
|
||||
)
|
||||
|
||||
clone_fields = ('device', 'module_type')
|
||||
|
||||
@ -1075,7 +1008,7 @@ class Module(NetBoxModel, ConfigContextModel):
|
||||
# Virtual chassis
|
||||
#
|
||||
|
||||
class VirtualChassis(NetBoxModel):
|
||||
class VirtualChassis(PrimaryModel):
|
||||
"""
|
||||
A collection of Devices which operate with a shared control plane (e.g. a switch stack).
|
||||
"""
|
||||
@ -1132,7 +1065,7 @@ class VirtualChassis(NetBoxModel):
|
||||
return super().delete(*args, **kwargs)
|
||||
|
||||
|
||||
class VirtualDeviceContext(NetBoxModel):
|
||||
class VirtualDeviceContext(PrimaryModel):
|
||||
device = models.ForeignKey(
|
||||
to='Device',
|
||||
on_delete=models.PROTECT,
|
||||
|
@ -6,9 +6,8 @@ from django.db import models
|
||||
from django.urls import reverse
|
||||
|
||||
from dcim.choices import *
|
||||
from dcim.constants import *
|
||||
from netbox.config import ConfigItem
|
||||
from netbox.models import NetBoxModel
|
||||
from netbox.models import PrimaryModel
|
||||
from utilities.validators import ExclusionValidator
|
||||
from .device_components import CabledObjectModel, PathEndpoint
|
||||
|
||||
@ -22,7 +21,7 @@ __all__ = (
|
||||
# Power
|
||||
#
|
||||
|
||||
class PowerPanel(NetBoxModel):
|
||||
class PowerPanel(PrimaryModel):
|
||||
"""
|
||||
A distribution point for electrical power; e.g. a data center RPP.
|
||||
"""
|
||||
@ -77,7 +76,7 @@ class PowerPanel(NetBoxModel):
|
||||
)
|
||||
|
||||
|
||||
class PowerFeed(NetBoxModel, PathEndpoint, CabledObjectModel):
|
||||
class PowerFeed(PrimaryModel, PathEndpoint, CabledObjectModel):
|
||||
"""
|
||||
An electrical circuit delivered from a PowerPanel.
|
||||
"""
|
||||
@ -132,9 +131,6 @@ class PowerFeed(NetBoxModel, PathEndpoint, CabledObjectModel):
|
||||
default=0,
|
||||
editable=False
|
||||
)
|
||||
comments = models.TextField(
|
||||
blank=True
|
||||
)
|
||||
|
||||
clone_fields = (
|
||||
'power_panel', 'rack', 'status', 'type', 'mark_connected', 'supply', 'phase', 'voltage', 'amperage',
|
||||
|
@ -14,7 +14,7 @@ from django.urls import reverse
|
||||
from dcim.choices import *
|
||||
from dcim.constants import *
|
||||
from dcim.svg import RackElevationSVG
|
||||
from netbox.models import OrganizationalModel, NetBoxModel
|
||||
from netbox.models import OrganizationalModel, PrimaryModel
|
||||
from utilities.choices import ColorChoices
|
||||
from utilities.fields import ColorField, NaturalOrderingField
|
||||
from utilities.utils import array_to_string, drange
|
||||
@ -38,33 +38,15 @@ class RackRole(OrganizationalModel):
|
||||
"""
|
||||
Racks can be organized by functional role, similar to Devices.
|
||||
"""
|
||||
name = models.CharField(
|
||||
max_length=100,
|
||||
unique=True
|
||||
)
|
||||
slug = models.SlugField(
|
||||
max_length=100,
|
||||
unique=True
|
||||
)
|
||||
color = ColorField(
|
||||
default=ColorChoices.COLOR_GREY
|
||||
)
|
||||
description = models.CharField(
|
||||
max_length=200,
|
||||
blank=True,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('dcim:rackrole', args=[self.pk])
|
||||
|
||||
|
||||
class Rack(NetBoxModel, WeightMixin):
|
||||
class Rack(PrimaryModel, WeightMixin):
|
||||
"""
|
||||
Devices are housed within Racks. Each rack has a defined height measured in rack units, and a front and rear face.
|
||||
Each Rack is assigned to a Site and (optionally) a Location.
|
||||
@ -175,9 +157,6 @@ class Rack(NetBoxModel, WeightMixin):
|
||||
'distance between the front and rear rails.'
|
||||
)
|
||||
)
|
||||
comments = models.TextField(
|
||||
blank=True
|
||||
)
|
||||
|
||||
# Generic relations
|
||||
vlan_groups = GenericRelation(
|
||||
@ -481,7 +460,7 @@ class Rack(NetBoxModel, WeightMixin):
|
||||
return round(total_weight / 1000, 2)
|
||||
|
||||
|
||||
class RackReservation(NetBoxModel):
|
||||
class RackReservation(PrimaryModel):
|
||||
"""
|
||||
One or more reserved units within a Rack.
|
||||
"""
|
||||
|
@ -2,12 +2,11 @@ from django.contrib.contenttypes.fields import GenericRelation
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import models
|
||||
from django.urls import reverse
|
||||
from mptt.models import TreeForeignKey
|
||||
from timezone_field import TimeZoneField
|
||||
|
||||
from dcim.choices import *
|
||||
from dcim.constants import *
|
||||
from netbox.models import NestedGroupModel, NetBoxModel
|
||||
from netbox.models import NestedGroupModel, PrimaryModel
|
||||
from utilities.fields import NaturalOrderingField
|
||||
|
||||
__all__ = (
|
||||
@ -28,25 +27,6 @@ class Region(NestedGroupModel):
|
||||
states, and/or cities. Regions are recursively nested into a hierarchy: all sites belonging to a child region are
|
||||
also considered to be members of its parent and ancestor region(s).
|
||||
"""
|
||||
parent = TreeForeignKey(
|
||||
to='self',
|
||||
on_delete=models.CASCADE,
|
||||
related_name='children',
|
||||
blank=True,
|
||||
null=True,
|
||||
db_index=True
|
||||
)
|
||||
name = models.CharField(
|
||||
max_length=100
|
||||
)
|
||||
slug = models.SlugField(
|
||||
max_length=100
|
||||
)
|
||||
description = models.CharField(
|
||||
max_length=200,
|
||||
blank=True
|
||||
)
|
||||
|
||||
# Generic relations
|
||||
vlan_groups = GenericRelation(
|
||||
to='ipam.VLANGroup',
|
||||
@ -102,25 +82,6 @@ class SiteGroup(NestedGroupModel):
|
||||
within corporate sites you might distinguish between offices and data centers. Like regions, site groups can be
|
||||
nested recursively to form a hierarchy.
|
||||
"""
|
||||
parent = TreeForeignKey(
|
||||
to='self',
|
||||
on_delete=models.CASCADE,
|
||||
related_name='children',
|
||||
blank=True,
|
||||
null=True,
|
||||
db_index=True
|
||||
)
|
||||
name = models.CharField(
|
||||
max_length=100
|
||||
)
|
||||
slug = models.SlugField(
|
||||
max_length=100
|
||||
)
|
||||
description = models.CharField(
|
||||
max_length=200,
|
||||
blank=True
|
||||
)
|
||||
|
||||
# Generic relations
|
||||
vlan_groups = GenericRelation(
|
||||
to='ipam.VLANGroup',
|
||||
@ -170,7 +131,7 @@ class SiteGroup(NestedGroupModel):
|
||||
# Sites
|
||||
#
|
||||
|
||||
class Site(NetBoxModel):
|
||||
class Site(PrimaryModel):
|
||||
"""
|
||||
A Site represents a geographic location within a network; typically a building or campus. The optional facility
|
||||
field can be used to include an external designation, such as a data center name (e.g. Equinix SV6).
|
||||
@ -227,10 +188,6 @@ class Site(NetBoxModel):
|
||||
time_zone = TimeZoneField(
|
||||
blank=True
|
||||
)
|
||||
description = models.CharField(
|
||||
max_length=200,
|
||||
blank=True
|
||||
)
|
||||
physical_address = models.CharField(
|
||||
max_length=200,
|
||||
blank=True
|
||||
@ -253,9 +210,6 @@ class Site(NetBoxModel):
|
||||
null=True,
|
||||
help_text='GPS coordinate (longitude)'
|
||||
)
|
||||
comments = models.TextField(
|
||||
blank=True
|
||||
)
|
||||
|
||||
# Generic relations
|
||||
vlan_groups = GenericRelation(
|
||||
@ -298,25 +252,11 @@ class Location(NestedGroupModel):
|
||||
A Location represents a subgroup of Racks and/or Devices within a Site. A Location may represent a building within a
|
||||
site, or a room within a building, for example.
|
||||
"""
|
||||
name = models.CharField(
|
||||
max_length=100
|
||||
)
|
||||
slug = models.SlugField(
|
||||
max_length=100
|
||||
)
|
||||
site = models.ForeignKey(
|
||||
to='dcim.Site',
|
||||
on_delete=models.CASCADE,
|
||||
related_name='locations'
|
||||
)
|
||||
parent = TreeForeignKey(
|
||||
to='self',
|
||||
on_delete=models.CASCADE,
|
||||
related_name='children',
|
||||
blank=True,
|
||||
null=True,
|
||||
db_index=True
|
||||
)
|
||||
status = models.CharField(
|
||||
max_length=50,
|
||||
choices=LocationStatusChoices,
|
||||
@ -329,10 +269,6 @@ class Location(NestedGroupModel):
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
description = models.CharField(
|
||||
max_length=200,
|
||||
blank=True
|
||||
)
|
||||
|
||||
# Generic relations
|
||||
vlan_groups = GenericRelation(
|
||||
|
@ -111,6 +111,7 @@ class CableTable(TenancyColumnsMixin, NetBoxTable):
|
||||
order_by=('_abs_length', 'length_unit')
|
||||
)
|
||||
color = columns.ColorColumn()
|
||||
comments = columns.MarkdownColumn()
|
||||
tags = columns.TagColumn(
|
||||
url_name='dcim:cable_list'
|
||||
)
|
||||
@ -120,7 +121,7 @@ class CableTable(TenancyColumnsMixin, NetBoxTable):
|
||||
fields = (
|
||||
'pk', 'id', 'label', 'a_terminations', 'b_terminations', 'device_a', 'device_b', 'rack_a', 'rack_b',
|
||||
'location_a', 'location_b', 'site_a', 'site_b', 'status', 'type', 'tenant', 'tenant_group', 'color',
|
||||
'length', 'tags', 'created', 'last_updated',
|
||||
'length', 'description', 'comments', 'tags', 'created', 'last_updated',
|
||||
)
|
||||
default_columns = (
|
||||
'pk', 'id', 'label', 'a_terminations', 'b_terminations', 'status', 'type',
|
||||
|
@ -1,22 +1,5 @@
|
||||
import django_tables2 as tables
|
||||
from dcim.models import (
|
||||
ConsolePort,
|
||||
ConsoleServerPort,
|
||||
Device,
|
||||
DeviceBay,
|
||||
DeviceRole,
|
||||
FrontPort,
|
||||
Interface,
|
||||
InventoryItem,
|
||||
InventoryItemRole,
|
||||
ModuleBay,
|
||||
Platform,
|
||||
PowerOutlet,
|
||||
PowerPort,
|
||||
RearPort,
|
||||
VirtualChassis,
|
||||
VirtualDeviceContext,
|
||||
)
|
||||
from dcim import models
|
||||
from django_tables2.utils import Accessor
|
||||
from tenancy.tables import ContactsColumnMixin, TenancyColumnsMixin
|
||||
|
||||
@ -108,7 +91,7 @@ class DeviceRoleTable(NetBoxTable):
|
||||
)
|
||||
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = DeviceRole
|
||||
model = models.DeviceRole
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'device_count', 'vm_count', 'color', 'vm_role', 'description', 'slug', 'tags',
|
||||
'actions', 'created', 'last_updated',
|
||||
@ -139,7 +122,7 @@ class PlatformTable(NetBoxTable):
|
||||
)
|
||||
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = Platform
|
||||
model = models.Platform
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'manufacturer', 'device_count', 'vm_count', 'slug', 'napalm_driver', 'napalm_args',
|
||||
'description', 'tags', 'actions', 'created', 'last_updated',
|
||||
@ -222,12 +205,12 @@ class DeviceTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
|
||||
)
|
||||
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = Device
|
||||
model = models.Device
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'status', 'tenant', 'tenant_group', 'device_role', 'manufacturer', 'device_type',
|
||||
'platform', 'serial', 'asset_tag', 'region', 'site_group', 'site', 'location', 'rack', 'position', 'face',
|
||||
'airflow', 'primary_ip', 'primary_ip4', 'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position',
|
||||
'vc_priority', 'comments', 'contacts', 'tags', 'created', 'last_updated',
|
||||
'vc_priority', 'description', 'comments', 'contacts', 'tags', 'created', 'last_updated',
|
||||
)
|
||||
default_columns = (
|
||||
'pk', 'name', 'status', 'tenant', 'site', 'location', 'rack', 'device_role', 'manufacturer', 'device_type',
|
||||
@ -254,7 +237,7 @@ class DeviceImportTable(TenancyColumnsMixin, NetBoxTable):
|
||||
)
|
||||
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = Device
|
||||
model = models.Device
|
||||
fields = ('id', 'name', 'status', 'tenant', 'tenant_group', 'site', 'rack', 'position', 'device_role', 'device_type')
|
||||
empty_text = False
|
||||
|
||||
@ -328,7 +311,7 @@ class ConsolePortTable(ModularDeviceComponentTable, PathEndpointTable):
|
||||
)
|
||||
|
||||
class Meta(DeviceComponentTable.Meta):
|
||||
model = ConsolePort
|
||||
model = models.ConsolePort
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'device', 'module_bay', 'module', 'label', 'type', 'speed', 'description',
|
||||
'mark_connected', 'cable', 'cable_color', 'link_peer', 'connection', 'tags', 'created', 'last_updated',
|
||||
@ -347,7 +330,7 @@ class DeviceConsolePortTable(ConsolePortTable):
|
||||
)
|
||||
|
||||
class Meta(DeviceComponentTable.Meta):
|
||||
model = ConsolePort
|
||||
model = models.ConsolePort
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'module_bay', 'module', 'label', 'type', 'speed', 'description', 'mark_connected',
|
||||
'cable', 'cable_color', 'link_peer', 'connection', 'tags', 'actions'
|
||||
@ -370,7 +353,7 @@ class ConsoleServerPortTable(ModularDeviceComponentTable, PathEndpointTable):
|
||||
)
|
||||
|
||||
class Meta(DeviceComponentTable.Meta):
|
||||
model = ConsoleServerPort
|
||||
model = models.ConsoleServerPort
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'device', 'module_bay', 'module', 'label', 'type', 'speed', 'description',
|
||||
'mark_connected', 'cable', 'cable_color', 'link_peer', 'connection', 'tags', 'created', 'last_updated',
|
||||
@ -390,7 +373,7 @@ class DeviceConsoleServerPortTable(ConsoleServerPortTable):
|
||||
)
|
||||
|
||||
class Meta(DeviceComponentTable.Meta):
|
||||
model = ConsoleServerPort
|
||||
model = models.ConsoleServerPort
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'module_bay', 'module', 'label', 'type', 'speed', 'description', 'mark_connected',
|
||||
'cable', 'cable_color', 'link_peer', 'connection', 'tags', 'actions',
|
||||
@ -413,7 +396,7 @@ class PowerPortTable(ModularDeviceComponentTable, PathEndpointTable):
|
||||
)
|
||||
|
||||
class Meta(DeviceComponentTable.Meta):
|
||||
model = PowerPort
|
||||
model = models.PowerPort
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'device', 'module_bay', 'module', 'label', 'type', 'description', 'mark_connected',
|
||||
'maximum_draw', 'allocated_draw', 'cable', 'cable_color', 'link_peer', 'connection', 'tags', 'created',
|
||||
@ -434,7 +417,7 @@ class DevicePowerPortTable(PowerPortTable):
|
||||
)
|
||||
|
||||
class Meta(DeviceComponentTable.Meta):
|
||||
model = PowerPort
|
||||
model = models.PowerPort
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'module_bay', 'module', 'label', 'type', 'maximum_draw', 'allocated_draw',
|
||||
'description', 'mark_connected', 'cable', 'cable_color', 'link_peer', 'connection', 'tags', 'actions',
|
||||
@ -462,7 +445,7 @@ class PowerOutletTable(ModularDeviceComponentTable, PathEndpointTable):
|
||||
)
|
||||
|
||||
class Meta(DeviceComponentTable.Meta):
|
||||
model = PowerOutlet
|
||||
model = models.PowerOutlet
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'device', 'module_bay', 'module', 'label', 'type', 'description', 'power_port',
|
||||
'feed_leg', 'mark_connected', 'cable', 'cable_color', 'link_peer', 'connection', 'tags', 'created',
|
||||
@ -482,7 +465,7 @@ class DevicePowerOutletTable(PowerOutletTable):
|
||||
)
|
||||
|
||||
class Meta(DeviceComponentTable.Meta):
|
||||
model = PowerOutlet
|
||||
model = models.PowerOutlet
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'module_bay', 'module', 'label', 'type', 'power_port', 'feed_leg', 'description',
|
||||
'mark_connected', 'cable', 'cable_color', 'link_peer', 'connection', 'tags', 'actions',
|
||||
@ -546,7 +529,7 @@ class InterfaceTable(ModularDeviceComponentTable, BaseInterfaceTable, PathEndpoi
|
||||
)
|
||||
|
||||
class Meta(DeviceComponentTable.Meta):
|
||||
model = Interface
|
||||
model = models.Interface
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'device', 'module_bay', 'module', 'label', 'enabled', 'type', 'mgmt_only', 'mtu',
|
||||
'speed', 'duplex', 'mode', 'mac_address', 'wwn', 'poe_mode', 'poe_type', 'rf_role', 'rf_channel',
|
||||
@ -580,7 +563,7 @@ class DeviceInterfaceTable(InterfaceTable):
|
||||
)
|
||||
|
||||
class Meta(DeviceComponentTable.Meta):
|
||||
model = Interface
|
||||
model = models.Interface
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'module_bay', 'module', 'label', 'enabled', 'type', 'parent', 'bridge', 'lag',
|
||||
'mgmt_only', 'mtu', 'mode', 'mac_address', 'wwn', 'rf_role', 'rf_channel', 'rf_channel_frequency',
|
||||
@ -619,7 +602,7 @@ class FrontPortTable(ModularDeviceComponentTable, CableTerminationTable):
|
||||
)
|
||||
|
||||
class Meta(DeviceComponentTable.Meta):
|
||||
model = FrontPort
|
||||
model = models.FrontPort
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'device', 'module_bay', 'module', 'label', 'type', 'color', 'rear_port',
|
||||
'rear_port_position', 'description', 'mark_connected', 'cable', 'cable_color', 'link_peer', 'tags',
|
||||
@ -642,7 +625,7 @@ class DeviceFrontPortTable(FrontPortTable):
|
||||
)
|
||||
|
||||
class Meta(DeviceComponentTable.Meta):
|
||||
model = FrontPort
|
||||
model = models.FrontPort
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'module_bay', 'module', 'label', 'type', 'rear_port', 'rear_port_position',
|
||||
'description', 'mark_connected', 'cable', 'cable_color', 'link_peer', 'tags', 'actions',
|
||||
@ -668,7 +651,7 @@ class RearPortTable(ModularDeviceComponentTable, CableTerminationTable):
|
||||
)
|
||||
|
||||
class Meta(DeviceComponentTable.Meta):
|
||||
model = RearPort
|
||||
model = models.RearPort
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'device', 'module_bay', 'module', 'label', 'type', 'color', 'positions', 'description',
|
||||
'mark_connected', 'cable', 'cable_color', 'link_peer', 'tags', 'created', 'last_updated',
|
||||
@ -688,7 +671,7 @@ class DeviceRearPortTable(RearPortTable):
|
||||
)
|
||||
|
||||
class Meta(DeviceComponentTable.Meta):
|
||||
model = RearPort
|
||||
model = models.RearPort
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'module_bay', 'module', 'label', 'type', 'positions', 'description', 'mark_connected',
|
||||
'cable', 'cable_color', 'link_peer', 'tags', 'actions',
|
||||
@ -729,7 +712,7 @@ class DeviceBayTable(DeviceComponentTable):
|
||||
)
|
||||
|
||||
class Meta(DeviceComponentTable.Meta):
|
||||
model = DeviceBay
|
||||
model = models.DeviceBay
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'device', 'label', 'status', 'device_role', 'device_type', 'installed_device', 'description', 'tags',
|
||||
'created', 'last_updated',
|
||||
@ -750,7 +733,7 @@ class DeviceDeviceBayTable(DeviceBayTable):
|
||||
)
|
||||
|
||||
class Meta(DeviceComponentTable.Meta):
|
||||
model = DeviceBay
|
||||
model = models.DeviceBay
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'label', 'status', 'installed_device', 'description', 'tags', 'actions',
|
||||
)
|
||||
@ -779,7 +762,7 @@ class ModuleBayTable(DeviceComponentTable):
|
||||
)
|
||||
|
||||
class Meta(DeviceComponentTable.Meta):
|
||||
model = ModuleBay
|
||||
model = models.ModuleBay
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'device', 'label', 'position', 'installed_module', 'module_serial', 'module_asset_tag',
|
||||
'description', 'tags',
|
||||
@ -793,7 +776,7 @@ class DeviceModuleBayTable(ModuleBayTable):
|
||||
)
|
||||
|
||||
class Meta(DeviceComponentTable.Meta):
|
||||
model = ModuleBay
|
||||
model = models.ModuleBay
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'label', 'position', 'installed_module', 'module_serial', 'module_asset_tag',
|
||||
'description', 'tags', 'actions',
|
||||
@ -823,7 +806,7 @@ class InventoryItemTable(DeviceComponentTable):
|
||||
cable = None # Override DeviceComponentTable
|
||||
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = InventoryItem
|
||||
model = models.InventoryItem
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'device', 'component', 'label', 'role', 'manufacturer', 'part_id', 'serial',
|
||||
'asset_tag', 'description', 'discovered', 'tags', 'created', 'last_updated',
|
||||
@ -842,7 +825,7 @@ class DeviceInventoryItemTable(InventoryItemTable):
|
||||
)
|
||||
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = InventoryItem
|
||||
model = models.InventoryItem
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'label', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'component',
|
||||
'description', 'discovered', 'tags', 'actions',
|
||||
@ -867,7 +850,7 @@ class InventoryItemRoleTable(NetBoxTable):
|
||||
)
|
||||
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = InventoryItemRole
|
||||
model = models.InventoryItemRole
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'inventoryitem_count', 'color', 'description', 'slug', 'tags', 'actions',
|
||||
)
|
||||
@ -890,13 +873,17 @@ class VirtualChassisTable(NetBoxTable):
|
||||
url_params={'virtual_chassis_id': 'pk'},
|
||||
verbose_name='Members'
|
||||
)
|
||||
comments = columns.MarkdownColumn()
|
||||
tags = columns.TagColumn(
|
||||
url_name='dcim:virtualchassis_list'
|
||||
)
|
||||
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = VirtualChassis
|
||||
fields = ('pk', 'id', 'name', 'domain', 'master', 'member_count', 'tags', 'created', 'last_updated',)
|
||||
model = models.VirtualChassis
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'domain', 'master', 'member_count', 'description', 'comments', 'tags', 'created',
|
||||
'last_updated',
|
||||
)
|
||||
default_columns = ('pk', 'name', 'domain', 'master', 'member_count')
|
||||
|
||||
|
||||
@ -931,7 +918,7 @@ class VirtualDeviceContextTable(TenancyColumnsMixin, NetBoxTable):
|
||||
)
|
||||
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = VirtualDeviceContext
|
||||
model = models.VirtualDeviceContext
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'identifier', 'tenant', 'tenant_group',
|
||||
'primary_ip', 'primary_ip4', 'primary_ip6', 'comments', 'tags', 'created', 'last_updated',
|
||||
|
@ -1,19 +1,6 @@
|
||||
import django_tables2 as tables
|
||||
|
||||
from dcim.models import (
|
||||
ConsolePortTemplate,
|
||||
ConsoleServerPortTemplate,
|
||||
DeviceBayTemplate,
|
||||
DeviceType,
|
||||
FrontPortTemplate,
|
||||
InterfaceTemplate,
|
||||
InventoryItemTemplate,
|
||||
Manufacturer,
|
||||
ModuleBayTemplate,
|
||||
PowerOutletTemplate,
|
||||
PowerPortTemplate,
|
||||
RearPortTemplate,
|
||||
)
|
||||
from dcim import models
|
||||
from netbox.tables import NetBoxTable, columns
|
||||
from tenancy.tables import ContactsColumnMixin
|
||||
from .template_code import MODULAR_COMPONENT_TEMPLATE_BUTTONS, DEVICE_WEIGHT
|
||||
@ -59,7 +46,7 @@ class ManufacturerTable(ContactsColumnMixin, NetBoxTable):
|
||||
)
|
||||
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = Manufacturer
|
||||
model = models.Manufacturer
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'devicetype_count', 'inventoryitem_count', 'platform_count', 'description', 'slug',
|
||||
'contacts', 'actions', 'created', 'last_updated',
|
||||
@ -100,15 +87,12 @@ class DeviceTypeTable(NetBoxTable):
|
||||
template_code=DEVICE_WEIGHT,
|
||||
order_by=('_abs_weight', 'weight_unit')
|
||||
)
|
||||
u_height = columns.TemplateColumn(
|
||||
template_code='{{ value|floatformat }}'
|
||||
)
|
||||
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = DeviceType
|
||||
model = models.DeviceType
|
||||
fields = (
|
||||
'pk', 'id', 'model', 'manufacturer', 'slug', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role',
|
||||
'airflow', 'weight', 'comments', 'instance_count', 'tags', 'created', 'last_updated',
|
||||
'airflow', 'weight', 'description', 'comments', 'instance_count', 'tags', 'created', 'last_updated',
|
||||
)
|
||||
default_columns = (
|
||||
'pk', 'model', 'manufacturer', 'part_number', 'u_height', 'is_full_depth', 'instance_count',
|
||||
@ -138,7 +122,7 @@ class ConsolePortTemplateTable(ComponentTemplateTable):
|
||||
)
|
||||
|
||||
class Meta(ComponentTemplateTable.Meta):
|
||||
model = ConsolePortTemplate
|
||||
model = models.ConsolePortTemplate
|
||||
fields = ('pk', 'name', 'label', 'type', 'description', 'actions')
|
||||
empty_text = "None"
|
||||
|
||||
@ -150,7 +134,7 @@ class ConsoleServerPortTemplateTable(ComponentTemplateTable):
|
||||
)
|
||||
|
||||
class Meta(ComponentTemplateTable.Meta):
|
||||
model = ConsoleServerPortTemplate
|
||||
model = models.ConsoleServerPortTemplate
|
||||
fields = ('pk', 'name', 'label', 'type', 'description', 'actions')
|
||||
empty_text = "None"
|
||||
|
||||
@ -162,7 +146,7 @@ class PowerPortTemplateTable(ComponentTemplateTable):
|
||||
)
|
||||
|
||||
class Meta(ComponentTemplateTable.Meta):
|
||||
model = PowerPortTemplate
|
||||
model = models.PowerPortTemplate
|
||||
fields = ('pk', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description', 'actions')
|
||||
empty_text = "None"
|
||||
|
||||
@ -174,7 +158,7 @@ class PowerOutletTemplateTable(ComponentTemplateTable):
|
||||
)
|
||||
|
||||
class Meta(ComponentTemplateTable.Meta):
|
||||
model = PowerOutletTemplate
|
||||
model = models.PowerOutletTemplate
|
||||
fields = ('pk', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description', 'actions')
|
||||
empty_text = "None"
|
||||
|
||||
@ -189,7 +173,7 @@ class InterfaceTemplateTable(ComponentTemplateTable):
|
||||
)
|
||||
|
||||
class Meta(ComponentTemplateTable.Meta):
|
||||
model = InterfaceTemplate
|
||||
model = models.InterfaceTemplate
|
||||
fields = ('pk', 'name', 'label', 'mgmt_only', 'type', 'description', 'poe_mode', 'poe_type', 'actions')
|
||||
empty_text = "None"
|
||||
|
||||
@ -205,7 +189,7 @@ class FrontPortTemplateTable(ComponentTemplateTable):
|
||||
)
|
||||
|
||||
class Meta(ComponentTemplateTable.Meta):
|
||||
model = FrontPortTemplate
|
||||
model = models.FrontPortTemplate
|
||||
fields = ('pk', 'name', 'label', 'type', 'color', 'rear_port', 'rear_port_position', 'description', 'actions')
|
||||
empty_text = "None"
|
||||
|
||||
@ -218,7 +202,7 @@ class RearPortTemplateTable(ComponentTemplateTable):
|
||||
)
|
||||
|
||||
class Meta(ComponentTemplateTable.Meta):
|
||||
model = RearPortTemplate
|
||||
model = models.RearPortTemplate
|
||||
fields = ('pk', 'name', 'label', 'type', 'color', 'positions', 'description', 'actions')
|
||||
empty_text = "None"
|
||||
|
||||
@ -229,7 +213,7 @@ class ModuleBayTemplateTable(ComponentTemplateTable):
|
||||
)
|
||||
|
||||
class Meta(ComponentTemplateTable.Meta):
|
||||
model = ModuleBayTemplate
|
||||
model = models.ModuleBayTemplate
|
||||
fields = ('pk', 'name', 'label', 'position', 'description', 'actions')
|
||||
empty_text = "None"
|
||||
|
||||
@ -240,7 +224,7 @@ class DeviceBayTemplateTable(ComponentTemplateTable):
|
||||
)
|
||||
|
||||
class Meta(ComponentTemplateTable.Meta):
|
||||
model = DeviceBayTemplate
|
||||
model = models.DeviceBayTemplate
|
||||
fields = ('pk', 'name', 'label', 'description', 'actions')
|
||||
empty_text = "None"
|
||||
|
||||
@ -260,7 +244,7 @@ class InventoryItemTemplateTable(ComponentTemplateTable):
|
||||
)
|
||||
|
||||
class Meta(ComponentTemplateTable.Meta):
|
||||
model = InventoryItemTemplate
|
||||
model = models.InventoryItemTemplate
|
||||
fields = (
|
||||
'pk', 'name', 'label', 'parent', 'role', 'manufacturer', 'part_id', 'component', 'description', 'actions',
|
||||
)
|
||||
|
@ -35,7 +35,7 @@ class ModuleTypeTable(NetBoxTable):
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = ModuleType
|
||||
fields = (
|
||||
'pk', 'id', 'model', 'manufacturer', 'part_number', 'weight', 'comments', 'tags',
|
||||
'pk', 'id', 'model', 'manufacturer', 'part_number', 'weight', 'description', 'comments', 'tags',
|
||||
)
|
||||
default_columns = (
|
||||
'pk', 'model', 'manufacturer', 'part_number',
|
||||
@ -64,8 +64,8 @@ class ModuleTable(NetBoxTable):
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = Module
|
||||
fields = (
|
||||
'pk', 'id', 'device', 'module_bay', 'manufacturer', 'module_type', 'serial', 'asset_tag', 'comments',
|
||||
'tags',
|
||||
'pk', 'id', 'device', 'module_bay', 'manufacturer', 'module_type', 'serial', 'asset_tag', 'description',
|
||||
'comments', 'tags',
|
||||
)
|
||||
default_columns = (
|
||||
'pk', 'id', 'device', 'module_bay', 'manufacturer', 'module_type', 'serial', 'asset_tag',
|
||||
|
@ -31,6 +31,7 @@ class PowerPanelTable(ContactsColumnMixin, NetBoxTable):
|
||||
url_params={'power_panel_id': 'pk'},
|
||||
verbose_name='Feeds'
|
||||
)
|
||||
comments = columns.MarkdownColumn()
|
||||
tags = columns.TagColumn(
|
||||
url_name='dcim:powerpanel_list'
|
||||
)
|
||||
@ -38,7 +39,8 @@ class PowerPanelTable(ContactsColumnMixin, NetBoxTable):
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = PowerPanel
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'site', 'location', 'powerfeed_count', 'contacts', 'tags', 'created', 'last_updated',
|
||||
'pk', 'id', 'name', 'site', 'location', 'powerfeed_count', 'contacts', 'description', 'comments', 'tags',
|
||||
'created', 'last_updated',
|
||||
)
|
||||
default_columns = ('pk', 'name', 'site', 'location', 'powerfeed_count')
|
||||
|
||||
@ -77,7 +79,7 @@ class PowerFeedTable(CableTerminationTable):
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'power_panel', 'rack', 'status', 'type', 'supply', 'voltage', 'amperage', 'phase',
|
||||
'max_utilization', 'mark_connected', 'cable', 'cable_color', 'link_peer', 'connection', 'available_power',
|
||||
'comments', 'tags', 'created', 'last_updated',
|
||||
'description', 'comments', 'tags', 'created', 'last_updated',
|
||||
)
|
||||
default_columns = (
|
||||
'pk', 'name', 'power_panel', 'rack', 'status', 'type', 'supply', 'voltage', 'amperage', 'phase', 'cable',
|
||||
|
@ -90,8 +90,8 @@ class RackTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'site', 'location', 'status', 'facility_id', 'tenant', 'tenant_group', 'role', 'serial',
|
||||
'asset_tag', 'type', 'u_height', 'width', 'outer_width', 'outer_depth', 'mounting_depth', 'weight',
|
||||
'comments', 'device_count', 'get_utilization', 'get_power_utilization', 'contacts', 'tags', 'created',
|
||||
'last_updated',
|
||||
'comments', 'device_count', 'get_utilization', 'get_power_utilization', 'description', 'contacts', 'tags',
|
||||
'created', 'last_updated',
|
||||
)
|
||||
default_columns = (
|
||||
'pk', 'name', 'site', 'location', 'status', 'facility_id', 'tenant', 'role', 'u_height', 'device_count',
|
||||
@ -123,6 +123,7 @@ class RackReservationTable(TenancyColumnsMixin, NetBoxTable):
|
||||
orderable=False,
|
||||
verbose_name='Units'
|
||||
)
|
||||
comments = columns.MarkdownColumn()
|
||||
tags = columns.TagColumn(
|
||||
url_name='dcim:rackreservation_list'
|
||||
)
|
||||
@ -130,7 +131,7 @@ class RackReservationTable(TenancyColumnsMixin, NetBoxTable):
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = RackReservation
|
||||
fields = (
|
||||
'pk', 'id', 'reservation', 'site', 'location', 'rack', 'unit_list', 'user', 'created', 'tenant', 'tenant_group', 'description', 'tags',
|
||||
'actions', 'created', 'last_updated',
|
||||
'pk', 'id', 'reservation', 'site', 'location', 'rack', 'unit_list', 'user', 'created', 'tenant',
|
||||
'tenant_group', 'description', 'comments', 'tags', 'actions', 'created', 'last_updated',
|
||||
)
|
||||
default_columns = ('pk', 'reservation', 'site', 'rack', 'unit_list', 'user', 'description')
|
||||
|
@ -1,6 +1,10 @@
|
||||
from decimal import Decimal
|
||||
try:
|
||||
from zoneinfo import ZoneInfo
|
||||
except ImportError:
|
||||
# Python 3.8
|
||||
from backports.zoneinfo import ZoneInfo
|
||||
|
||||
import pytz
|
||||
import yaml
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
@ -12,7 +16,6 @@ from dcim.choices import *
|
||||
from dcim.constants import *
|
||||
from dcim.models import *
|
||||
from ipam.models import ASN, RIR, VLAN, VRF
|
||||
from netbox.api.serializers import GenericObjectSerializer
|
||||
from tenancy.models import Tenant
|
||||
from utilities.testing import ViewTestCases, create_tags, create_test_device, post_data
|
||||
from wireless.models import WirelessLAN
|
||||
@ -153,7 +156,7 @@ class SiteTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||
'tenant': None,
|
||||
'facility': 'Facility X',
|
||||
'asns': [asns[6].pk, asns[7].pk],
|
||||
'time_zone': pytz.UTC,
|
||||
'time_zone': ZoneInfo('UTC'),
|
||||
'description': 'Site description',
|
||||
'physical_address': '742 Evergreen Terrace, Springfield, USA',
|
||||
'shipping_address': '742 Evergreen Terrace, Springfield, USA',
|
||||
@ -182,7 +185,7 @@ class SiteTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||
'region': regions[1].pk,
|
||||
'group': groups[1].pk,
|
||||
'tenant': None,
|
||||
'time_zone': pytz.timezone('US/Eastern'),
|
||||
'time_zone': ZoneInfo('US/Eastern'),
|
||||
'description': 'New description',
|
||||
}
|
||||
|
||||
|
@ -13,6 +13,7 @@ __all__ = [
|
||||
'NestedImageAttachmentSerializer',
|
||||
'NestedJobResultSerializer',
|
||||
'NestedJournalEntrySerializer',
|
||||
'NestedSavedFilterSerializer',
|
||||
'NestedTagSerializer', # Defined in netbox.api.serializers
|
||||
'NestedWebhookSerializer',
|
||||
]
|
||||
@ -58,6 +59,14 @@ class NestedExportTemplateSerializer(WritableNestedSerializer):
|
||||
fields = ['id', 'url', 'display', 'name']
|
||||
|
||||
|
||||
class NestedSavedFilterSerializer(WritableNestedSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='extras-api:savedfilter-detail')
|
||||
|
||||
class Meta:
|
||||
model = models.SavedFilter
|
||||
fields = ['id', 'url', 'display', 'name']
|
||||
|
||||
|
||||
class NestedImageAttachmentSerializer(WritableNestedSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='extras-api:imageattachment-detail')
|
||||
|
||||
|
@ -39,6 +39,7 @@ __all__ = (
|
||||
'ReportDetailSerializer',
|
||||
'ReportSerializer',
|
||||
'ReportInputSerializer',
|
||||
'SavedFilterSerializer',
|
||||
'ScriptDetailSerializer',
|
||||
'ScriptInputSerializer',
|
||||
'ScriptLogMessageSerializer',
|
||||
@ -149,6 +150,25 @@ class ExportTemplateSerializer(ValidatedModelSerializer):
|
||||
]
|
||||
|
||||
|
||||
#
|
||||
# Saved filters
|
||||
#
|
||||
|
||||
class SavedFilterSerializer(ValidatedModelSerializer):
|
||||
url = serializers.HyperlinkedIdentityField(view_name='extras-api:savedfilter-detail')
|
||||
content_types = ContentTypeField(
|
||||
queryset=ContentType.objects.all(),
|
||||
many=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = SavedFilter
|
||||
fields = [
|
||||
'id', 'url', 'display', 'content_types', 'name', 'description', 'user', 'weight',
|
||||
'enabled', 'shared', 'parameters', 'created', 'last_updated',
|
||||
]
|
||||
|
||||
|
||||
#
|
||||
# Tags
|
||||
#
|
||||
|
@ -5,43 +5,19 @@ from . import views
|
||||
router = NetBoxRouter()
|
||||
router.APIRootView = views.ExtrasRootView
|
||||
|
||||
# Webhooks
|
||||
router.register('webhooks', views.WebhookViewSet)
|
||||
|
||||
# Custom fields
|
||||
router.register('custom-fields', views.CustomFieldViewSet)
|
||||
|
||||
# Custom links
|
||||
router.register('custom-links', views.CustomLinkViewSet)
|
||||
|
||||
# Export templates
|
||||
router.register('export-templates', views.ExportTemplateViewSet)
|
||||
|
||||
# Tags
|
||||
router.register('saved-filters', views.SavedFilterViewSet)
|
||||
router.register('tags', views.TagViewSet)
|
||||
|
||||
# Image attachments
|
||||
router.register('image-attachments', views.ImageAttachmentViewSet)
|
||||
|
||||
# Journal entries
|
||||
router.register('journal-entries', views.JournalEntryViewSet)
|
||||
|
||||
# Config contexts
|
||||
router.register('config-contexts', views.ConfigContextViewSet)
|
||||
|
||||
# Reports
|
||||
router.register('reports', views.ReportViewSet, basename='report')
|
||||
|
||||
# Scripts
|
||||
router.register('scripts', views.ScriptViewSet, basename='script')
|
||||
|
||||
# Change logging
|
||||
router.register('object-changes', views.ObjectChangeViewSet)
|
||||
|
||||
# Job Results
|
||||
router.register('job-results', views.JobResultViewSet)
|
||||
|
||||
# ContentTypes
|
||||
router.register('content-types', views.ContentTypeViewSet)
|
||||
|
||||
app_name = 'extras-api'
|
||||
|
@ -1,4 +1,5 @@
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db.models import Q
|
||||
from django.http import Http404
|
||||
from django_rq.queues import get_connection
|
||||
from rest_framework import status
|
||||
@ -98,6 +99,17 @@ class ExportTemplateViewSet(NetBoxModelViewSet):
|
||||
filterset_class = filtersets.ExportTemplateFilterSet
|
||||
|
||||
|
||||
#
|
||||
# Saved filters
|
||||
#
|
||||
|
||||
class SavedFilterViewSet(NetBoxModelViewSet):
|
||||
metadata_class = ContentTypeMetadata
|
||||
queryset = SavedFilter.objects.all()
|
||||
serializer_class = serializers.SavedFilterSerializer
|
||||
filterset_class = filtersets.SavedFilterFilterSet
|
||||
|
||||
|
||||
#
|
||||
# Tags
|
||||
#
|
||||
|
@ -23,6 +23,7 @@ __all__ = (
|
||||
'JournalEntryFilterSet',
|
||||
'LocalConfigContextFilterSet',
|
||||
'ObjectChangeFilterSet',
|
||||
'SavedFilterFilterSet',
|
||||
'TagFilterSet',
|
||||
'WebhookFilterSet',
|
||||
)
|
||||
@ -138,6 +139,55 @@ class ExportTemplateFilterSet(BaseFilterSet):
|
||||
)
|
||||
|
||||
|
||||
class SavedFilterFilterSet(BaseFilterSet):
|
||||
q = django_filters.CharFilter(
|
||||
method='search',
|
||||
label='Search',
|
||||
)
|
||||
content_type_id = MultiValueNumberFilter(
|
||||
field_name='content_types__id'
|
||||
)
|
||||
content_types = ContentTypeFilter()
|
||||
user_id = django_filters.ModelMultipleChoiceFilter(
|
||||
queryset=User.objects.all(),
|
||||
label='User (ID)',
|
||||
)
|
||||
user = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='user__username',
|
||||
queryset=User.objects.all(),
|
||||
to_field_name='username',
|
||||
label='User (name)',
|
||||
)
|
||||
usable = django_filters.BooleanFilter(
|
||||
method='_usable'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = SavedFilter
|
||||
fields = ['id', 'content_types', 'name', 'description', 'enabled', 'shared', 'weight']
|
||||
|
||||
def search(self, queryset, name, value):
|
||||
if not value.strip():
|
||||
return queryset
|
||||
return queryset.filter(
|
||||
Q(name__icontains=value) |
|
||||
Q(description__icontains=value)
|
||||
)
|
||||
|
||||
def _usable(self, queryset, name, value):
|
||||
"""
|
||||
Return only SavedFilters that are both enabled and are shared (or belong to the current user).
|
||||
"""
|
||||
user = self.request.user if self.request else None
|
||||
if not user or user.is_anonymous:
|
||||
if value:
|
||||
return queryset.filter(enabled=True, shared=True)
|
||||
return queryset.filter(Q(enabled=False) | Q(shared=False))
|
||||
if value:
|
||||
return queryset.filter(enabled=True).filter(Q(shared=True) | Q(user=user))
|
||||
return queryset.filter(Q(enabled=False) | Q(Q(shared=False) & ~Q(user=user)))
|
||||
|
||||
|
||||
class ImageAttachmentFilterSet(BaseFilterSet):
|
||||
q = django_filters.CharFilter(
|
||||
method='search',
|
||||
|
@ -2,6 +2,6 @@ from .model_forms import *
|
||||
from .filtersets import *
|
||||
from .bulk_edit import *
|
||||
from .bulk_import import *
|
||||
from .customfields import *
|
||||
from .mixins import *
|
||||
from .config import *
|
||||
from .scripts import *
|
||||
|
@ -1,11 +1,9 @@
|
||||
from django import forms
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
from extras.choices import *
|
||||
from extras.models import *
|
||||
from extras.utils import FeatureQuery
|
||||
from utilities.forms import (
|
||||
add_blank_choice, BulkEditForm, BulkEditNullBooleanSelect, ColorField, ContentTypeChoiceField, StaticSelect,
|
||||
add_blank_choice, BulkEditForm, BulkEditNullBooleanSelect, ColorField, StaticSelect,
|
||||
)
|
||||
|
||||
__all__ = (
|
||||
@ -14,6 +12,7 @@ __all__ = (
|
||||
'CustomLinkBulkEditForm',
|
||||
'ExportTemplateBulkEditForm',
|
||||
'JournalEntryBulkEditForm',
|
||||
'SavedFilterBulkEditForm',
|
||||
'TagBulkEditForm',
|
||||
'WebhookBulkEditForm',
|
||||
)
|
||||
@ -96,6 +95,30 @@ class ExportTemplateBulkEditForm(BulkEditForm):
|
||||
nullable_fields = ('description', 'mime_type', 'file_extension')
|
||||
|
||||
|
||||
class SavedFilterBulkEditForm(BulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=SavedFilter.objects.all(),
|
||||
widget=forms.MultipleHiddenInput
|
||||
)
|
||||
description = forms.CharField(
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
weight = forms.IntegerField(
|
||||
required=False
|
||||
)
|
||||
enabled = forms.NullBooleanField(
|
||||
required=False,
|
||||
widget=BulkEditNullBooleanSelect()
|
||||
)
|
||||
shared = forms.NullBooleanField(
|
||||
required=False,
|
||||
widget=BulkEditNullBooleanSelect()
|
||||
)
|
||||
|
||||
nullable_fields = ('description',)
|
||||
|
||||
|
||||
class WebhookBulkEditForm(BulkEditForm):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=Webhook.objects.all(),
|
||||
|
@ -12,6 +12,7 @@ __all__ = (
|
||||
'CustomFieldCSVForm',
|
||||
'CustomLinkCSVForm',
|
||||
'ExportTemplateCSVForm',
|
||||
'SavedFilterCSVForm',
|
||||
'TagCSVForm',
|
||||
'WebhookCSVForm',
|
||||
)
|
||||
@ -81,6 +82,19 @@ class ExportTemplateCSVForm(CSVModelForm):
|
||||
)
|
||||
|
||||
|
||||
class SavedFilterCSVForm(CSVModelForm):
|
||||
content_types = CSVMultipleContentTypeField(
|
||||
queryset=ContentType.objects.all(),
|
||||
help_text="One or more assigned object types"
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = SavedFilter
|
||||
fields = (
|
||||
'name', 'content_types', 'description', 'weight', 'enabled', 'shared', 'parameters',
|
||||
)
|
||||
|
||||
|
||||
class WebhookCSVForm(CSVModelForm):
|
||||
content_types = CSVMultipleContentTypeField(
|
||||
queryset=ContentType.objects.all(),
|
||||
|
@ -15,6 +15,7 @@ from utilities.forms import (
|
||||
StaticSelect, TagFilterField,
|
||||
)
|
||||
from virtualization.models import Cluster, ClusterGroup, ClusterType
|
||||
from .mixins import SavedFiltersMixin
|
||||
|
||||
__all__ = (
|
||||
'ConfigContextFilterForm',
|
||||
@ -25,14 +26,15 @@ __all__ = (
|
||||
'JournalEntryFilterForm',
|
||||
'LocalConfigContextFilterForm',
|
||||
'ObjectChangeFilterForm',
|
||||
'SavedFilterFilterForm',
|
||||
'TagFilterForm',
|
||||
'WebhookFilterForm',
|
||||
)
|
||||
|
||||
|
||||
class CustomFieldFilterForm(FilterForm):
|
||||
class CustomFieldFilterForm(SavedFiltersMixin, FilterForm):
|
||||
fieldsets = (
|
||||
(None, ('q',)),
|
||||
(None, ('q', 'filter')),
|
||||
('Attributes', ('type', 'content_type_id', 'group_name', 'weight', 'required', 'ui_visibility')),
|
||||
)
|
||||
content_type_id = ContentTypeMultipleChoiceField(
|
||||
@ -66,9 +68,9 @@ class CustomFieldFilterForm(FilterForm):
|
||||
)
|
||||
|
||||
|
||||
class JobResultFilterForm(FilterForm):
|
||||
class JobResultFilterForm(SavedFiltersMixin, FilterForm):
|
||||
fieldsets = (
|
||||
(None, ('q',)),
|
||||
(None, ('q', 'filter')),
|
||||
('Attributes', ('obj_type', 'status')),
|
||||
('Creation', ('created__before', 'created__after', 'completed__before', 'completed__after',
|
||||
'scheduled_time__before', 'scheduled_time__after', 'user')),
|
||||
@ -118,9 +120,9 @@ class JobResultFilterForm(FilterForm):
|
||||
)
|
||||
|
||||
|
||||
class CustomLinkFilterForm(FilterForm):
|
||||
class CustomLinkFilterForm(SavedFiltersMixin, FilterForm):
|
||||
fieldsets = (
|
||||
(None, ('q',)),
|
||||
(None, ('q', 'filter')),
|
||||
('Attributes', ('content_types', 'enabled', 'new_window', 'weight')),
|
||||
)
|
||||
content_types = ContentTypeMultipleChoiceField(
|
||||
@ -145,9 +147,9 @@ class CustomLinkFilterForm(FilterForm):
|
||||
)
|
||||
|
||||
|
||||
class ExportTemplateFilterForm(FilterForm):
|
||||
class ExportTemplateFilterForm(SavedFiltersMixin, FilterForm):
|
||||
fieldsets = (
|
||||
(None, ('q',)),
|
||||
(None, ('q', 'filter')),
|
||||
('Attributes', ('content_types', 'mime_type', 'file_extension', 'as_attachment')),
|
||||
)
|
||||
content_types = ContentTypeMultipleChoiceField(
|
||||
@ -170,9 +172,36 @@ class ExportTemplateFilterForm(FilterForm):
|
||||
)
|
||||
|
||||
|
||||
class WebhookFilterForm(FilterForm):
|
||||
class SavedFilterFilterForm(SavedFiltersMixin, FilterForm):
|
||||
fieldsets = (
|
||||
(None, ('q',)),
|
||||
(None, ('q', 'filter')),
|
||||
('Attributes', ('content_types', 'enabled', 'shared', 'weight')),
|
||||
)
|
||||
content_types = ContentTypeMultipleChoiceField(
|
||||
queryset=ContentType.objects.all(),
|
||||
limit_choices_to=FeatureQuery('export_templates'),
|
||||
required=False
|
||||
)
|
||||
enabled = forms.NullBooleanField(
|
||||
required=False,
|
||||
widget=StaticSelect(
|
||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||
)
|
||||
)
|
||||
shared = forms.NullBooleanField(
|
||||
required=False,
|
||||
widget=StaticSelect(
|
||||
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||
)
|
||||
)
|
||||
weight = forms.IntegerField(
|
||||
required=False
|
||||
)
|
||||
|
||||
|
||||
class WebhookFilterForm(SavedFiltersMixin, FilterForm):
|
||||
fieldsets = (
|
||||
(None, ('q', 'filter')),
|
||||
('Attributes', ('content_type_id', 'http_method', 'enabled')),
|
||||
('Events', ('type_create', 'type_update', 'type_delete')),
|
||||
)
|
||||
@ -213,7 +242,7 @@ class WebhookFilterForm(FilterForm):
|
||||
)
|
||||
|
||||
|
||||
class TagFilterForm(FilterForm):
|
||||
class TagFilterForm(SavedFiltersMixin, FilterForm):
|
||||
model = Tag
|
||||
content_type_id = ContentTypeMultipleChoiceField(
|
||||
queryset=ContentType.objects.filter(FeatureQuery('tags').get_query()),
|
||||
@ -222,9 +251,9 @@ class TagFilterForm(FilterForm):
|
||||
)
|
||||
|
||||
|
||||
class ConfigContextFilterForm(FilterForm):
|
||||
class ConfigContextFilterForm(SavedFiltersMixin, FilterForm):
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag_id')),
|
||||
(None, ('q', 'filter', 'tag_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')),
|
||||
@ -311,7 +340,7 @@ class LocalConfigContextFilterForm(forms.Form):
|
||||
class JournalEntryFilterForm(NetBoxModelFilterSetForm):
|
||||
model = JournalEntry
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
(None, ('q', 'filter', 'tag')),
|
||||
('Creation', ('created_before', 'created_after', 'created_by_id')),
|
||||
('Attributes', ('assigned_object_type_id', 'kind'))
|
||||
)
|
||||
@ -349,10 +378,10 @@ class JournalEntryFilterForm(NetBoxModelFilterSetForm):
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class ObjectChangeFilterForm(FilterForm):
|
||||
class ObjectChangeFilterForm(SavedFiltersMixin, FilterForm):
|
||||
model = ObjectChange
|
||||
fieldsets = (
|
||||
(None, ('q',)),
|
||||
(None, ('q', 'filter')),
|
||||
('Time', ('time_before', 'time_after')),
|
||||
('Attributes', ('action', 'user_id', 'changed_object_type_id')),
|
||||
)
|
||||
|
@ -1,10 +1,13 @@
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django import forms
|
||||
|
||||
from extras.models import *
|
||||
from extras.choices import CustomFieldVisibilityChoices
|
||||
from utilities.forms.fields import DynamicModelMultipleChoiceField
|
||||
|
||||
__all__ = (
|
||||
'CustomFieldsMixin',
|
||||
'SavedFiltersMixin',
|
||||
)
|
||||
|
||||
|
||||
@ -57,3 +60,14 @@ class CustomFieldsMixin:
|
||||
if customfield.group_name not in self.custom_field_groups:
|
||||
self.custom_field_groups[customfield.group_name] = []
|
||||
self.custom_field_groups[customfield.group_name].append(field_name)
|
||||
|
||||
|
||||
class SavedFiltersMixin(forms.Form):
|
||||
filter = DynamicModelMultipleChoiceField(
|
||||
queryset=SavedFilter.objects.all(),
|
||||
required=False,
|
||||
label='Saved Filter',
|
||||
query_params={
|
||||
'usable': True,
|
||||
}
|
||||
)
|
@ -1,5 +1,6 @@
|
||||
from django import forms
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.http import QueryDict
|
||||
|
||||
from dcim.models import DeviceRole, DeviceType, Location, Platform, Region, Site, SiteGroup
|
||||
from extras.choices import *
|
||||
@ -20,6 +21,7 @@ __all__ = (
|
||||
'ExportTemplateForm',
|
||||
'ImageAttachmentForm',
|
||||
'JournalEntryForm',
|
||||
'SavedFilterForm',
|
||||
'TagForm',
|
||||
'WebhookForm',
|
||||
)
|
||||
@ -108,6 +110,34 @@ class ExportTemplateForm(BootstrapMixin, forms.ModelForm):
|
||||
}
|
||||
|
||||
|
||||
class SavedFilterForm(BootstrapMixin, forms.ModelForm):
|
||||
content_types = ContentTypeMultipleChoiceField(
|
||||
queryset=ContentType.objects.all()
|
||||
)
|
||||
|
||||
fieldsets = (
|
||||
('Saved Filter', ('name', 'content_types', 'description', 'weight', 'enabled', 'shared')),
|
||||
('Parameters', ('parameters',)),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = SavedFilter
|
||||
exclude = ('user',)
|
||||
widgets = {
|
||||
'parameters': forms.Textarea(attrs={'class': 'font-monospace'}),
|
||||
}
|
||||
|
||||
def __init__(self, *args, initial=None, **kwargs):
|
||||
|
||||
# Convert any parameters delivered via initial data to a dictionary
|
||||
if initial and 'parameters' in initial:
|
||||
if type(initial['parameters']) is str:
|
||||
# TODO: Make a utility function for this
|
||||
initial['parameters'] = dict(QueryDict(initial['parameters']).lists())
|
||||
|
||||
super().__init__(*args, initial=initial, **kwargs)
|
||||
|
||||
|
||||
class WebhookForm(BootstrapMixin, forms.ModelForm):
|
||||
content_types = ContentTypeMultipleChoiceField(
|
||||
queryset=ContentType.objects.all(),
|
||||
|
@ -20,6 +20,9 @@ class ExtrasQuery(graphene.ObjectType):
|
||||
image_attachment = ObjectField(ImageAttachmentType)
|
||||
image_attachment_list = ObjectListField(ImageAttachmentType)
|
||||
|
||||
saved_filter = ObjectField(SavedFilterType)
|
||||
saved_filter_list = ObjectListField(SavedFilterType)
|
||||
|
||||
journal_entry = ObjectField(JournalEntryType)
|
||||
journal_entry_list = ObjectListField(JournalEntryType)
|
||||
|
||||
|
@ -10,6 +10,7 @@ __all__ = (
|
||||
'ImageAttachmentType',
|
||||
'JournalEntryType',
|
||||
'ObjectChangeType',
|
||||
'SavedFilterType',
|
||||
'TagType',
|
||||
'WebhookType',
|
||||
)
|
||||
@ -71,6 +72,14 @@ class ObjectChangeType(BaseObjectType):
|
||||
filterset_class = filtersets.ObjectChangeFilterSet
|
||||
|
||||
|
||||
class SavedFilterType(ObjectType):
|
||||
|
||||
class Meta:
|
||||
model = models.SavedFilter
|
||||
exclude = ('content_types', )
|
||||
filterset_class = filtersets.SavedFilterFilterSet
|
||||
|
||||
|
||||
class TagType(ObjectType):
|
||||
|
||||
class Meta:
|
||||
|
@ -1,7 +1,7 @@
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
|
||||
from extras.registry import registry
|
||||
from netbox.registry import registry
|
||||
from netbox.search.backends import search_backend
|
||||
|
||||
|
||||
|
36
netbox/extras/migrations/0083_savedfilter.py
Normal file
36
netbox/extras/migrations/0083_savedfilter.py
Normal file
@ -0,0 +1,36 @@
|
||||
# Generated by Django 4.1.1 on 2022-10-27 18:18
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('contenttypes', '0002_remove_content_type_name'),
|
||||
('extras', '0082_exporttemplate_content_types'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='SavedFilter',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
|
||||
('created', models.DateTimeField(auto_now_add=True, null=True)),
|
||||
('last_updated', models.DateTimeField(auto_now=True, null=True)),
|
||||
('name', models.CharField(max_length=100, unique=True)),
|
||||
('description', models.CharField(blank=True, max_length=200)),
|
||||
('weight', models.PositiveSmallIntegerField(default=100)),
|
||||
('enabled', models.BooleanField(default=True)),
|
||||
('shared', models.BooleanField(default=True)),
|
||||
('parameters', models.JSONField()),
|
||||
('content_types', models.ManyToManyField(related_name='saved_filters', to='contenttypes.contenttype')),
|
||||
('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'ordering': ('weight', 'name'),
|
||||
},
|
||||
),
|
||||
]
|
@ -18,6 +18,7 @@ __all__ = (
|
||||
'JournalEntry',
|
||||
'ObjectChange',
|
||||
'Report',
|
||||
'SavedFilter',
|
||||
'Script',
|
||||
'Tag',
|
||||
'TaggedItem',
|
||||
|
@ -8,7 +8,7 @@ from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.cache import cache
|
||||
from django.core.validators import ValidationError
|
||||
from django.db import models
|
||||
from django.http import HttpResponse
|
||||
from django.http import HttpResponse, QueryDict
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
from django.utils.formats import date_format
|
||||
@ -34,6 +34,7 @@ __all__ = (
|
||||
'JobResult',
|
||||
'JournalEntry',
|
||||
'Report',
|
||||
'SavedFilter',
|
||||
'Script',
|
||||
'Webhook',
|
||||
)
|
||||
@ -350,6 +351,69 @@ class ExportTemplate(ExportTemplatesMixin, WebhooksMixin, ChangeLoggedModel):
|
||||
return response
|
||||
|
||||
|
||||
class SavedFilter(CloningMixin, ExportTemplatesMixin, WebhooksMixin, ChangeLoggedModel):
|
||||
"""
|
||||
A set of predefined keyword parameters that can be reused to filter for specific objects.
|
||||
"""
|
||||
content_types = models.ManyToManyField(
|
||||
to=ContentType,
|
||||
related_name='saved_filters',
|
||||
help_text='The object type(s) to which this filter applies.'
|
||||
)
|
||||
name = models.CharField(
|
||||
max_length=100,
|
||||
unique=True
|
||||
)
|
||||
description = models.CharField(
|
||||
max_length=200,
|
||||
blank=True
|
||||
)
|
||||
user = models.ForeignKey(
|
||||
to=User,
|
||||
on_delete=models.SET_NULL,
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
weight = models.PositiveSmallIntegerField(
|
||||
default=100
|
||||
)
|
||||
enabled = models.BooleanField(
|
||||
default=True
|
||||
)
|
||||
shared = models.BooleanField(
|
||||
default=True
|
||||
)
|
||||
parameters = models.JSONField()
|
||||
|
||||
clone_fields = (
|
||||
'enabled', 'weight',
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ('weight', 'name')
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('extras:savedfilter', args=[self.pk])
|
||||
|
||||
def clean(self):
|
||||
super().clean()
|
||||
|
||||
# Verify that `parameters` is a JSON object
|
||||
if type(self.parameters) is not dict:
|
||||
raise ValidationError(
|
||||
{'parameters': 'Filter parameters must be stored as a dictionary of keyword arguments.'}
|
||||
)
|
||||
|
||||
@property
|
||||
def url_params(self):
|
||||
qd = QueryDict(mutable=True)
|
||||
qd.update(self.parameters)
|
||||
return qd.urlencode()
|
||||
|
||||
|
||||
class ImageAttachment(WebhooksMixin, ChangeLoggedModel):
|
||||
"""
|
||||
An uploaded image which is associated with an object.
|
||||
|
@ -1,17 +1,16 @@
|
||||
import collections
|
||||
import inspect
|
||||
from packaging import version
|
||||
|
||||
from django.apps import AppConfig
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.template.loader import get_template
|
||||
from django.utils.module_loading import import_string
|
||||
from packaging import version
|
||||
|
||||
from extras.registry import registry
|
||||
from netbox.navigation import MenuGroup
|
||||
from netbox.registry import registry
|
||||
from netbox.search import register_search
|
||||
from utilities.choices import ButtonColorChoices
|
||||
|
||||
from .navigation import *
|
||||
from .registration import *
|
||||
from .templates import *
|
||||
|
||||
# Initialize plugin registry
|
||||
registry['plugins'] = {
|
||||
@ -145,185 +144,20 @@ class PluginConfig(AppConfig):
|
||||
|
||||
|
||||
#
|
||||
# Template content injection
|
||||
# Utilities
|
||||
#
|
||||
|
||||
class PluginTemplateExtension:
|
||||
def get_plugin_config(plugin_name, parameter, default=None):
|
||||
"""
|
||||
This class is used to register plugin content to be injected into core NetBox templates. It contains methods
|
||||
that are overridden by plugin authors to return template content.
|
||||
Return the value of the specified plugin configuration parameter.
|
||||
|
||||
The `model` attribute on the class defines the which model detail page this class renders content for. It
|
||||
should be set as a string in the form '<app_label>.<model_name>'. render() provides the following context data:
|
||||
|
||||
* object - The object being viewed
|
||||
* request - The current request
|
||||
* settings - Global NetBox settings
|
||||
* config - Plugin-specific configuration parameters
|
||||
Args:
|
||||
plugin_name: The name of the plugin
|
||||
parameter: The name of the configuration parameter
|
||||
default: The value to return if the parameter is not defined (default: None)
|
||||
"""
|
||||
model = None
|
||||
|
||||
def __init__(self, context):
|
||||
self.context = context
|
||||
|
||||
def render(self, template_name, extra_context=None):
|
||||
"""
|
||||
Convenience method for rendering the specified Django template using the default context data. An additional
|
||||
context dictionary may be passed as `extra_context`.
|
||||
"""
|
||||
if extra_context is None:
|
||||
extra_context = {}
|
||||
elif not isinstance(extra_context, dict):
|
||||
raise TypeError("extra_context must be a dictionary")
|
||||
|
||||
return get_template(template_name).render({**self.context, **extra_context})
|
||||
|
||||
def left_page(self):
|
||||
"""
|
||||
Content that will be rendered on the left of the detail page view. Content should be returned as an
|
||||
HTML string. Note that content does not need to be marked as safe because this is automatically handled.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def right_page(self):
|
||||
"""
|
||||
Content that will be rendered on the right of the detail page view. Content should be returned as an
|
||||
HTML string. Note that content does not need to be marked as safe because this is automatically handled.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def full_width_page(self):
|
||||
"""
|
||||
Content that will be rendered within the full width of the detail page view. Content should be returned as an
|
||||
HTML string. Note that content does not need to be marked as safe because this is automatically handled.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def buttons(self):
|
||||
"""
|
||||
Buttons that will be rendered and added to the existing list of buttons on the detail page view. Content
|
||||
should be returned as an HTML string. Note that content does not need to be marked as safe because this is
|
||||
automatically handled.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def register_template_extensions(class_list):
|
||||
"""
|
||||
Register a list of PluginTemplateExtension classes
|
||||
"""
|
||||
# Validation
|
||||
for template_extension in class_list:
|
||||
if not inspect.isclass(template_extension):
|
||||
raise TypeError(f"PluginTemplateExtension class {template_extension} was passed as an instance!")
|
||||
if not issubclass(template_extension, PluginTemplateExtension):
|
||||
raise TypeError(f"{template_extension} is not a subclass of extras.plugins.PluginTemplateExtension!")
|
||||
if template_extension.model is None:
|
||||
raise TypeError(f"PluginTemplateExtension class {template_extension} does not define a valid model!")
|
||||
|
||||
registry['plugins']['template_extensions'][template_extension.model].append(template_extension)
|
||||
|
||||
|
||||
#
|
||||
# Navigation menu links
|
||||
#
|
||||
|
||||
class PluginMenu:
|
||||
icon_class = 'mdi mdi-puzzle'
|
||||
|
||||
def __init__(self, label, groups, icon_class=None):
|
||||
self.label = label
|
||||
self.groups = [
|
||||
MenuGroup(label, items) for label, items in groups
|
||||
]
|
||||
if icon_class is not None:
|
||||
self.icon_class = icon_class
|
||||
|
||||
|
||||
class PluginMenuItem:
|
||||
"""
|
||||
This class represents a navigation menu item. This constitutes primary link and its text, but also allows for
|
||||
specifying additional link buttons that appear to the right of the item in the van menu.
|
||||
|
||||
Links are specified as Django reverse URL strings.
|
||||
Buttons are each specified as a list of PluginMenuButton instances.
|
||||
"""
|
||||
permissions = []
|
||||
buttons = []
|
||||
|
||||
def __init__(self, link, link_text, permissions=None, buttons=None):
|
||||
self.link = link
|
||||
self.link_text = link_text
|
||||
if permissions is not None:
|
||||
if type(permissions) not in (list, tuple):
|
||||
raise TypeError("Permissions must be passed as a tuple or list.")
|
||||
self.permissions = permissions
|
||||
if buttons is not None:
|
||||
if type(buttons) not in (list, tuple):
|
||||
raise TypeError("Buttons must be passed as a tuple or list.")
|
||||
self.buttons = buttons
|
||||
|
||||
|
||||
class PluginMenuButton:
|
||||
"""
|
||||
This class represents a button within a PluginMenuItem. Note that button colors should come from
|
||||
ButtonColorChoices.
|
||||
"""
|
||||
color = ButtonColorChoices.DEFAULT
|
||||
permissions = []
|
||||
|
||||
def __init__(self, link, title, icon_class, color=None, permissions=None):
|
||||
self.link = link
|
||||
self.title = title
|
||||
self.icon_class = icon_class
|
||||
if permissions is not None:
|
||||
if type(permissions) not in (list, tuple):
|
||||
raise TypeError("Permissions must be passed as a tuple or list.")
|
||||
self.permissions = permissions
|
||||
if color is not None:
|
||||
if color not in ButtonColorChoices.values():
|
||||
raise ValueError("Button color must be a choice within ButtonColorChoices.")
|
||||
self.color = color
|
||||
|
||||
|
||||
def register_menu(menu):
|
||||
if not isinstance(menu, PluginMenu):
|
||||
raise TypeError(f"{menu} must be an instance of extras.plugins.PluginMenu")
|
||||
registry['plugins']['menus'].append(menu)
|
||||
|
||||
|
||||
def register_menu_items(section_name, class_list):
|
||||
"""
|
||||
Register a list of PluginMenuItem instances for a given menu section (e.g. plugin name)
|
||||
"""
|
||||
# Validation
|
||||
for menu_link in class_list:
|
||||
if not isinstance(menu_link, PluginMenuItem):
|
||||
raise TypeError(f"{menu_link} must be an instance of extras.plugins.PluginMenuItem")
|
||||
for button in menu_link.buttons:
|
||||
if not isinstance(button, PluginMenuButton):
|
||||
raise TypeError(f"{button} must be an instance of extras.plugins.PluginMenuButton")
|
||||
|
||||
registry['plugins']['menu_items'][section_name] = class_list
|
||||
|
||||
|
||||
#
|
||||
# GraphQL schemas
|
||||
#
|
||||
|
||||
def register_graphql_schema(graphql_schema):
|
||||
"""
|
||||
Register a GraphQL schema class for inclusion in NetBox's GraphQL API.
|
||||
"""
|
||||
registry['plugins']['graphql_schemas'].append(graphql_schema)
|
||||
|
||||
|
||||
#
|
||||
# User preferences
|
||||
#
|
||||
|
||||
def register_user_preferences(plugin_name, preferences):
|
||||
"""
|
||||
Register a list of user preferences defined by a plugin.
|
||||
"""
|
||||
registry['plugins']['preferences'][plugin_name] = preferences
|
||||
try:
|
||||
plugin_config = settings.PLUGINS_CONFIG[plugin_name]
|
||||
return plugin_config.get(parameter, default)
|
||||
except KeyError:
|
||||
raise ImproperlyConfigured(f"Plugin {plugin_name} is not registered.")
|
||||
|
66
netbox/extras/plugins/navigation.py
Normal file
66
netbox/extras/plugins/navigation.py
Normal file
@ -0,0 +1,66 @@
|
||||
from netbox.navigation import MenuGroup
|
||||
from utilities.choices import ButtonColorChoices
|
||||
|
||||
__all__ = (
|
||||
'PluginMenu',
|
||||
'PluginMenuButton',
|
||||
'PluginMenuItem',
|
||||
)
|
||||
|
||||
|
||||
class PluginMenu:
|
||||
icon_class = 'mdi mdi-puzzle'
|
||||
|
||||
def __init__(self, label, groups, icon_class=None):
|
||||
self.label = label
|
||||
self.groups = [
|
||||
MenuGroup(label, items) for label, items in groups
|
||||
]
|
||||
if icon_class is not None:
|
||||
self.icon_class = icon_class
|
||||
|
||||
|
||||
class PluginMenuItem:
|
||||
"""
|
||||
This class represents a navigation menu item. This constitutes primary link and its text, but also allows for
|
||||
specifying additional link buttons that appear to the right of the item in the van menu.
|
||||
|
||||
Links are specified as Django reverse URL strings.
|
||||
Buttons are each specified as a list of PluginMenuButton instances.
|
||||
"""
|
||||
permissions = []
|
||||
buttons = []
|
||||
|
||||
def __init__(self, link, link_text, permissions=None, buttons=None):
|
||||
self.link = link
|
||||
self.link_text = link_text
|
||||
if permissions is not None:
|
||||
if type(permissions) not in (list, tuple):
|
||||
raise TypeError("Permissions must be passed as a tuple or list.")
|
||||
self.permissions = permissions
|
||||
if buttons is not None:
|
||||
if type(buttons) not in (list, tuple):
|
||||
raise TypeError("Buttons must be passed as a tuple or list.")
|
||||
self.buttons = buttons
|
||||
|
||||
|
||||
class PluginMenuButton:
|
||||
"""
|
||||
This class represents a button within a PluginMenuItem. Note that button colors should come from
|
||||
ButtonColorChoices.
|
||||
"""
|
||||
color = ButtonColorChoices.DEFAULT
|
||||
permissions = []
|
||||
|
||||
def __init__(self, link, title, icon_class, color=None, permissions=None):
|
||||
self.link = link
|
||||
self.title = title
|
||||
self.icon_class = icon_class
|
||||
if permissions is not None:
|
||||
if type(permissions) not in (list, tuple):
|
||||
raise TypeError("Permissions must be passed as a tuple or list.")
|
||||
self.permissions = permissions
|
||||
if color is not None:
|
||||
if color not in ButtonColorChoices.values():
|
||||
raise ValueError("Button color must be a choice within ButtonColorChoices.")
|
||||
self.color = color
|
64
netbox/extras/plugins/registration.py
Normal file
64
netbox/extras/plugins/registration.py
Normal file
@ -0,0 +1,64 @@
|
||||
import inspect
|
||||
|
||||
from netbox.registry import registry
|
||||
from .navigation import PluginMenu, PluginMenuButton, PluginMenuItem
|
||||
from .templates import PluginTemplateExtension
|
||||
|
||||
__all__ = (
|
||||
'register_graphql_schema',
|
||||
'register_menu',
|
||||
'register_menu_items',
|
||||
'register_template_extensions',
|
||||
'register_user_preferences',
|
||||
)
|
||||
|
||||
|
||||
def register_template_extensions(class_list):
|
||||
"""
|
||||
Register a list of PluginTemplateExtension classes
|
||||
"""
|
||||
# Validation
|
||||
for template_extension in class_list:
|
||||
if not inspect.isclass(template_extension):
|
||||
raise TypeError(f"PluginTemplateExtension class {template_extension} was passed as an instance!")
|
||||
if not issubclass(template_extension, PluginTemplateExtension):
|
||||
raise TypeError(f"{template_extension} is not a subclass of extras.plugins.PluginTemplateExtension!")
|
||||
if template_extension.model is None:
|
||||
raise TypeError(f"PluginTemplateExtension class {template_extension} does not define a valid model!")
|
||||
|
||||
registry['plugins']['template_extensions'][template_extension.model].append(template_extension)
|
||||
|
||||
|
||||
def register_menu(menu):
|
||||
if not isinstance(menu, PluginMenu):
|
||||
raise TypeError(f"{menu} must be an instance of extras.plugins.PluginMenu")
|
||||
registry['plugins']['menus'].append(menu)
|
||||
|
||||
|
||||
def register_menu_items(section_name, class_list):
|
||||
"""
|
||||
Register a list of PluginMenuItem instances for a given menu section (e.g. plugin name)
|
||||
"""
|
||||
# Validation
|
||||
for menu_link in class_list:
|
||||
if not isinstance(menu_link, PluginMenuItem):
|
||||
raise TypeError(f"{menu_link} must be an instance of extras.plugins.PluginMenuItem")
|
||||
for button in menu_link.buttons:
|
||||
if not isinstance(button, PluginMenuButton):
|
||||
raise TypeError(f"{button} must be an instance of extras.plugins.PluginMenuButton")
|
||||
|
||||
registry['plugins']['menu_items'][section_name] = class_list
|
||||
|
||||
|
||||
def register_graphql_schema(graphql_schema):
|
||||
"""
|
||||
Register a GraphQL schema class for inclusion in NetBox's GraphQL API.
|
||||
"""
|
||||
registry['plugins']['graphql_schemas'].append(graphql_schema)
|
||||
|
||||
|
||||
def register_user_preferences(plugin_name, preferences):
|
||||
"""
|
||||
Register a list of user preferences defined by a plugin.
|
||||
"""
|
||||
registry['plugins']['preferences'][plugin_name] = preferences
|
65
netbox/extras/plugins/templates.py
Normal file
65
netbox/extras/plugins/templates.py
Normal file
@ -0,0 +1,65 @@
|
||||
from django.template.loader import get_template
|
||||
|
||||
__all__ = (
|
||||
'PluginTemplateExtension',
|
||||
)
|
||||
|
||||
|
||||
class PluginTemplateExtension:
|
||||
"""
|
||||
This class is used to register plugin content to be injected into core NetBox templates. It contains methods
|
||||
that are overridden by plugin authors to return template content.
|
||||
|
||||
The `model` attribute on the class defines the which model detail page this class renders content for. It
|
||||
should be set as a string in the form '<app_label>.<model_name>'. render() provides the following context data:
|
||||
|
||||
* object - The object being viewed
|
||||
* request - The current request
|
||||
* settings - Global NetBox settings
|
||||
* config - Plugin-specific configuration parameters
|
||||
"""
|
||||
model = None
|
||||
|
||||
def __init__(self, context):
|
||||
self.context = context
|
||||
|
||||
def render(self, template_name, extra_context=None):
|
||||
"""
|
||||
Convenience method for rendering the specified Django template using the default context data. An additional
|
||||
context dictionary may be passed as `extra_context`.
|
||||
"""
|
||||
if extra_context is None:
|
||||
extra_context = {}
|
||||
elif not isinstance(extra_context, dict):
|
||||
raise TypeError("extra_context must be a dictionary")
|
||||
|
||||
return get_template(template_name).render({**self.context, **extra_context})
|
||||
|
||||
def left_page(self):
|
||||
"""
|
||||
Content that will be rendered on the left of the detail page view. Content should be returned as an
|
||||
HTML string. Note that content does not need to be marked as safe because this is automatically handled.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def right_page(self):
|
||||
"""
|
||||
Content that will be rendered on the right of the detail page view. Content should be returned as an
|
||||
HTML string. Note that content does not need to be marked as safe because this is automatically handled.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def full_width_page(self):
|
||||
"""
|
||||
Content that will be rendered within the full width of the detail page view. Content should be returned as an
|
||||
HTML string. Note that content does not need to be marked as safe because this is automatically handled.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def buttons(self):
|
||||
"""
|
||||
Buttons that will be rendered and added to the existing list of buttons on the detail page view. Content
|
||||
should be returned as an HTML string. Note that content does not need to be marked as safe because this is
|
||||
automatically handled.
|
||||
"""
|
||||
raise NotImplementedError
|
@ -13,16 +13,13 @@ __all__ = (
|
||||
'ExportTemplateTable',
|
||||
'JournalEntryTable',
|
||||
'ObjectChangeTable',
|
||||
'SavedFilterTable',
|
||||
'TaggedItemTable',
|
||||
'TagTable',
|
||||
'WebhookTable',
|
||||
)
|
||||
|
||||
|
||||
#
|
||||
# Custom fields
|
||||
#
|
||||
|
||||
class CustomFieldTable(NetBoxTable):
|
||||
name = tables.Column(
|
||||
linkify=True
|
||||
@ -40,10 +37,6 @@ class CustomFieldTable(NetBoxTable):
|
||||
default_columns = ('pk', 'name', 'content_types', 'label', 'group_name', 'type', 'required', 'description')
|
||||
|
||||
|
||||
#
|
||||
# Custom fields
|
||||
#
|
||||
|
||||
class JobResultTable(NetBoxTable):
|
||||
name = tables.Column(
|
||||
linkify=True
|
||||
@ -61,10 +54,6 @@ class JobResultTable(NetBoxTable):
|
||||
default_columns = ('pk', 'id', 'name', 'obj_type', 'status', 'created', 'completed', 'user',)
|
||||
|
||||
|
||||
#
|
||||
# Custom links
|
||||
#
|
||||
|
||||
class CustomLinkTable(NetBoxTable):
|
||||
name = tables.Column(
|
||||
linkify=True
|
||||
@ -82,10 +71,6 @@ class CustomLinkTable(NetBoxTable):
|
||||
default_columns = ('pk', 'name', 'content_types', 'enabled', 'group_name', 'button_class', 'new_window')
|
||||
|
||||
|
||||
#
|
||||
# Export templates
|
||||
#
|
||||
|
||||
class ExportTemplateTable(NetBoxTable):
|
||||
name = tables.Column(
|
||||
linkify=True
|
||||
@ -104,9 +89,24 @@ class ExportTemplateTable(NetBoxTable):
|
||||
)
|
||||
|
||||
|
||||
#
|
||||
# Webhooks
|
||||
#
|
||||
class SavedFilterTable(NetBoxTable):
|
||||
name = tables.Column(
|
||||
linkify=True
|
||||
)
|
||||
content_types = columns.ContentTypesColumn()
|
||||
enabled = columns.BooleanColumn()
|
||||
shared = columns.BooleanColumn()
|
||||
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = SavedFilter
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'content_types', 'description', 'user', 'weight', 'enabled', 'shared',
|
||||
'created', 'last_updated',
|
||||
)
|
||||
default_columns = (
|
||||
'pk', 'name', 'content_types', 'user', 'description', 'enabled', 'shared',
|
||||
)
|
||||
|
||||
|
||||
class WebhookTable(NetBoxTable):
|
||||
name = tables.Column(
|
||||
@ -139,10 +139,6 @@ class WebhookTable(NetBoxTable):
|
||||
)
|
||||
|
||||
|
||||
#
|
||||
# Tags
|
||||
#
|
||||
|
||||
class TagTable(NetBoxTable):
|
||||
name = tables.Column(
|
||||
linkify=True
|
||||
|
@ -3,7 +3,7 @@ from django.conf import settings
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
from extras.plugins import PluginTemplateExtension
|
||||
from extras.registry import registry
|
||||
from netbox.registry import registry
|
||||
|
||||
register = template_.Library()
|
||||
|
||||
|
@ -3,7 +3,6 @@ from unittest import skipIf
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.test import override_settings
|
||||
from django.urls import reverse
|
||||
from django.utils.timezone import make_aware
|
||||
from django_rq.queues import get_connection
|
||||
@ -17,7 +16,6 @@ from extras.reports import Report
|
||||
from extras.scripts import BooleanVar, IntegerVar, Script, StringVar
|
||||
from utilities.testing import APITestCase, APIViewTestCases
|
||||
|
||||
|
||||
rq_worker_running = Worker.count(get_connection('default'))
|
||||
|
||||
|
||||
@ -192,6 +190,73 @@ class CustomLinkTest(APIViewTestCases.APIViewTestCase):
|
||||
custom_link.content_types.set([site_ct])
|
||||
|
||||
|
||||
class SavedFilterTest(APIViewTestCases.APIViewTestCase):
|
||||
model = SavedFilter
|
||||
brief_fields = ['display', 'id', 'name', 'url']
|
||||
create_data = [
|
||||
{
|
||||
'content_types': ['dcim.site'],
|
||||
'name': 'Saved Filter 4',
|
||||
'weight': 100,
|
||||
'enabled': True,
|
||||
'shared': True,
|
||||
'parameters': {'status': ['active']},
|
||||
},
|
||||
{
|
||||
'content_types': ['dcim.site'],
|
||||
'name': 'Saved Filter 5',
|
||||
'weight': 200,
|
||||
'enabled': True,
|
||||
'shared': True,
|
||||
'parameters': {'status': ['planned']},
|
||||
},
|
||||
{
|
||||
'content_types': ['dcim.site'],
|
||||
'name': 'Saved Filter 6',
|
||||
'weight': 300,
|
||||
'enabled': True,
|
||||
'shared': True,
|
||||
'parameters': {'status': ['retired']},
|
||||
},
|
||||
]
|
||||
bulk_update_data = {
|
||||
'weight': 1000,
|
||||
'enabled': False,
|
||||
'shared': False,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
site_ct = ContentType.objects.get_for_model(Site)
|
||||
|
||||
saved_filters = (
|
||||
SavedFilter(
|
||||
name='Saved Filter 1',
|
||||
weight=100,
|
||||
enabled=True,
|
||||
shared=True,
|
||||
parameters={'status': ['active']}
|
||||
),
|
||||
SavedFilter(
|
||||
name='Saved Filter 2',
|
||||
weight=200,
|
||||
enabled=True,
|
||||
shared=True,
|
||||
parameters={'status': ['planned']}
|
||||
),
|
||||
SavedFilter(
|
||||
name='Saved Filter 3',
|
||||
weight=300,
|
||||
enabled=True,
|
||||
shared=True,
|
||||
parameters={'status': ['retired']}
|
||||
),
|
||||
)
|
||||
SavedFilter.objects.bulk_create(saved_filters)
|
||||
for i, savedfilter in enumerate(saved_filters):
|
||||
savedfilter.content_types.set([site_ct])
|
||||
|
||||
|
||||
class ExportTemplateTest(APIViewTestCases.APIViewTestCase):
|
||||
model = ExportTemplate
|
||||
brief_fields = ['display', 'id', 'name', 'url']
|
||||
|
@ -222,6 +222,92 @@ class CustomLinkTestCase(TestCase, BaseFilterSetTests):
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||
|
||||
|
||||
class SavedFilterTestCase(TestCase, BaseFilterSetTests):
|
||||
queryset = SavedFilter.objects.all()
|
||||
filterset = SavedFilterFilterSet
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
content_types = ContentType.objects.filter(model__in=['site', 'rack', 'device'])
|
||||
|
||||
users = (
|
||||
User(username='User 1'),
|
||||
User(username='User 2'),
|
||||
User(username='User 3'),
|
||||
)
|
||||
User.objects.bulk_create(users)
|
||||
|
||||
saved_filters = (
|
||||
SavedFilter(
|
||||
name='Saved Filter 1',
|
||||
user=users[0],
|
||||
weight=100,
|
||||
enabled=True,
|
||||
shared=True,
|
||||
parameters={'status': ['active']}
|
||||
),
|
||||
SavedFilter(
|
||||
name='Saved Filter 2',
|
||||
user=users[1],
|
||||
weight=200,
|
||||
enabled=True,
|
||||
shared=True,
|
||||
parameters={'status': ['planned']}
|
||||
),
|
||||
SavedFilter(
|
||||
name='Saved Filter 3',
|
||||
user=users[2],
|
||||
weight=300,
|
||||
enabled=False,
|
||||
shared=False,
|
||||
parameters={'status': ['retired']}
|
||||
),
|
||||
)
|
||||
SavedFilter.objects.bulk_create(saved_filters)
|
||||
for i, savedfilter in enumerate(saved_filters):
|
||||
savedfilter.content_types.set([content_types[i]])
|
||||
|
||||
def test_name(self):
|
||||
params = {'name': ['Saved Filter 1', 'Saved Filter 2']}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_content_types(self):
|
||||
params = {'content_types': 'dcim.site'}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||
params = {'content_type_id': [ContentType.objects.get_for_model(Site).pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||
|
||||
def test_user(self):
|
||||
users = User.objects.filter(username__startswith='User')
|
||||
params = {'user': [users[0].username, users[1].username]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
params = {'user_id': [users[0].pk, users[1].pk]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_weight(self):
|
||||
params = {'weight': [100, 200]}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
|
||||
def test_enabled(self):
|
||||
params = {'enabled': True}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
params = {'enabled': False}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||
|
||||
def test_shared(self):
|
||||
params = {'enabled': True}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
params = {'enabled': False}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||
|
||||
def test_usable(self):
|
||||
# Filtering for an anonymous user
|
||||
params = {'usable': True}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||
params = {'usable': False}
|
||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||
|
||||
|
||||
class ExportTemplateTestCase(TestCase, BaseFilterSetTests):
|
||||
queryset = ExportTemplate.objects.all()
|
||||
filterset = ExportTemplateFilterSet
|
||||
|
@ -5,10 +5,10 @@ from django.core.exceptions import ImproperlyConfigured
|
||||
from django.test import Client, TestCase, override_settings
|
||||
from django.urls import reverse
|
||||
|
||||
from extras.plugins import PluginMenu
|
||||
from extras.registry import registry
|
||||
from extras.plugins import PluginMenu, get_plugin_config
|
||||
from extras.tests.dummy_plugin import config as dummy_config
|
||||
from netbox.graphql.schema import Query
|
||||
from netbox.registry import registry
|
||||
|
||||
|
||||
@skipIf('extras.tests.dummy_plugin' not in settings.PLUGINS, "dummy_plugin not in settings.PLUGINS")
|
||||
@ -173,3 +173,13 @@ class PluginTest(TestCase):
|
||||
|
||||
self.assertIn(DummyQuery, registry['plugins']['graphql_schemas'])
|
||||
self.assertTrue(issubclass(Query, DummyQuery))
|
||||
|
||||
@override_settings(PLUGINS_CONFIG={'extras.tests.dummy_plugin': {'foo': 123}})
|
||||
def test_get_plugin_config(self):
|
||||
"""
|
||||
Validate that get_plugin_config() returns config parameters correctly.
|
||||
"""
|
||||
plugin = 'extras.tests.dummy_plugin'
|
||||
self.assertEqual(get_plugin_config(plugin, 'foo'), 123)
|
||||
self.assertEqual(get_plugin_config(plugin, 'bar'), None)
|
||||
self.assertEqual(get_plugin_config(plugin, 'bar', default=456), 456)
|
||||
|
@ -107,6 +107,58 @@ class CustomLinkTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||
}
|
||||
|
||||
|
||||
class SavedFilterTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||
model = SavedFilter
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
site_ct = ContentType.objects.get_for_model(Site)
|
||||
|
||||
users = (
|
||||
User(username='User 1'),
|
||||
User(username='User 2'),
|
||||
User(username='User 3'),
|
||||
)
|
||||
User.objects.bulk_create(users)
|
||||
|
||||
saved_filters = (
|
||||
SavedFilter(name='Saved Filter 1', user=users[0], weight=100, parameters={'status': ['active']}),
|
||||
SavedFilter(name='Saved Filter 2', user=users[1], weight=200, parameters={'status': ['planned']}),
|
||||
SavedFilter(name='Saved Filter 3', user=users[2], weight=300, parameters={'status': ['retired']}),
|
||||
)
|
||||
SavedFilter.objects.bulk_create(saved_filters)
|
||||
for i, savedfilter in enumerate(saved_filters):
|
||||
savedfilter.content_types.set([site_ct])
|
||||
|
||||
cls.form_data = {
|
||||
'name': 'Saved Filter X',
|
||||
'content_types': [site_ct.pk],
|
||||
'description': 'Foo',
|
||||
'weight': 1000,
|
||||
'enabled': True,
|
||||
'shared': True,
|
||||
'parameters': '{"foo": 123}',
|
||||
}
|
||||
|
||||
cls.csv_data = (
|
||||
'name,content_types,weight,enabled,shared,parameters',
|
||||
'Saved Filter 4,dcim.device,400,True,True,{"foo": "a"}',
|
||||
'Saved Filter 5,dcim.device,500,True,True,{"foo": "b"}',
|
||||
'Saved Filter 6,dcim.device,600,True,True,{"foo": "c"}',
|
||||
)
|
||||
|
||||
cls.csv_update_data = (
|
||||
"id,name",
|
||||
f"{saved_filters[0].pk},Saved Filter 7",
|
||||
f"{saved_filters[1].pk},Saved Filter 8",
|
||||
f"{saved_filters[2].pk},Saved Filter 9",
|
||||
)
|
||||
|
||||
cls.bulk_edit_data = {
|
||||
'weight': 999,
|
||||
}
|
||||
|
||||
|
||||
class ExportTemplateTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
||||
model = ExportTemplate
|
||||
|
||||
|
@ -31,6 +31,14 @@ urlpatterns = [
|
||||
path('export-templates/delete/', views.ExportTemplateBulkDeleteView.as_view(), name='exporttemplate_bulk_delete'),
|
||||
path('export-templates/<int:pk>/', include(get_model_urls('extras', 'exporttemplate'))),
|
||||
|
||||
# Saved filters
|
||||
path('saved-filters/', views.SavedFilterListView.as_view(), name='savedfilter_list'),
|
||||
path('saved-filters/add/', views.SavedFilterEditView.as_view(), name='savedfilter_add'),
|
||||
path('saved-filters/import/', views.SavedFilterBulkImportView.as_view(), name='savedfilter_import'),
|
||||
path('saved-filters/edit/', views.SavedFilterBulkEditView.as_view(), name='savedfilter_bulk_edit'),
|
||||
path('saved-filters/delete/', views.SavedFilterBulkDeleteView.as_view(), name='savedfilter_bulk_delete'),
|
||||
path('saved-filters/<int:pk>/', include(get_model_urls('extras', 'savedfilter'))),
|
||||
|
||||
# Webhooks
|
||||
path('webhooks/', views.WebhookListView.as_view(), name='webhook_list'),
|
||||
path('webhooks/add/', views.WebhookEditView.as_view(), name='webhook_add'),
|
||||
|
@ -3,7 +3,7 @@ from django.utils.deconstruct import deconstructible
|
||||
from taggit.managers import _TaggableManager
|
||||
|
||||
from extras.constants import EXTRAS_FEATURES
|
||||
from extras.registry import registry
|
||||
from netbox.registry import registry
|
||||
|
||||
|
||||
def is_taggable(obj):
|
||||
|
@ -9,7 +9,6 @@ from django_rq.queues import get_connection
|
||||
from rq import Worker
|
||||
|
||||
from netbox.views import generic
|
||||
from utilities.forms import ConfirmationForm
|
||||
from utilities.htmx import is_htmx
|
||||
from utilities.utils import copy_safe_request, count_related, get_viewname, normalize_querydict, shallow_compare_dict
|
||||
from utilities.views import ContentTypePermissionRequiredMixin, register_model_view
|
||||
@ -159,6 +158,74 @@ class ExportTemplateBulkDeleteView(generic.BulkDeleteView):
|
||||
table = tables.ExportTemplateTable
|
||||
|
||||
|
||||
#
|
||||
# Saved filters
|
||||
#
|
||||
|
||||
class SavedFilterMixin:
|
||||
|
||||
def get_queryset(self, request):
|
||||
"""
|
||||
Return only shared SavedFilters, or those owned by the current user, unless
|
||||
this is a superuser.
|
||||
"""
|
||||
queryset = SavedFilter.objects.all()
|
||||
user = request.user
|
||||
if user.is_superuser:
|
||||
return queryset
|
||||
if user.is_anonymous:
|
||||
return queryset.filter(shared=True)
|
||||
return queryset.filter(
|
||||
Q(shared=True) | Q(user=user)
|
||||
)
|
||||
|
||||
|
||||
class SavedFilterListView(SavedFilterMixin, generic.ObjectListView):
|
||||
filterset = filtersets.SavedFilterFilterSet
|
||||
filterset_form = forms.SavedFilterFilterForm
|
||||
table = tables.SavedFilterTable
|
||||
|
||||
|
||||
@register_model_view(SavedFilter)
|
||||
class SavedFilterView(SavedFilterMixin, generic.ObjectView):
|
||||
queryset = SavedFilter.objects.all()
|
||||
|
||||
|
||||
@register_model_view(SavedFilter, 'edit')
|
||||
class SavedFilterEditView(SavedFilterMixin, generic.ObjectEditView):
|
||||
queryset = SavedFilter.objects.all()
|
||||
form = forms.SavedFilterForm
|
||||
|
||||
def alter_object(self, obj, request, url_args, url_kwargs):
|
||||
if not obj.pk:
|
||||
obj.user = request.user
|
||||
return obj
|
||||
|
||||
|
||||
@register_model_view(SavedFilter, 'delete')
|
||||
class SavedFilterDeleteView(SavedFilterMixin, generic.ObjectDeleteView):
|
||||
queryset = SavedFilter.objects.all()
|
||||
|
||||
|
||||
class SavedFilterBulkImportView(SavedFilterMixin, generic.BulkImportView):
|
||||
queryset = SavedFilter.objects.all()
|
||||
model_form = forms.SavedFilterCSVForm
|
||||
table = tables.SavedFilterTable
|
||||
|
||||
|
||||
class SavedFilterBulkEditView(SavedFilterMixin, generic.BulkEditView):
|
||||
queryset = SavedFilter.objects.all()
|
||||
filterset = filtersets.SavedFilterFilterSet
|
||||
table = tables.SavedFilterTable
|
||||
form = forms.SavedFilterBulkEditForm
|
||||
|
||||
|
||||
class SavedFilterBulkDeleteView(SavedFilterMixin, generic.BulkDeleteView):
|
||||
queryset = SavedFilter.objects.all()
|
||||
filterset = filtersets.SavedFilterFilterSet
|
||||
table = tables.SavedFilterTable
|
||||
|
||||
|
||||
#
|
||||
# Webhooks
|
||||
#
|
||||
|
@ -5,11 +5,11 @@ from django.contrib.contenttypes.models import ContentType
|
||||
from django.utils import timezone
|
||||
from django_rq import get_queue
|
||||
|
||||
from netbox.registry import registry
|
||||
from utilities.api import get_serializer_for_model
|
||||
from utilities.utils import serialize_object
|
||||
from .choices import *
|
||||
from .models import Webhook
|
||||
from .registry import registry
|
||||
|
||||
|
||||
def serialize_for_webhook(instance):
|
||||
|
@ -31,8 +31,8 @@ class ASNSerializer(NetBoxModelSerializer):
|
||||
class Meta:
|
||||
model = ASN
|
||||
fields = [
|
||||
'id', 'url', 'display', 'asn', 'rir', 'tenant', 'description', 'site_count', 'provider_count', 'tags',
|
||||
'custom_fields', 'created', 'last_updated',
|
||||
'id', 'url', 'display', 'asn', 'rir', 'tenant', 'description', 'comments', 'tags', 'custom_fields',
|
||||
'created', 'last_updated', 'site_count', 'provider_count',
|
||||
]
|
||||
|
||||
|
||||
@ -61,8 +61,9 @@ class VRFSerializer(NetBoxModelSerializer):
|
||||
class Meta:
|
||||
model = VRF
|
||||
fields = [
|
||||
'id', 'url', 'display', 'name', 'rd', 'tenant', 'enforce_unique', 'description', 'import_targets',
|
||||
'export_targets', 'tags', 'custom_fields', 'created', 'last_updated', 'ipaddress_count', 'prefix_count',
|
||||
'id', 'url', 'display', 'name', 'rd', 'tenant', 'enforce_unique', 'description', 'comments',
|
||||
'import_targets', 'export_targets', 'tags', 'custom_fields', 'created', 'last_updated', 'ipaddress_count',
|
||||
'prefix_count',
|
||||
]
|
||||
|
||||
|
||||
@ -77,7 +78,8 @@ class RouteTargetSerializer(NetBoxModelSerializer):
|
||||
class Meta:
|
||||
model = RouteTarget
|
||||
fields = [
|
||||
'id', 'url', 'display', 'name', 'tenant', 'description', 'tags', 'custom_fields', 'created', 'last_updated',
|
||||
'id', 'url', 'display', 'name', 'tenant', 'description', 'comments', 'tags', 'custom_fields', 'created',
|
||||
'last_updated',
|
||||
]
|
||||
|
||||
|
||||
@ -106,8 +108,8 @@ class AggregateSerializer(NetBoxModelSerializer):
|
||||
class Meta:
|
||||
model = Aggregate
|
||||
fields = [
|
||||
'id', 'url', 'display', 'family', 'prefix', 'rir', 'tenant', 'date_added', 'description', 'tags',
|
||||
'custom_fields', 'created', 'last_updated',
|
||||
'id', 'url', 'display', 'family', 'prefix', 'rir', 'tenant', 'date_added', 'description', 'comments',
|
||||
'tags', 'custom_fields', 'created', 'last_updated',
|
||||
]
|
||||
read_only_fields = ['family']
|
||||
|
||||
@ -123,8 +125,8 @@ class FHRPGroupSerializer(NetBoxModelSerializer):
|
||||
class Meta:
|
||||
model = FHRPGroup
|
||||
fields = [
|
||||
'id', 'name', 'url', 'display', 'protocol', 'group_id', 'auth_type', 'auth_key', 'description', 'ip_addresses',
|
||||
'tags', 'custom_fields', 'created', 'last_updated',
|
||||
'id', 'name', 'url', 'display', 'protocol', 'group_id', 'auth_type', 'auth_key', 'description', 'comments',
|
||||
'tags', 'custom_fields', 'created', 'last_updated', 'ip_addresses',
|
||||
]
|
||||
|
||||
|
||||
@ -215,7 +217,7 @@ class VLANSerializer(NetBoxModelSerializer):
|
||||
model = VLAN
|
||||
fields = [
|
||||
'id', 'url', 'display', 'site', 'group', 'vid', 'name', 'tenant', 'status', 'role', 'description',
|
||||
'l2vpn_termination', 'tags', 'custom_fields', 'created', 'last_updated', 'prefix_count',
|
||||
'comments', 'l2vpn_termination', 'tags', 'custom_fields', 'created', 'last_updated', 'prefix_count',
|
||||
]
|
||||
|
||||
|
||||
@ -273,7 +275,8 @@ class PrefixSerializer(NetBoxModelSerializer):
|
||||
model = Prefix
|
||||
fields = [
|
||||
'id', 'url', 'display', 'family', 'prefix', 'site', 'vrf', 'tenant', 'vlan', 'status', 'role', 'is_pool',
|
||||
'mark_utilized', 'description', 'tags', 'custom_fields', 'created', 'last_updated', 'children', '_depth',
|
||||
'mark_utilized', 'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'children',
|
||||
'_depth',
|
||||
]
|
||||
read_only_fields = ['family']
|
||||
|
||||
@ -342,7 +345,7 @@ class IPRangeSerializer(NetBoxModelSerializer):
|
||||
model = IPRange
|
||||
fields = [
|
||||
'id', 'url', 'display', 'family', 'start_address', 'end_address', 'size', 'vrf', 'tenant', 'status', 'role',
|
||||
'description', 'tags', 'custom_fields', 'created', 'last_updated', 'children',
|
||||
'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'children',
|
||||
]
|
||||
read_only_fields = ['family']
|
||||
|
||||
@ -371,8 +374,8 @@ class IPAddressSerializer(NetBoxModelSerializer):
|
||||
model = IPAddress
|
||||
fields = [
|
||||
'id', 'url', 'display', 'family', 'address', 'vrf', 'tenant', 'status', 'role', 'assigned_object_type',
|
||||
'assigned_object_id', 'assigned_object', 'nat_inside', 'nat_outside', 'dns_name', 'description', 'tags',
|
||||
'custom_fields', 'created', 'last_updated',
|
||||
'assigned_object_id', 'assigned_object', 'nat_inside', 'nat_outside', 'dns_name', 'description', 'comments',
|
||||
'tags', 'custom_fields', 'created', 'last_updated',
|
||||
]
|
||||
|
||||
@swagger_serializer_method(serializer_or_field=serializers.JSONField)
|
||||
@ -415,8 +418,8 @@ class ServiceTemplateSerializer(NetBoxModelSerializer):
|
||||
class Meta:
|
||||
model = ServiceTemplate
|
||||
fields = [
|
||||
'id', 'url', 'display', 'name', 'ports', 'protocol', 'description', 'tags', 'custom_fields', 'created',
|
||||
'last_updated',
|
||||
'id', 'url', 'display', 'name', 'ports', 'protocol', 'description', 'comments', 'tags', 'custom_fields',
|
||||
'created', 'last_updated',
|
||||
]
|
||||
|
||||
|
||||
@ -436,7 +439,7 @@ class ServiceSerializer(NetBoxModelSerializer):
|
||||
model = Service
|
||||
fields = [
|
||||
'id', 'url', 'display', 'device', 'virtual_machine', 'name', 'ports', 'protocol', 'ipaddresses',
|
||||
'description', 'tags', 'custom_fields', 'created', 'last_updated',
|
||||
'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
|
||||
]
|
||||
|
||||
#
|
||||
@ -465,7 +468,7 @@ class L2VPNSerializer(NetBoxModelSerializer):
|
||||
model = L2VPN
|
||||
fields = [
|
||||
'id', 'url', 'display', 'identifier', 'name', 'slug', 'type', 'import_targets', 'export_targets',
|
||||
'description', 'tenant', 'tags', 'custom_fields', 'created', 'last_updated'
|
||||
'description', 'comments', 'tenant', 'tags', 'custom_fields', 'created', 'last_updated'
|
||||
]
|
||||
|
||||
|
||||
|
@ -8,8 +8,8 @@ from ipam.models import ASN
|
||||
from netbox.forms import NetBoxModelBulkEditForm
|
||||
from tenancy.models import Tenant
|
||||
from utilities.forms import (
|
||||
add_blank_choice, BulkEditNullBooleanSelect, DynamicModelChoiceField, NumericArrayField, StaticSelect,
|
||||
DynamicModelMultipleChoiceField,
|
||||
add_blank_choice, BulkEditNullBooleanSelect, CommentField, DynamicModelChoiceField, NumericArrayField,
|
||||
SmallTextarea, StaticSelect, DynamicModelMultipleChoiceField,
|
||||
)
|
||||
|
||||
__all__ = (
|
||||
@ -43,15 +43,19 @@ class VRFBulkEditForm(NetBoxModelBulkEditForm):
|
||||
label='Enforce unique space'
|
||||
)
|
||||
description = forms.CharField(
|
||||
max_length=100,
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
comments = CommentField(
|
||||
widget=SmallTextarea,
|
||||
label='Comments'
|
||||
)
|
||||
|
||||
model = VRF
|
||||
fieldsets = (
|
||||
(None, ('tenant', 'enforce_unique', 'description')),
|
||||
)
|
||||
nullable_fields = ('tenant', 'description')
|
||||
nullable_fields = ('tenant', 'description', 'comments')
|
||||
|
||||
|
||||
class RouteTargetBulkEditForm(NetBoxModelBulkEditForm):
|
||||
@ -63,12 +67,16 @@ class RouteTargetBulkEditForm(NetBoxModelBulkEditForm):
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
comments = CommentField(
|
||||
widget=SmallTextarea,
|
||||
label='Comments'
|
||||
)
|
||||
|
||||
model = RouteTarget
|
||||
fieldsets = (
|
||||
(None, ('tenant', 'description')),
|
||||
)
|
||||
nullable_fields = ('tenant', 'description')
|
||||
nullable_fields = ('tenant', 'description', 'comments')
|
||||
|
||||
|
||||
class RIRBulkEditForm(NetBoxModelBulkEditForm):
|
||||
@ -103,15 +111,19 @@ class ASNBulkEditForm(NetBoxModelBulkEditForm):
|
||||
required=False
|
||||
)
|
||||
description = forms.CharField(
|
||||
max_length=100,
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
comments = CommentField(
|
||||
widget=SmallTextarea,
|
||||
label='Comments'
|
||||
)
|
||||
|
||||
model = ASN
|
||||
fieldsets = (
|
||||
(None, ('sites', 'rir', 'tenant', 'description')),
|
||||
)
|
||||
nullable_fields = ('date_added', 'description')
|
||||
nullable_fields = ('date_added', 'description', 'comments')
|
||||
|
||||
|
||||
class AggregateBulkEditForm(NetBoxModelBulkEditForm):
|
||||
@ -128,15 +140,19 @@ class AggregateBulkEditForm(NetBoxModelBulkEditForm):
|
||||
required=False
|
||||
)
|
||||
description = forms.CharField(
|
||||
max_length=100,
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
comments = CommentField(
|
||||
widget=SmallTextarea,
|
||||
label='Comments'
|
||||
)
|
||||
|
||||
model = Aggregate
|
||||
fieldsets = (
|
||||
(None, ('rir', 'tenant', 'date_added', 'description')),
|
||||
)
|
||||
nullable_fields = ('date_added', 'description')
|
||||
nullable_fields = ('date_added', 'description', 'comments')
|
||||
|
||||
|
||||
class RoleBulkEditForm(NetBoxModelBulkEditForm):
|
||||
@ -206,9 +222,13 @@ class PrefixBulkEditForm(NetBoxModelBulkEditForm):
|
||||
label='Treat as 100% utilized'
|
||||
)
|
||||
description = forms.CharField(
|
||||
max_length=100,
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
comments = CommentField(
|
||||
widget=SmallTextarea,
|
||||
label='Comments'
|
||||
)
|
||||
|
||||
model = Prefix
|
||||
fieldsets = (
|
||||
@ -217,7 +237,7 @@ class PrefixBulkEditForm(NetBoxModelBulkEditForm):
|
||||
('Addressing', ('vrf', 'prefix_length', 'is_pool', 'mark_utilized')),
|
||||
)
|
||||
nullable_fields = (
|
||||
'site', 'vrf', 'tenant', 'role', 'description',
|
||||
'site', 'vrf', 'tenant', 'role', 'description', 'comments',
|
||||
)
|
||||
|
||||
|
||||
@ -241,16 +261,20 @@ class IPRangeBulkEditForm(NetBoxModelBulkEditForm):
|
||||
required=False
|
||||
)
|
||||
description = forms.CharField(
|
||||
max_length=100,
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
comments = CommentField(
|
||||
widget=SmallTextarea,
|
||||
label='Comments'
|
||||
)
|
||||
|
||||
model = IPRange
|
||||
fieldsets = (
|
||||
(None, ('status', 'role', 'vrf', 'tenant', 'description')),
|
||||
)
|
||||
nullable_fields = (
|
||||
'vrf', 'tenant', 'role', 'description',
|
||||
'vrf', 'tenant', 'role', 'description', 'comments',
|
||||
)
|
||||
|
||||
|
||||
@ -285,9 +309,13 @@ class IPAddressBulkEditForm(NetBoxModelBulkEditForm):
|
||||
label='DNS name'
|
||||
)
|
||||
description = forms.CharField(
|
||||
max_length=100,
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
comments = CommentField(
|
||||
widget=SmallTextarea,
|
||||
label='Comments'
|
||||
)
|
||||
|
||||
model = IPAddress
|
||||
fieldsets = (
|
||||
@ -295,7 +323,7 @@ class IPAddressBulkEditForm(NetBoxModelBulkEditForm):
|
||||
('Addressing', ('vrf', 'mask_length', 'dns_name')),
|
||||
)
|
||||
nullable_fields = (
|
||||
'vrf', 'role', 'tenant', 'dns_name', 'description',
|
||||
'vrf', 'role', 'tenant', 'dns_name', 'description', 'comments',
|
||||
)
|
||||
|
||||
|
||||
@ -329,13 +357,17 @@ class FHRPGroupBulkEditForm(NetBoxModelBulkEditForm):
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
comments = CommentField(
|
||||
widget=SmallTextarea,
|
||||
label='Comments'
|
||||
)
|
||||
|
||||
model = FHRPGroup
|
||||
fieldsets = (
|
||||
(None, ('protocol', 'group_id', 'name', 'description')),
|
||||
('Authentication', ('auth_type', 'auth_key')),
|
||||
)
|
||||
nullable_fields = ('auth_type', 'auth_key', 'name', 'description')
|
||||
nullable_fields = ('auth_type', 'auth_key', 'name', 'description', 'comments')
|
||||
|
||||
|
||||
class VLANGroupBulkEditForm(NetBoxModelBulkEditForm):
|
||||
@ -405,9 +437,13 @@ class VLANBulkEditForm(NetBoxModelBulkEditForm):
|
||||
required=False
|
||||
)
|
||||
description = forms.CharField(
|
||||
max_length=100,
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
comments = CommentField(
|
||||
widget=SmallTextarea,
|
||||
label='Comments'
|
||||
)
|
||||
|
||||
model = VLAN
|
||||
fieldsets = (
|
||||
@ -415,7 +451,7 @@ class VLANBulkEditForm(NetBoxModelBulkEditForm):
|
||||
('Site & Group', ('region', 'site_group', 'site', 'group')),
|
||||
)
|
||||
nullable_fields = (
|
||||
'site', 'group', 'tenant', 'role', 'description',
|
||||
'site', 'group', 'tenant', 'role', 'description', 'comments',
|
||||
)
|
||||
|
||||
|
||||
@ -433,15 +469,19 @@ class ServiceTemplateBulkEditForm(NetBoxModelBulkEditForm):
|
||||
required=False
|
||||
)
|
||||
description = forms.CharField(
|
||||
max_length=100,
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
comments = CommentField(
|
||||
widget=SmallTextarea,
|
||||
label='Comments'
|
||||
)
|
||||
|
||||
model = ServiceTemplate
|
||||
fieldsets = (
|
||||
(None, ('protocol', 'ports', 'description')),
|
||||
)
|
||||
nullable_fields = ('description',)
|
||||
nullable_fields = ('description', 'comments')
|
||||
|
||||
|
||||
class ServiceBulkEditForm(ServiceTemplateBulkEditForm):
|
||||
@ -459,15 +499,19 @@ class L2VPNBulkEditForm(NetBoxModelBulkEditForm):
|
||||
required=False
|
||||
)
|
||||
description = forms.CharField(
|
||||
max_length=100,
|
||||
max_length=200,
|
||||
required=False
|
||||
)
|
||||
comments = CommentField(
|
||||
widget=SmallTextarea,
|
||||
label='Comments'
|
||||
)
|
||||
|
||||
model = L2VPN
|
||||
fieldsets = (
|
||||
(None, ('type', 'description', 'tenant')),
|
||||
(None, ('type', 'tenant', 'description')),
|
||||
)
|
||||
nullable_fields = ('tenant', 'description',)
|
||||
nullable_fields = ('tenant', 'description', 'comments')
|
||||
|
||||
|
||||
class L2VPNTerminationBulkEditForm(NetBoxModelBulkEditForm):
|
||||
|
@ -41,7 +41,7 @@ class VRFCSVForm(NetBoxModelCSVForm):
|
||||
|
||||
class Meta:
|
||||
model = VRF
|
||||
fields = ('name', 'rd', 'tenant', 'enforce_unique', 'description')
|
||||
fields = ('name', 'rd', 'tenant', 'enforce_unique', 'description', 'comments')
|
||||
|
||||
|
||||
class RouteTargetCSVForm(NetBoxModelCSVForm):
|
||||
@ -54,7 +54,7 @@ class RouteTargetCSVForm(NetBoxModelCSVForm):
|
||||
|
||||
class Meta:
|
||||
model = RouteTarget
|
||||
fields = ('name', 'description', 'tenant')
|
||||
fields = ('name', 'tenant', 'description', 'comments')
|
||||
|
||||
|
||||
class RIRCSVForm(NetBoxModelCSVForm):
|
||||
@ -83,7 +83,7 @@ class AggregateCSVForm(NetBoxModelCSVForm):
|
||||
|
||||
class Meta:
|
||||
model = Aggregate
|
||||
fields = ('prefix', 'rir', 'tenant', 'date_added', 'description')
|
||||
fields = ('prefix', 'rir', 'tenant', 'date_added', 'description', 'comments')
|
||||
|
||||
|
||||
class ASNCSVForm(NetBoxModelCSVForm):
|
||||
@ -101,7 +101,7 @@ class ASNCSVForm(NetBoxModelCSVForm):
|
||||
|
||||
class Meta:
|
||||
model = ASN
|
||||
fields = ('asn', 'rir', 'tenant', 'description')
|
||||
fields = ('asn', 'rir', 'tenant', 'description', 'comments')
|
||||
help_texts = {}
|
||||
|
||||
|
||||
@ -159,7 +159,7 @@ class PrefixCSVForm(NetBoxModelCSVForm):
|
||||
model = Prefix
|
||||
fields = (
|
||||
'prefix', 'vrf', 'tenant', 'site', 'vlan_group', 'vlan', 'status', 'role', 'is_pool', 'mark_utilized',
|
||||
'description',
|
||||
'description', 'comments',
|
||||
)
|
||||
|
||||
def __init__(self, data=None, *args, **kwargs):
|
||||
@ -204,7 +204,7 @@ class IPRangeCSVForm(NetBoxModelCSVForm):
|
||||
class Meta:
|
||||
model = IPRange
|
||||
fields = (
|
||||
'start_address', 'end_address', 'vrf', 'tenant', 'status', 'role', 'description',
|
||||
'start_address', 'end_address', 'vrf', 'tenant', 'status', 'role', 'description', 'comments',
|
||||
)
|
||||
|
||||
|
||||
@ -257,7 +257,7 @@ class IPAddressCSVForm(NetBoxModelCSVForm):
|
||||
model = IPAddress
|
||||
fields = [
|
||||
'address', 'vrf', 'tenant', 'status', 'role', 'device', 'virtual_machine', 'interface', 'is_primary',
|
||||
'dns_name', 'description',
|
||||
'dns_name', 'description', 'comments',
|
||||
]
|
||||
|
||||
def __init__(self, data=None, *args, **kwargs):
|
||||
@ -326,7 +326,7 @@ class FHRPGroupCSVForm(NetBoxModelCSVForm):
|
||||
|
||||
class Meta:
|
||||
model = FHRPGroup
|
||||
fields = ('protocol', 'group_id', 'auth_type', 'auth_key', 'name', 'description')
|
||||
fields = ('protocol', 'group_id', 'auth_type', 'auth_key', 'name', 'description', 'comments')
|
||||
|
||||
|
||||
class VLANGroupCSVForm(NetBoxModelCSVForm):
|
||||
@ -389,7 +389,7 @@ class VLANCSVForm(NetBoxModelCSVForm):
|
||||
|
||||
class Meta:
|
||||
model = VLAN
|
||||
fields = ('site', 'group', 'vid', 'name', 'tenant', 'status', 'role', 'description')
|
||||
fields = ('site', 'group', 'vid', 'name', 'tenant', 'status', 'role', 'description', 'comments')
|
||||
help_texts = {
|
||||
'vid': 'Numeric VLAN ID (1-4094)',
|
||||
'name': 'VLAN name',
|
||||
@ -404,7 +404,7 @@ class ServiceTemplateCSVForm(NetBoxModelCSVForm):
|
||||
|
||||
class Meta:
|
||||
model = ServiceTemplate
|
||||
fields = ('name', 'protocol', 'ports', 'description')
|
||||
fields = ('name', 'protocol', 'ports', 'description', 'comments')
|
||||
|
||||
|
||||
class ServiceCSVForm(NetBoxModelCSVForm):
|
||||
@ -427,7 +427,7 @@ class ServiceCSVForm(NetBoxModelCSVForm):
|
||||
|
||||
class Meta:
|
||||
model = Service
|
||||
fields = ('device', 'virtual_machine', 'name', 'protocol', 'ports', 'description')
|
||||
fields = ('device', 'virtual_machine', 'name', 'protocol', 'ports', 'description', 'comments')
|
||||
|
||||
|
||||
class L2VPNCSVForm(NetBoxModelCSVForm):
|
||||
@ -443,7 +443,7 @@ class L2VPNCSVForm(NetBoxModelCSVForm):
|
||||
|
||||
class Meta:
|
||||
model = L2VPN
|
||||
fields = ('identifier', 'name', 'slug', 'type', 'description')
|
||||
fields = ('identifier', 'name', 'slug', 'type', 'description', 'comments')
|
||||
|
||||
|
||||
class L2VPNTerminationCSVForm(NetBoxModelCSVForm):
|
||||
|
@ -1,6 +1,5 @@
|
||||
from django import forms
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db.models import Q
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from dcim.models import Location, Rack, Region, Site, SiteGroup, Device
|
||||
@ -11,7 +10,7 @@ from netbox.forms import NetBoxModelFilterSetForm
|
||||
from tenancy.forms import TenancyFilterForm
|
||||
from utilities.forms import (
|
||||
add_blank_choice, ContentTypeMultipleChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField,
|
||||
MultipleChoiceField, StaticSelect, TagFilterField, BOOLEAN_WITH_BLANK_CHOICES, APISelectMultiple,
|
||||
MultipleChoiceField, StaticSelect, TagFilterField, BOOLEAN_WITH_BLANK_CHOICES,
|
||||
)
|
||||
from virtualization.models import VirtualMachine
|
||||
|
||||
@ -46,7 +45,7 @@ IPADDRESS_MASK_LENGTH_CHOICES = add_blank_choice([
|
||||
class VRFFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
model = VRF
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
(None, ('q', 'filter', 'tag')),
|
||||
('Route Targets', ('import_target_id', 'export_target_id')),
|
||||
('Tenant', ('tenant_group_id', 'tenant_id')),
|
||||
)
|
||||
@ -66,7 +65,7 @@ class VRFFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
class RouteTargetFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
model = RouteTarget
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
(None, ('q', 'filter', 'tag')),
|
||||
('VRF', ('importing_vrf_id', 'exporting_vrf_id')),
|
||||
('Tenant', ('tenant_group_id', 'tenant_id')),
|
||||
)
|
||||
@ -98,7 +97,7 @@ class RIRFilterForm(NetBoxModelFilterSetForm):
|
||||
class AggregateFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
model = Aggregate
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
(None, ('q', 'filter', 'tag')),
|
||||
('Attributes', ('family', 'rir_id')),
|
||||
('Tenant', ('tenant_group_id', 'tenant_id')),
|
||||
)
|
||||
@ -119,7 +118,7 @@ class AggregateFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
class ASNFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
model = ASN
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
(None, ('q', 'filter', 'tag')),
|
||||
('Assignment', ('rir_id', 'site_id')),
|
||||
('Tenant', ('tenant_group_id', 'tenant_id')),
|
||||
)
|
||||
@ -144,7 +143,7 @@ class RoleFilterForm(NetBoxModelFilterSetForm):
|
||||
class PrefixFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
model = Prefix
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
(None, ('q', 'filter', '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')),
|
||||
@ -233,7 +232,7 @@ class PrefixFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
class IPRangeFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
model = IPRange
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
(None, ('q', 'filter', 'tag')),
|
||||
('Attriubtes', ('family', 'vrf_id', 'status', 'role_id')),
|
||||
('Tenant', ('tenant_group_id', 'tenant_id')),
|
||||
)
|
||||
@ -265,7 +264,7 @@ class IPRangeFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
class IPAddressFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
model = IPAddress
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
(None, ('q', 'filter', 'tag')),
|
||||
('Attributes', ('parent', 'family', 'status', 'role', 'mask_length', 'assigned_to_interface')),
|
||||
('VRF', ('vrf_id', 'present_in_vrf_id')),
|
||||
('Tenant', ('tenant_group_id', 'tenant_id')),
|
||||
@ -334,7 +333,7 @@ class IPAddressFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
class FHRPGroupFilterForm(NetBoxModelFilterSetForm):
|
||||
model = FHRPGroup
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
(None, ('q', 'filter', 'tag')),
|
||||
('Attributes', ('name', 'protocol', 'group_id')),
|
||||
('Authentication', ('auth_type', 'auth_key')),
|
||||
)
|
||||
@ -364,7 +363,7 @@ class FHRPGroupFilterForm(NetBoxModelFilterSetForm):
|
||||
|
||||
class VLANGroupFilterForm(NetBoxModelFilterSetForm):
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
(None, ('q', 'filter', 'tag')),
|
||||
('Location', ('region', 'sitegroup', 'site', 'location', 'rack')),
|
||||
('VLAN ID', ('min_vid', 'max_vid')),
|
||||
)
|
||||
@ -412,7 +411,7 @@ class VLANGroupFilterForm(NetBoxModelFilterSetForm):
|
||||
class VLANFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
model = VLAN
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
(None, ('q', 'filter', 'tag')),
|
||||
('Location', ('region_id', 'site_group_id', 'site_id')),
|
||||
('Attributes', ('group_id', 'status', 'role_id', 'vid')),
|
||||
('Tenant', ('tenant_group_id', 'tenant_id')),
|
||||
@ -465,7 +464,7 @@ class VLANFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
class ServiceTemplateFilterForm(NetBoxModelFilterSetForm):
|
||||
model = ServiceTemplate
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
(None, ('q', 'filter', 'tag')),
|
||||
('Attributes', ('protocol', 'port')),
|
||||
)
|
||||
protocol = forms.ChoiceField(
|
||||
@ -486,7 +485,7 @@ class ServiceFilterForm(ServiceTemplateFilterForm):
|
||||
class L2VPNFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
model = L2VPN
|
||||
fieldsets = (
|
||||
(None, ('q', 'tag')),
|
||||
(None, ('q', 'filter', 'tag')),
|
||||
('Attributes', ('type', 'import_target_id', 'export_target_id')),
|
||||
('Tenant', ('tenant_group_id', 'tenant_id')),
|
||||
)
|
||||
@ -511,8 +510,10 @@ class L2VPNFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
class L2VPNTerminationFilterForm(NetBoxModelFilterSetForm):
|
||||
model = L2VPNTermination
|
||||
fieldsets = (
|
||||
(None, ('l2vpn_id', )),
|
||||
('Assigned Object', ('assigned_object_type_id', 'region_id', 'site_id', 'device_id', 'virtual_machine_id', 'vlan_id')),
|
||||
(None, ('filter', 'l2vpn_id',)),
|
||||
('Assigned Object', (
|
||||
'assigned_object_type_id', 'region_id', 'site_id', 'device_id', 'virtual_machine_id', 'vlan_id',
|
||||
)),
|
||||
)
|
||||
l2vpn_id = DynamicModelChoiceField(
|
||||
queryset=L2VPN.objects.all(),
|
||||
|
@ -11,7 +11,7 @@ from netbox.forms import NetBoxModelForm
|
||||
from tenancy.forms import TenancyForm
|
||||
from utilities.exceptions import PermissionsViolation
|
||||
from utilities.forms import (
|
||||
add_blank_choice, BootstrapMixin, ContentTypeChoiceField, DatePicker, DynamicModelChoiceField,
|
||||
add_blank_choice, BootstrapMixin, CommentField, ContentTypeChoiceField, DatePicker, DynamicModelChoiceField,
|
||||
DynamicModelMultipleChoiceField, NumericArrayField, SlugField, StaticSelect, StaticSelectMultiple,
|
||||
)
|
||||
from virtualization.models import Cluster, ClusterGroup, VirtualMachine, VMInterface
|
||||
@ -49,6 +49,7 @@ class VRFForm(TenancyForm, NetBoxModelForm):
|
||||
queryset=RouteTarget.objects.all(),
|
||||
required=False
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
fieldsets = (
|
||||
('VRF', ('name', 'rd', 'enforce_unique', 'description', 'tags')),
|
||||
@ -59,8 +60,8 @@ class VRFForm(TenancyForm, NetBoxModelForm):
|
||||
class Meta:
|
||||
model = VRF
|
||||
fields = [
|
||||
'name', 'rd', 'enforce_unique', 'description', 'import_targets', 'export_targets', 'tenant_group', 'tenant',
|
||||
'tags',
|
||||
'name', 'rd', 'enforce_unique', 'import_targets', 'export_targets', 'tenant_group', 'tenant', 'description',
|
||||
'comments', 'tags',
|
||||
]
|
||||
labels = {
|
||||
'rd': "RD",
|
||||
@ -75,11 +76,12 @@ class RouteTargetForm(TenancyForm, NetBoxModelForm):
|
||||
('Route Target', ('name', 'description', 'tags')),
|
||||
('Tenancy', ('tenant_group', 'tenant')),
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
class Meta:
|
||||
model = RouteTarget
|
||||
fields = [
|
||||
'name', 'description', 'tenant_group', 'tenant', 'tags',
|
||||
'name', 'tenant_group', 'tenant', 'description', 'comments', 'tags',
|
||||
]
|
||||
|
||||
|
||||
@ -104,6 +106,7 @@ class AggregateForm(TenancyForm, NetBoxModelForm):
|
||||
queryset=RIR.objects.all(),
|
||||
label='RIR'
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
fieldsets = (
|
||||
('Aggregate', ('prefix', 'rir', 'date_added', 'description', 'tags')),
|
||||
@ -113,7 +116,7 @@ class AggregateForm(TenancyForm, NetBoxModelForm):
|
||||
class Meta:
|
||||
model = Aggregate
|
||||
fields = [
|
||||
'prefix', 'rir', 'date_added', 'description', 'tenant_group', 'tenant', 'tags',
|
||||
'prefix', 'rir', 'date_added', 'tenant_group', 'tenant', 'description', 'comments', 'tags',
|
||||
]
|
||||
help_texts = {
|
||||
'prefix': "IPv4 or IPv6 network",
|
||||
@ -134,6 +137,7 @@ class ASNForm(TenancyForm, NetBoxModelForm):
|
||||
label='Sites',
|
||||
required=False
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
fieldsets = (
|
||||
('ASN', ('asn', 'rir', 'sites', 'description', 'tags')),
|
||||
@ -143,7 +147,7 @@ class ASNForm(TenancyForm, NetBoxModelForm):
|
||||
class Meta:
|
||||
model = ASN
|
||||
fields = [
|
||||
'asn', 'rir', 'sites', 'tenant_group', 'tenant', 'description', 'tags'
|
||||
'asn', 'rir', 'sites', 'tenant_group', 'tenant', 'description', 'comments', 'tags'
|
||||
]
|
||||
help_texts = {
|
||||
'asn': "AS number",
|
||||
@ -235,6 +239,7 @@ class PrefixForm(TenancyForm, NetBoxModelForm):
|
||||
queryset=Role.objects.all(),
|
||||
required=False
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
fieldsets = (
|
||||
('Prefix', ('prefix', 'status', 'vrf', 'role', 'is_pool', 'mark_utilized', 'description', 'tags')),
|
||||
@ -245,8 +250,8 @@ class PrefixForm(TenancyForm, NetBoxModelForm):
|
||||
class Meta:
|
||||
model = Prefix
|
||||
fields = [
|
||||
'prefix', 'vrf', 'site', 'vlan', 'status', 'role', 'is_pool', 'mark_utilized', 'description',
|
||||
'tenant_group', 'tenant', 'tags',
|
||||
'prefix', 'vrf', 'site', 'vlan', 'status', 'role', 'is_pool', 'mark_utilized', 'tenant_group', 'tenant',
|
||||
'description', 'comments', 'tags',
|
||||
]
|
||||
widgets = {
|
||||
'status': StaticSelect(),
|
||||
@ -263,6 +268,7 @@ class IPRangeForm(TenancyForm, NetBoxModelForm):
|
||||
queryset=Role.objects.all(),
|
||||
required=False
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
fieldsets = (
|
||||
('IP Range', ('vrf', 'start_address', 'end_address', 'role', 'status', 'description', 'tags')),
|
||||
@ -272,7 +278,8 @@ class IPRangeForm(TenancyForm, NetBoxModelForm):
|
||||
class Meta:
|
||||
model = IPRange
|
||||
fields = [
|
||||
'vrf', 'start_address', 'end_address', 'status', 'role', 'description', 'tenant_group', 'tenant', 'tags',
|
||||
'vrf', 'start_address', 'end_address', 'status', 'role', 'tenant_group', 'tenant', 'description',
|
||||
'comments', 'tags',
|
||||
]
|
||||
widgets = {
|
||||
'status': StaticSelect(),
|
||||
@ -394,13 +401,14 @@ class IPAddressForm(TenancyForm, NetBoxModelForm):
|
||||
required=False,
|
||||
label='Make this the primary IP for the device/VM'
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
class Meta:
|
||||
model = IPAddress
|
||||
fields = [
|
||||
'address', 'vrf', 'status', 'role', 'dns_name', 'description', 'primary_for_parent', 'nat_site', 'nat_rack',
|
||||
'nat_device', 'nat_cluster', 'nat_virtual_machine', 'nat_vrf', 'nat_inside', 'tenant_group', 'tenant',
|
||||
'tags',
|
||||
'address', 'vrf', 'status', 'role', 'dns_name', 'primary_for_parent', 'nat_site', 'nat_rack', 'nat_device',
|
||||
'nat_cluster', 'nat_virtual_machine', 'nat_vrf', 'nat_inside', 'tenant_group', 'tenant', 'description',
|
||||
'comments', 'tags',
|
||||
]
|
||||
widgets = {
|
||||
'status': StaticSelect(),
|
||||
@ -535,6 +543,7 @@ class FHRPGroupForm(NetBoxModelForm):
|
||||
required=False,
|
||||
label='Status'
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
fieldsets = (
|
||||
('FHRP Group', ('protocol', 'group_id', 'name', 'description', 'tags')),
|
||||
@ -545,7 +554,8 @@ class FHRPGroupForm(NetBoxModelForm):
|
||||
class Meta:
|
||||
model = FHRPGroup
|
||||
fields = (
|
||||
'protocol', 'group_id', 'auth_type', 'auth_key', 'name', 'description', 'ip_vrf', 'ip_address', 'ip_status', 'tags',
|
||||
'protocol', 'group_id', 'auth_type', 'auth_key', 'name', 'ip_vrf', 'ip_address', 'ip_status', 'description',
|
||||
'comments', 'tags',
|
||||
)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
@ -767,11 +777,13 @@ class VLANForm(TenancyForm, NetBoxModelForm):
|
||||
queryset=Role.objects.all(),
|
||||
required=False
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
class Meta:
|
||||
model = VLAN
|
||||
fields = [
|
||||
'site', 'group', 'vid', 'name', 'status', 'role', 'description', 'tenant_group', 'tenant', 'tags',
|
||||
'site', 'group', 'vid', 'name', 'status', 'role', 'tenant_group', 'tenant', 'description', 'comments',
|
||||
'tags',
|
||||
]
|
||||
help_texts = {
|
||||
'site': "Leave blank if this VLAN spans multiple sites",
|
||||
@ -794,6 +806,7 @@ class ServiceTemplateForm(NetBoxModelForm):
|
||||
),
|
||||
help_text="Comma-separated list of one or more port numbers. A range may be specified using a hyphen."
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
fieldsets = (
|
||||
('Service Template', (
|
||||
@ -803,7 +816,7 @@ class ServiceTemplateForm(NetBoxModelForm):
|
||||
|
||||
class Meta:
|
||||
model = ServiceTemplate
|
||||
fields = ('name', 'protocol', 'ports', 'description', 'tags')
|
||||
fields = ('name', 'protocol', 'ports', 'description', 'comments', 'tags')
|
||||
widgets = {
|
||||
'protocol': StaticSelect(),
|
||||
}
|
||||
@ -834,11 +847,12 @@ class ServiceForm(NetBoxModelForm):
|
||||
'virtual_machine_id': '$virtual_machine',
|
||||
}
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
class Meta:
|
||||
model = Service
|
||||
fields = [
|
||||
'device', 'virtual_machine', 'name', 'protocol', 'ports', 'ipaddresses', 'description', 'tags',
|
||||
'device', 'virtual_machine', 'name', 'protocol', 'ports', 'ipaddresses', 'description', 'comments', 'tags',
|
||||
]
|
||||
help_texts = {
|
||||
'ipaddresses': "IP address assignment is optional. If no IPs are selected, the service is assumed to be "
|
||||
@ -899,6 +913,7 @@ class L2VPNForm(TenancyForm, NetBoxModelForm):
|
||||
queryset=RouteTarget.objects.all(),
|
||||
required=False
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
fieldsets = (
|
||||
('L2VPN', ('name', 'slug', 'type', 'identifier', 'description', 'tags')),
|
||||
@ -909,7 +924,8 @@ class L2VPNForm(TenancyForm, NetBoxModelForm):
|
||||
class Meta:
|
||||
model = L2VPN
|
||||
fields = (
|
||||
'name', 'slug', 'type', 'identifier', 'description', 'import_targets', 'export_targets', 'tenant', 'tags'
|
||||
'name', 'slug', 'type', 'identifier', 'import_targets', 'export_targets', 'tenant', 'description',
|
||||
'comments', 'tags'
|
||||
)
|
||||
widgets = {
|
||||
'type': StaticSelect(),
|
||||
|
@ -91,7 +91,7 @@ class Migration(migrations.Migration):
|
||||
options={
|
||||
'verbose_name': 'RIR',
|
||||
'verbose_name_plural': 'RIRs',
|
||||
'ordering': ['name'],
|
||||
'ordering': ('name',),
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
@ -107,7 +107,7 @@ class Migration(migrations.Migration):
|
||||
('description', models.CharField(blank=True, max_length=200)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['weight', 'name'],
|
||||
'ordering': ('weight', 'name'),
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
|
@ -0,0 +1,73 @@
|
||||
# Generated by Django 4.1.2 on 2022-11-03 18:24
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('ipam', '0062_unique_constraints'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='aggregate',
|
||||
name='comments',
|
||||
field=models.TextField(blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='asn',
|
||||
name='comments',
|
||||
field=models.TextField(blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='fhrpgroup',
|
||||
name='comments',
|
||||
field=models.TextField(blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='ipaddress',
|
||||
name='comments',
|
||||
field=models.TextField(blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='iprange',
|
||||
name='comments',
|
||||
field=models.TextField(blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='l2vpn',
|
||||
name='comments',
|
||||
field=models.TextField(blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='prefix',
|
||||
name='comments',
|
||||
field=models.TextField(blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='routetarget',
|
||||
name='comments',
|
||||
field=models.TextField(blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='service',
|
||||
name='comments',
|
||||
field=models.TextField(blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='servicetemplate',
|
||||
name='comments',
|
||||
field=models.TextField(blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='vlan',
|
||||
name='comments',
|
||||
field=models.TextField(blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='vrf',
|
||||
name='comments',
|
||||
field=models.TextField(blank=True),
|
||||
),
|
||||
]
|
@ -4,7 +4,7 @@ from django.core.validators import MaxValueValidator, MinValueValidator
|
||||
from django.db import models
|
||||
from django.urls import reverse
|
||||
|
||||
from netbox.models import ChangeLoggedModel, NetBoxModel
|
||||
from netbox.models import ChangeLoggedModel, PrimaryModel
|
||||
from netbox.models.features import WebhooksMixin
|
||||
from ipam.choices import *
|
||||
from ipam.constants import *
|
||||
@ -15,7 +15,7 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class FHRPGroup(NetBoxModel):
|
||||
class FHRPGroup(PrimaryModel):
|
||||
"""
|
||||
A grouping of next hope resolution protocol (FHRP) peers. (For instance, VRRP or HSRP.)
|
||||
"""
|
||||
@ -41,10 +41,6 @@ class FHRPGroup(NetBoxModel):
|
||||
blank=True,
|
||||
verbose_name='Authentication key'
|
||||
)
|
||||
description = models.CharField(
|
||||
max_length=200,
|
||||
blank=True
|
||||
)
|
||||
ip_addresses = GenericRelation(
|
||||
to='ipam.IPAddress',
|
||||
content_type_field='assigned_object_type',
|
||||
|
@ -9,7 +9,7 @@ from django.utils.functional import cached_property
|
||||
|
||||
from dcim.fields import ASNField
|
||||
from dcim.models import Device
|
||||
from netbox.models import OrganizationalModel, NetBoxModel
|
||||
from netbox.models import OrganizationalModel, PrimaryModel
|
||||
from ipam.choices import *
|
||||
from ipam.constants import *
|
||||
from ipam.fields import IPNetworkField, IPAddressField
|
||||
@ -61,37 +61,22 @@ class RIR(OrganizationalModel):
|
||||
A Regional Internet Registry (RIR) is responsible for the allocation of a large portion of the global IP address
|
||||
space. This can be an organization like ARIN or RIPE, or a governing standard such as RFC 1918.
|
||||
"""
|
||||
name = models.CharField(
|
||||
max_length=100,
|
||||
unique=True
|
||||
)
|
||||
slug = models.SlugField(
|
||||
max_length=100,
|
||||
unique=True
|
||||
)
|
||||
is_private = models.BooleanField(
|
||||
default=False,
|
||||
verbose_name='Private',
|
||||
help_text='IP space managed by this RIR is considered private'
|
||||
)
|
||||
description = models.CharField(
|
||||
max_length=200,
|
||||
blank=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
ordering = ('name',)
|
||||
verbose_name = 'RIR'
|
||||
verbose_name_plural = 'RIRs'
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('ipam:rir', args=[self.pk])
|
||||
|
||||
|
||||
class ASN(NetBoxModel):
|
||||
class ASN(PrimaryModel):
|
||||
"""
|
||||
An autonomous system (AS) number is typically used to represent an independent routing domain. A site can have
|
||||
one or more ASNs assigned to it.
|
||||
@ -101,10 +86,6 @@ class ASN(NetBoxModel):
|
||||
verbose_name='ASN',
|
||||
help_text='32-bit autonomous system number'
|
||||
)
|
||||
description = models.CharField(
|
||||
max_length=200,
|
||||
blank=True
|
||||
)
|
||||
rir = models.ForeignKey(
|
||||
to='ipam.RIR',
|
||||
on_delete=models.PROTECT,
|
||||
@ -154,7 +135,7 @@ class ASN(NetBoxModel):
|
||||
return self.asn
|
||||
|
||||
|
||||
class Aggregate(GetAvailablePrefixesMixin, NetBoxModel):
|
||||
class Aggregate(GetAvailablePrefixesMixin, PrimaryModel):
|
||||
"""
|
||||
An aggregate exists at the root level of the IP address space hierarchy in NetBox. Aggregates are used to organize
|
||||
the hierarchy and track the overall utilization of available address space. Each Aggregate is assigned to a RIR.
|
||||
@ -177,10 +158,6 @@ class Aggregate(GetAvailablePrefixesMixin, NetBoxModel):
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
description = models.CharField(
|
||||
max_length=200,
|
||||
blank=True
|
||||
)
|
||||
|
||||
clone_fields = (
|
||||
'rir', 'tenant', 'date_added', 'description',
|
||||
@ -265,24 +242,12 @@ class Role(OrganizationalModel):
|
||||
A Role represents the functional role of a Prefix or VLAN; for example, "Customer," "Infrastructure," or
|
||||
"Management."
|
||||
"""
|
||||
name = models.CharField(
|
||||
max_length=100,
|
||||
unique=True
|
||||
)
|
||||
slug = models.SlugField(
|
||||
max_length=100,
|
||||
unique=True
|
||||
)
|
||||
weight = models.PositiveSmallIntegerField(
|
||||
default=1000
|
||||
)
|
||||
description = models.CharField(
|
||||
max_length=200,
|
||||
blank=True,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ['weight', 'name']
|
||||
ordering = ('weight', 'name')
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
@ -291,7 +256,7 @@ class Role(OrganizationalModel):
|
||||
return reverse('ipam:role', args=[self.pk])
|
||||
|
||||
|
||||
class Prefix(GetAvailablePrefixesMixin, NetBoxModel):
|
||||
class Prefix(GetAvailablePrefixesMixin, PrimaryModel):
|
||||
"""
|
||||
A Prefix represents an IPv4 or IPv6 network, including mask length. Prefixes can optionally be assigned to Sites and
|
||||
VRFs. A Prefix must be assigned a status and may optionally be assigned a used-define Role. A Prefix can also be
|
||||
@ -354,10 +319,6 @@ class Prefix(GetAvailablePrefixesMixin, NetBoxModel):
|
||||
default=False,
|
||||
help_text="Treat as 100% utilized"
|
||||
)
|
||||
description = models.CharField(
|
||||
max_length=200,
|
||||
blank=True
|
||||
)
|
||||
|
||||
# Cached depth & child counts
|
||||
_depth = models.PositiveSmallIntegerField(
|
||||
@ -572,7 +533,7 @@ class Prefix(GetAvailablePrefixesMixin, NetBoxModel):
|
||||
return min(utilization, 100)
|
||||
|
||||
|
||||
class IPRange(NetBoxModel):
|
||||
class IPRange(PrimaryModel):
|
||||
"""
|
||||
A range of IP addresses, defined by start and end addresses.
|
||||
"""
|
||||
@ -614,10 +575,6 @@ class IPRange(NetBoxModel):
|
||||
null=True,
|
||||
help_text='The primary function of this range'
|
||||
)
|
||||
description = models.CharField(
|
||||
max_length=200,
|
||||
blank=True
|
||||
)
|
||||
|
||||
clone_fields = (
|
||||
'vrf', 'tenant', 'status', 'role', 'description',
|
||||
@ -767,7 +724,7 @@ class IPRange(NetBoxModel):
|
||||
return int(float(child_count) / self.size * 100)
|
||||
|
||||
|
||||
class IPAddress(NetBoxModel):
|
||||
class IPAddress(PrimaryModel):
|
||||
"""
|
||||
An IPAddress represents an individual IPv4 or IPv6 address and its mask. The mask length should match what is
|
||||
configured in the real world. (Typically, only loopback interfaces are configured with /32 or /128 masks.) Like
|
||||
@ -840,10 +797,6 @@ class IPAddress(NetBoxModel):
|
||||
verbose_name='DNS Name',
|
||||
help_text='Hostname or FQDN (not case-sensitive)'
|
||||
)
|
||||
description = models.CharField(
|
||||
max_length=200,
|
||||
blank=True
|
||||
)
|
||||
|
||||
objects = IPAddressManager()
|
||||
|
||||
|
@ -8,7 +8,7 @@ from django.utils.functional import cached_property
|
||||
|
||||
from ipam.choices import L2VPNTypeChoices
|
||||
from ipam.constants import L2VPN_ASSIGNMENT_MODELS
|
||||
from netbox.models import NetBoxModel
|
||||
from netbox.models import NetBoxModel, PrimaryModel
|
||||
|
||||
__all__ = (
|
||||
'L2VPN',
|
||||
@ -16,7 +16,7 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class L2VPN(NetBoxModel):
|
||||
class L2VPN(PrimaryModel):
|
||||
name = models.CharField(
|
||||
max_length=100,
|
||||
unique=True
|
||||
@ -43,10 +43,6 @@ class L2VPN(NetBoxModel):
|
||||
related_name='exporting_l2vpns',
|
||||
blank=True
|
||||
)
|
||||
description = models.CharField(
|
||||
max_length=200,
|
||||
blank=True
|
||||
)
|
||||
tenant = models.ForeignKey(
|
||||
to='tenancy.Tenant',
|
||||
on_delete=models.PROTECT,
|
||||
|
@ -6,7 +6,7 @@ from django.urls import reverse
|
||||
|
||||
from ipam.choices import *
|
||||
from ipam.constants import *
|
||||
from netbox.models import NetBoxModel
|
||||
from netbox.models import PrimaryModel
|
||||
from utilities.utils import array_to_string
|
||||
|
||||
|
||||
@ -30,10 +30,6 @@ class ServiceBase(models.Model):
|
||||
),
|
||||
verbose_name='Port numbers'
|
||||
)
|
||||
description = models.CharField(
|
||||
max_length=200,
|
||||
blank=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
@ -46,7 +42,7 @@ class ServiceBase(models.Model):
|
||||
return array_to_string(self.ports)
|
||||
|
||||
|
||||
class ServiceTemplate(ServiceBase, NetBoxModel):
|
||||
class ServiceTemplate(ServiceBase, PrimaryModel):
|
||||
"""
|
||||
A template for a Service to be applied to a device or virtual machine.
|
||||
"""
|
||||
@ -62,7 +58,7 @@ class ServiceTemplate(ServiceBase, NetBoxModel):
|
||||
return reverse('ipam:servicetemplate', args=[self.pk])
|
||||
|
||||
|
||||
class Service(ServiceBase, NetBoxModel):
|
||||
class Service(ServiceBase, PrimaryModel):
|
||||
"""
|
||||
A Service represents a layer-four service (e.g. HTTP or SSH) running on a Device or VirtualMachine. A Service may
|
||||
optionally be tied to one or more specific IPAddresses belonging to its parent.
|
||||
|
@ -8,12 +8,10 @@ from django.urls import reverse
|
||||
from dcim.models import Interface
|
||||
from ipam.choices import *
|
||||
from ipam.constants import *
|
||||
from ipam.models import L2VPNTermination
|
||||
from ipam.querysets import VLANQuerySet
|
||||
from netbox.models import OrganizationalModel, NetBoxModel
|
||||
from netbox.models import OrganizationalModel, PrimaryModel
|
||||
from virtualization.models import VMInterface
|
||||
|
||||
|
||||
__all__ = (
|
||||
'VLAN',
|
||||
'VLANGroup',
|
||||
@ -63,10 +61,6 @@ class VLANGroup(OrganizationalModel):
|
||||
),
|
||||
help_text='Highest permissible ID of a child VLAN'
|
||||
)
|
||||
description = models.CharField(
|
||||
max_length=200,
|
||||
blank=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ('name', 'pk') # Name may be non-unique
|
||||
@ -83,9 +77,6 @@ class VLANGroup(OrganizationalModel):
|
||||
verbose_name = 'VLAN group'
|
||||
verbose_name_plural = 'VLAN groups'
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('ipam:vlangroup', args=[self.pk])
|
||||
|
||||
@ -123,7 +114,7 @@ class VLANGroup(OrganizationalModel):
|
||||
return None
|
||||
|
||||
|
||||
class VLAN(NetBoxModel):
|
||||
class VLAN(PrimaryModel):
|
||||
"""
|
||||
A VLAN is a distinct layer two forwarding domain identified by a 12-bit integer (1-4094). Each VLAN must be assigned
|
||||
to a Site, however VLAN IDs need not be unique within a Site. A VLAN may optionally be assigned to a VLANGroup,
|
||||
@ -175,10 +166,6 @@ class VLAN(NetBoxModel):
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
description = models.CharField(
|
||||
max_length=200,
|
||||
blank=True
|
||||
)
|
||||
|
||||
l2vpn_terminations = GenericRelation(
|
||||
to='ipam.L2VPNTermination',
|
||||
|
@ -2,7 +2,7 @@ from django.db import models
|
||||
from django.urls import reverse
|
||||
|
||||
from ipam.constants import *
|
||||
from netbox.models import NetBoxModel
|
||||
from netbox.models import PrimaryModel
|
||||
|
||||
|
||||
__all__ = (
|
||||
@ -11,7 +11,7 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
class VRF(NetBoxModel):
|
||||
class VRF(PrimaryModel):
|
||||
"""
|
||||
A virtual routing and forwarding (VRF) table represents a discrete layer three forwarding domain (e.g. a routing
|
||||
table). Prefixes and IPAddresses can optionally be assigned to VRFs. (Prefixes and IPAddresses not assigned to a VRF
|
||||
@ -40,10 +40,6 @@ class VRF(NetBoxModel):
|
||||
verbose_name='Enforce unique space',
|
||||
help_text='Prevent duplicate prefixes/IP addresses within this VRF'
|
||||
)
|
||||
description = models.CharField(
|
||||
max_length=200,
|
||||
blank=True
|
||||
)
|
||||
import_targets = models.ManyToManyField(
|
||||
to='ipam.RouteTarget',
|
||||
related_name='importing_vrfs',
|
||||
@ -73,7 +69,7 @@ class VRF(NetBoxModel):
|
||||
return reverse('ipam:vrf', args=[self.pk])
|
||||
|
||||
|
||||
class RouteTarget(NetBoxModel):
|
||||
class RouteTarget(PrimaryModel):
|
||||
"""
|
||||
A BGP extended community used to control the redistribution of routes among VRFs, as defined in RFC 4364.
|
||||
"""
|
||||
@ -82,10 +78,6 @@ class RouteTarget(NetBoxModel):
|
||||
unique=True,
|
||||
help_text='Route target value (formatted in accordance with RFC 4360)'
|
||||
)
|
||||
description = models.CharField(
|
||||
max_length=200,
|
||||
blank=True
|
||||
)
|
||||
tenant = models.ForeignKey(
|
||||
to='tenancy.Tenant',
|
||||
on_delete=models.PROTECT,
|
||||
|
@ -20,7 +20,6 @@ class FHRPGroupTable(NetBoxTable):
|
||||
group_id = tables.Column(
|
||||
linkify=True
|
||||
)
|
||||
comments = columns.MarkdownColumn()
|
||||
ip_addresses = tables.TemplateColumn(
|
||||
template_code=IPADDRESSES,
|
||||
orderable=False,
|
||||
@ -29,6 +28,7 @@ class FHRPGroupTable(NetBoxTable):
|
||||
member_count = tables.Column(
|
||||
verbose_name='Members'
|
||||
)
|
||||
comments = columns.MarkdownColumn()
|
||||
tags = columns.TagColumn(
|
||||
url_name='ipam:fhrpgroup_list'
|
||||
)
|
||||
@ -36,7 +36,7 @@ class FHRPGroupTable(NetBoxTable):
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = FHRPGroup
|
||||
fields = (
|
||||
'pk', 'group_id', 'protocol', 'name', 'auth_type', 'auth_key', 'description', 'ip_addresses',
|
||||
'pk', 'group_id', 'protocol', 'name', 'auth_type', 'auth_key', 'description', 'comments', 'ip_addresses',
|
||||
'member_count', 'tags', 'created', 'last_updated',
|
||||
)
|
||||
default_columns = (
|
||||
|
@ -120,6 +120,7 @@ class ASNTable(TenancyColumnsMixin, NetBoxTable):
|
||||
linkify_item=True,
|
||||
verbose_name='Sites'
|
||||
)
|
||||
comments = columns.MarkdownColumn()
|
||||
tags = columns.TagColumn(
|
||||
url_name='ipam:asn_list'
|
||||
)
|
||||
@ -127,8 +128,8 @@ class ASNTable(TenancyColumnsMixin, NetBoxTable):
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = ASN
|
||||
fields = (
|
||||
'pk', 'asn', 'asn_asdot', 'rir', 'site_count', 'provider_count', 'tenant', 'tenant_group', 'description', 'sites', 'tags',
|
||||
'created', 'last_updated', 'actions',
|
||||
'pk', 'asn', 'asn_asdot', 'rir', 'site_count', 'provider_count', 'tenant', 'tenant_group', 'description',
|
||||
'comments', 'sites', 'tags', 'created', 'last_updated', 'actions',
|
||||
)
|
||||
default_columns = ('pk', 'asn', 'rir', 'site_count', 'provider_count', 'sites', 'description', 'tenant')
|
||||
|
||||
@ -153,6 +154,7 @@ class AggregateTable(TenancyColumnsMixin, NetBoxTable):
|
||||
accessor='get_utilization',
|
||||
orderable=False
|
||||
)
|
||||
comments = columns.MarkdownColumn()
|
||||
tags = columns.TagColumn(
|
||||
url_name='ipam:aggregate_list'
|
||||
)
|
||||
@ -160,8 +162,8 @@ class AggregateTable(TenancyColumnsMixin, NetBoxTable):
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = Aggregate
|
||||
fields = (
|
||||
'pk', 'id', 'prefix', 'rir', 'tenant', 'tenant_group', 'child_count', 'utilization', 'date_added', 'description', 'tags',
|
||||
'created', 'last_updated',
|
||||
'pk', 'id', 'prefix', 'rir', 'tenant', 'tenant_group', 'child_count', 'utilization', 'date_added',
|
||||
'description', 'comments', 'tags', 'created', 'last_updated',
|
||||
)
|
||||
default_columns = ('pk', 'prefix', 'rir', 'tenant', 'child_count', 'utilization', 'date_added', 'description')
|
||||
|
||||
@ -278,6 +280,7 @@ class PrefixTable(TenancyColumnsMixin, NetBoxTable):
|
||||
accessor='get_utilization',
|
||||
orderable=False
|
||||
)
|
||||
comments = columns.MarkdownColumn()
|
||||
tags = columns.TagColumn(
|
||||
url_name='ipam:prefix_list'
|
||||
)
|
||||
@ -285,8 +288,9 @@ class PrefixTable(TenancyColumnsMixin, NetBoxTable):
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = Prefix
|
||||
fields = (
|
||||
'pk', 'id', 'prefix', 'prefix_flat', 'status', 'children', 'vrf', 'utilization', 'tenant', 'tenant_group', 'site',
|
||||
'vlan_group', 'vlan', 'role', 'is_pool', 'mark_utilized', 'description', 'tags', 'created', 'last_updated',
|
||||
'pk', 'id', 'prefix', 'prefix_flat', 'status', 'children', 'vrf', 'utilization', 'tenant', 'tenant_group',
|
||||
'site', 'vlan_group', 'vlan', 'role', 'is_pool', 'mark_utilized', 'description', 'comments', 'tags',
|
||||
'created', 'last_updated',
|
||||
)
|
||||
default_columns = (
|
||||
'pk', 'prefix', 'status', 'children', 'vrf', 'utilization', 'tenant', 'site', 'vlan', 'role', 'description',
|
||||
@ -317,6 +321,7 @@ class IPRangeTable(TenancyColumnsMixin, NetBoxTable):
|
||||
accessor='utilization',
|
||||
orderable=False
|
||||
)
|
||||
comments = columns.MarkdownColumn()
|
||||
tags = columns.TagColumn(
|
||||
url_name='ipam:iprange_list'
|
||||
)
|
||||
@ -324,8 +329,8 @@ class IPRangeTable(TenancyColumnsMixin, NetBoxTable):
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = IPRange
|
||||
fields = (
|
||||
'pk', 'id', 'start_address', 'end_address', 'size', 'vrf', 'status', 'role', 'tenant', 'tenant_group', 'description',
|
||||
'utilization', 'tags', 'created', 'last_updated',
|
||||
'pk', 'id', 'start_address', 'end_address', 'size', 'vrf', 'status', 'role', 'tenant', 'tenant_group',
|
||||
'utilization', 'description', 'comments', 'tags', 'created', 'last_updated',
|
||||
)
|
||||
default_columns = (
|
||||
'pk', 'start_address', 'end_address', 'size', 'vrf', 'status', 'role', 'tenant', 'description',
|
||||
@ -378,6 +383,7 @@ class IPAddressTable(TenancyColumnsMixin, NetBoxTable):
|
||||
linkify=lambda record: record.assigned_object.get_absolute_url(),
|
||||
verbose_name='Assigned'
|
||||
)
|
||||
comments = columns.MarkdownColumn()
|
||||
tags = columns.TagColumn(
|
||||
url_name='ipam:ipaddress_list'
|
||||
)
|
||||
@ -385,8 +391,8 @@ class IPAddressTable(TenancyColumnsMixin, NetBoxTable):
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = IPAddress
|
||||
fields = (
|
||||
'pk', 'id', 'address', 'vrf', 'status', 'role', 'tenant', 'tenant_group', 'nat_inside', 'nat_outside', 'assigned', 'dns_name', 'description',
|
||||
'tags', 'created', 'last_updated',
|
||||
'pk', 'id', 'address', 'vrf', 'status', 'role', 'tenant', 'tenant_group', 'nat_inside', 'nat_outside',
|
||||
'assigned', 'dns_name', 'description', 'comments', 'tags', 'created', 'last_updated',
|
||||
)
|
||||
default_columns = (
|
||||
'pk', 'address', 'vrf', 'status', 'role', 'tenant', 'assigned', 'dns_name', 'description',
|
||||
|
@ -29,12 +29,16 @@ class L2VPNTable(TenancyColumnsMixin, NetBoxTable):
|
||||
template_code=L2VPN_TARGETS,
|
||||
orderable=False
|
||||
)
|
||||
comments = columns.MarkdownColumn()
|
||||
tags = columns.TagColumn(
|
||||
url_name='ipam:prefix_list'
|
||||
)
|
||||
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = L2VPN
|
||||
fields = (
|
||||
'pk', 'name', 'slug', 'identifier', 'type', 'description', 'import_targets', 'export_targets', 'tenant', 'tenant_group',
|
||||
'actions',
|
||||
'pk', 'name', 'slug', 'identifier', 'type', 'import_targets', 'export_targets', 'tenant', 'tenant_group',
|
||||
'description', 'comments', 'tags', 'created', 'last_updated', 'actions',
|
||||
)
|
||||
default_columns = ('pk', 'name', 'identifier', 'type', 'description', 'actions')
|
||||
|
||||
|
@ -17,13 +17,16 @@ class ServiceTemplateTable(NetBoxTable):
|
||||
accessor=tables.A('port_list'),
|
||||
order_by=tables.A('ports'),
|
||||
)
|
||||
comments = columns.MarkdownColumn()
|
||||
tags = columns.TagColumn(
|
||||
url_name='ipam:servicetemplate_list'
|
||||
)
|
||||
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = ServiceTemplate
|
||||
fields = ('pk', 'id', 'name', 'protocol', 'ports', 'description', 'tags')
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'protocol', 'ports', 'description', 'comments', 'tags', 'created', 'last_updated',
|
||||
)
|
||||
default_columns = ('pk', 'name', 'protocol', 'ports', 'description')
|
||||
|
||||
|
||||
@ -39,6 +42,7 @@ class ServiceTable(NetBoxTable):
|
||||
accessor=tables.A('port_list'),
|
||||
order_by=tables.A('ports'),
|
||||
)
|
||||
comments = columns.MarkdownColumn()
|
||||
tags = columns.TagColumn(
|
||||
url_name='ipam:service_list'
|
||||
)
|
||||
@ -46,7 +50,7 @@ class ServiceTable(NetBoxTable):
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = Service
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'parent', 'protocol', 'ports', 'ipaddresses', 'description', 'tags', 'created',
|
||||
'last_updated',
|
||||
'pk', 'id', 'name', 'parent', 'protocol', 'ports', 'ipaddresses', 'description', 'comments', 'tags',
|
||||
'created', 'last_updated',
|
||||
)
|
||||
default_columns = ('pk', 'name', 'parent', 'protocol', 'ports', 'description')
|
||||
|
@ -121,6 +121,7 @@ class VLANTable(TenancyColumnsMixin, NetBoxTable):
|
||||
orderable=False,
|
||||
verbose_name='Prefixes'
|
||||
)
|
||||
comments = columns.MarkdownColumn()
|
||||
tags = columns.TagColumn(
|
||||
url_name='ipam:vlan_list'
|
||||
)
|
||||
@ -129,7 +130,7 @@ class VLANTable(TenancyColumnsMixin, NetBoxTable):
|
||||
model = VLAN
|
||||
fields = (
|
||||
'pk', 'id', 'vid', 'name', 'site', 'group', 'prefixes', 'tenant', 'tenant_group', 'status', 'role',
|
||||
'description', 'tags', 'l2vpn', 'created', 'last_updated',
|
||||
'description', 'comments', 'tags', 'l2vpn', 'created', 'last_updated',
|
||||
)
|
||||
default_columns = ('pk', 'vid', 'name', 'site', 'group', 'prefixes', 'tenant', 'status', 'role', 'description')
|
||||
row_attrs = {
|
||||
|
@ -38,6 +38,7 @@ class VRFTable(TenancyColumnsMixin, NetBoxTable):
|
||||
template_code=VRF_TARGETS,
|
||||
orderable=False
|
||||
)
|
||||
comments = columns.MarkdownColumn()
|
||||
tags = columns.TagColumn(
|
||||
url_name='ipam:vrf_list'
|
||||
)
|
||||
@ -45,8 +46,8 @@ class VRFTable(TenancyColumnsMixin, NetBoxTable):
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = VRF
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'rd', 'tenant', 'tenant_group', 'enforce_unique', 'description', 'import_targets', 'export_targets',
|
||||
'tags', 'created', 'last_updated',
|
||||
'pk', 'id', 'name', 'rd', 'tenant', 'tenant_group', 'enforce_unique', 'import_targets', 'export_targets',
|
||||
'description', 'comments', 'tags', 'created', 'last_updated',
|
||||
)
|
||||
default_columns = ('pk', 'name', 'rd', 'tenant', 'description')
|
||||
|
||||
@ -59,11 +60,14 @@ class RouteTargetTable(TenancyColumnsMixin, NetBoxTable):
|
||||
name = tables.Column(
|
||||
linkify=True
|
||||
)
|
||||
comments = columns.MarkdownColumn()
|
||||
tags = columns.TagColumn(
|
||||
url_name='ipam:vrf_list'
|
||||
)
|
||||
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = RouteTarget
|
||||
fields = ('pk', 'id', 'name', 'tenant', 'tenant_group', 'description', 'tags', 'created', 'last_updated',)
|
||||
fields = (
|
||||
'pk', 'id', 'name', 'tenant', 'tenant_group', 'description', 'comments', 'tags', 'created', 'last_updated',
|
||||
)
|
||||
default_columns = ('pk', 'name', 'tenant', 'description')
|
||||
|
@ -72,6 +72,9 @@ ADMINS = [
|
||||
# ('John Doe', 'jdoe@example.com'),
|
||||
]
|
||||
|
||||
# Permit the retrieval of API tokens after their creation.
|
||||
ALLOW_TOKEN_RETRIEVAL = False
|
||||
|
||||
# Enable any desired validators for local account passwords below. For a list of included validators, please see the
|
||||
# Django documentation at https://docs.djangoproject.com/en/stable/topics/auth/passwords/#password-validation.
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
|
@ -1,7 +1,7 @@
|
||||
from django.conf import settings as django_settings
|
||||
|
||||
from extras.registry import registry
|
||||
from netbox.config import get_config
|
||||
from netbox.registry import registry
|
||||
|
||||
|
||||
def settings_and_registry(request):
|
||||
|
@ -3,7 +3,7 @@ import logging
|
||||
from django.db.models.signals import post_save
|
||||
from django.dispatch import receiver
|
||||
|
||||
from extras.registry import registry
|
||||
from netbox.registry import registry
|
||||
|
||||
|
||||
logger = logging.getLogger('netbox.denormalized')
|
||||
|
@ -4,10 +4,11 @@ from django.contrib.contenttypes.models import ContentType
|
||||
from django.db import models
|
||||
from django_filters.exceptions import FieldLookupError
|
||||
from django_filters.utils import get_model_field, resolve_field
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
from extras.choices import CustomFieldFilterLogicChoices
|
||||
from extras.filters import TagFilter
|
||||
from extras.models import CustomField
|
||||
from extras.models import CustomField, SavedFilter
|
||||
from utilities.constants import (
|
||||
FILTER_CHAR_BASED_LOOKUP_MAP, FILTER_NEGATION_LOOKUP_MAP, FILTER_TREENODE_NEGATION_LOOKUP_MAP,
|
||||
FILTER_NUMERIC_BASED_LOOKUP_MAP
|
||||
@ -80,12 +81,28 @@ class BaseFilterSet(django_filters.FilterSet):
|
||||
},
|
||||
})
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
def __init__(self, data=None, *args, **kwargs):
|
||||
# bit of a hack for #9231 - extras.lookup.Empty is registered in apps.ready
|
||||
# however FilterSet Factory is setup before this which creates the
|
||||
# initial filters. This recreates the filters so Empty is picked up correctly.
|
||||
self.base_filters = self.__class__.get_filters()
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# Apply any referenced SavedFilters
|
||||
if data and 'filter' in data:
|
||||
data = data.copy() # Get a mutable copy
|
||||
saved_filters = SavedFilter.objects.filter(pk__in=data.pop('filter'))
|
||||
for sf in saved_filters:
|
||||
for key, value in sf.parameters.items():
|
||||
# QueryDicts are... fun
|
||||
if type(value) not in (list, tuple):
|
||||
value = [value]
|
||||
if key in data:
|
||||
for v in value:
|
||||
data.appendlist(key, v)
|
||||
else:
|
||||
data.setlist(key, value)
|
||||
|
||||
super().__init__(data, *args, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def _get_filter_lookup_dict(existing_filter):
|
||||
|
@ -3,7 +3,7 @@ from django.contrib.contenttypes.models import ContentType
|
||||
from django.db.models import Q
|
||||
|
||||
from extras.choices import CustomFieldFilterLogicChoices, CustomFieldTypeChoices, CustomFieldVisibilityChoices
|
||||
from extras.forms.customfields import CustomFieldsMixin
|
||||
from extras.forms.mixins import CustomFieldsMixin, SavedFiltersMixin
|
||||
from extras.models import CustomField, Tag
|
||||
from utilities.forms import BootstrapMixin, CSVModelForm
|
||||
from utilities.forms.fields import DynamicModelMultipleChoiceField
|
||||
@ -114,7 +114,7 @@ class NetBoxModelBulkEditForm(BootstrapMixin, CustomFieldsMixin, forms.Form):
|
||||
self.nullable_fields = (*self.nullable_fields, *nullable_custom_fields)
|
||||
|
||||
|
||||
class NetBoxModelFilterSetForm(BootstrapMixin, CustomFieldsMixin, forms.Form):
|
||||
class NetBoxModelFilterSetForm(BootstrapMixin, CustomFieldsMixin, SavedFiltersMixin, forms.Form):
|
||||
"""
|
||||
Base form for FilerSet forms. These are used to filter object lists in the NetBox UI. Note that the
|
||||
corresponding FilterSet *must* provide a `q` filter.
|
||||
@ -129,6 +129,15 @@ class NetBoxModelFilterSetForm(BootstrapMixin, CustomFieldsMixin, forms.Form):
|
||||
label='Search'
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# Limit saved filters to those applicable to the form's model
|
||||
content_type = ContentType.objects.get_for_model(self.model)
|
||||
self.fields['filter'].widget.add_query_params({
|
||||
'content_type_id': content_type.pk,
|
||||
})
|
||||
|
||||
def _get_custom_fields(self, content_type):
|
||||
return super()._get_custom_fields(content_type).exclude(
|
||||
Q(filter_logic=CustomFieldFilterLogicChoices.FILTER_DISABLED) |
|
||||
|
@ -3,8 +3,8 @@ import graphene
|
||||
from circuits.graphql.schema import CircuitsQuery
|
||||
from dcim.graphql.schema import DCIMQuery
|
||||
from extras.graphql.schema import ExtrasQuery
|
||||
from extras.registry import registry
|
||||
from ipam.graphql.schema import IPAMQuery
|
||||
from netbox.registry import registry
|
||||
from tenancy.graphql.schema import TenancyQuery
|
||||
from users.graphql.schema import UsersQuery
|
||||
from virtualization.graphql.schema import VirtualizationQuery
|
||||
|
@ -10,8 +10,9 @@ from netbox.models.features import *
|
||||
__all__ = (
|
||||
'ChangeLoggedModel',
|
||||
'NestedGroupModel',
|
||||
'OrganizationalModel',
|
||||
'NetBoxModel',
|
||||
'OrganizationalModel',
|
||||
'PrimaryModel',
|
||||
)
|
||||
|
||||
|
||||
@ -21,6 +22,7 @@ class NetBoxFeatureSet(
|
||||
CustomLinksMixin,
|
||||
CustomValidationMixin,
|
||||
ExportTemplatesMixin,
|
||||
JournalingMixin,
|
||||
TagsMixin,
|
||||
WebhooksMixin
|
||||
):
|
||||
@ -55,11 +57,27 @@ class ChangeLoggedModel(ChangeLoggingMixin, CustomValidationMixin, models.Model)
|
||||
abstract = True
|
||||
|
||||
|
||||
class NetBoxModel(CloningMixin, JournalingMixin, NetBoxFeatureSet, models.Model):
|
||||
class NetBoxModel(CloningMixin, NetBoxFeatureSet, models.Model):
|
||||
"""
|
||||
Base model for most object types. Suitable for use by plugins.
|
||||
"""
|
||||
objects = RestrictedQuerySet.as_manager()
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
class PrimaryModel(NetBoxModel):
|
||||
"""
|
||||
Primary models represent real objects within the infrastructure being modeled.
|
||||
"""
|
||||
objects = RestrictedQuerySet.as_manager()
|
||||
description = models.CharField(
|
||||
max_length=200,
|
||||
blank=True
|
||||
)
|
||||
comments = models.TextField(
|
||||
blank=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
@ -81,6 +99,9 @@ class NestedGroupModel(NetBoxFeatureSet, MPTTModel):
|
||||
name = models.CharField(
|
||||
max_length=100
|
||||
)
|
||||
slug = models.SlugField(
|
||||
max_length=100
|
||||
)
|
||||
description = models.CharField(
|
||||
max_length=200,
|
||||
blank=True
|
||||
@ -134,3 +155,6 @@ class OrganizationalModel(NetBoxFeatureSet, models.Model):
|
||||
class Meta:
|
||||
abstract = True
|
||||
ordering = ('name',)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
@ -1,4 +1,4 @@
|
||||
from extras.registry import registry
|
||||
from netbox.registry import registry
|
||||
from . import *
|
||||
|
||||
|
||||
@ -279,6 +279,7 @@ OTHER_MENU = Menu(
|
||||
get_model_item('extras', 'customfield', 'Custom Fields'),
|
||||
get_model_item('extras', 'customlink', 'Custom Links'),
|
||||
get_model_item('extras', 'exporttemplate', 'Export Templates'),
|
||||
get_model_item('extras', 'savedfilter', 'Saved Filters'),
|
||||
),
|
||||
),
|
||||
MenuGroup(
|
||||
|
@ -1,4 +1,4 @@
|
||||
from extras.registry import registry
|
||||
from netbox.registry import registry
|
||||
from users.preferences import UserPreference
|
||||
from utilities.paginator import EnhancedPaginator
|
||||
|
||||
|
@ -2,7 +2,7 @@ from collections import namedtuple
|
||||
|
||||
from django.db import models
|
||||
|
||||
from extras.registry import registry
|
||||
from netbox.registry import registry
|
||||
|
||||
ObjectFieldValue = namedtuple('ObjectFieldValue', ('name', 'type', 'weight', 'value'))
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user