mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-24 09:28:38 -06:00
Merge pull request #8414 from netbox-community/8392-plugins-features
Closes #8392: Enable NetBox features for plugin models
This commit is contained in:
commit
7002319cc8
@ -82,6 +82,10 @@ markdown-include
|
|||||||
# https://github.com/squidfunk/mkdocs-material
|
# https://github.com/squidfunk/mkdocs-material
|
||||||
mkdocs-material
|
mkdocs-material
|
||||||
|
|
||||||
|
# Introspection for embedded code
|
||||||
|
# https://github.com/mkdocstrings/mkdocstrings
|
||||||
|
mkdocstrings
|
||||||
|
|
||||||
# Library for manipulating IP prefixes and addresses
|
# Library for manipulating IP prefixes and addresses
|
||||||
# https://github.com/drkjam/netaddr
|
# https://github.com/drkjam/netaddr
|
||||||
netaddr
|
netaddr
|
||||||
|
3
docs/plugins/development/index.md
Normal file
3
docs/plugins/development/index.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# Plugins Development
|
||||||
|
|
||||||
|
TODO
|
64
docs/plugins/development/model-features.md
Normal file
64
docs/plugins/development/model-features.md
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
# Model Features
|
||||||
|
|
||||||
|
## Enabling NetBox Features
|
||||||
|
|
||||||
|
Plugin models can leverage certain NetBox features by inheriting from NetBox's `BaseModel` class. This class extends the plugin model to enable numerous feature, including:
|
||||||
|
|
||||||
|
* Custom fields
|
||||||
|
* Custom links
|
||||||
|
* Custom validation
|
||||||
|
* Export templates
|
||||||
|
* Journaling
|
||||||
|
* Tags
|
||||||
|
* Webhooks
|
||||||
|
|
||||||
|
This class performs two crucial functions:
|
||||||
|
|
||||||
|
1. Apply any fields, methods, or attributes necessary to the operation of these features
|
||||||
|
2. Register the model with NetBox as utilizing these feature
|
||||||
|
|
||||||
|
Simply subclass BaseModel when defining a model in your plugin:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# models.py
|
||||||
|
from netbox.models import BaseModel
|
||||||
|
|
||||||
|
class MyModel(BaseModel):
|
||||||
|
foo = models.CharField()
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Enabling Features Individually
|
||||||
|
|
||||||
|
If you prefer instead to enable only a subset of these features for a plugin model, NetBox provides a discrete "mix-in" class for each feature. You can subclass each of these individually when defining your model. (You will also need to inherit from Django's built-in `Model` class.)
|
||||||
|
|
||||||
|
```python
|
||||||
|
# models.py
|
||||||
|
from django.db.models import models
|
||||||
|
from netbox.models.features import ExportTemplatesMixin, TagsMixin
|
||||||
|
|
||||||
|
class MyModel(ExportTemplatesMixin, TagsMixin, models.Model):
|
||||||
|
foo = models.CharField()
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
The example above will enable export templates and tags, but no other NetBox features. A complete list of available feature mixins is included below. (Inheriting all the available mixins is essentially the same as subclassing `BaseModel`.)
|
||||||
|
|
||||||
|
## Feature Mixins Reference
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
Please note that only the classes which appear in this documentation are currently supported. Although other classes may be present within the `features` module, they are not yet supported for use by plugins.
|
||||||
|
|
||||||
|
::: netbox.models.features.CustomLinksMixin
|
||||||
|
|
||||||
|
::: netbox.models.features.CustomFieldsMixin
|
||||||
|
|
||||||
|
::: netbox.models.features.CustomValidationMixin
|
||||||
|
|
||||||
|
::: netbox.models.features.ExportTemplatesMixin
|
||||||
|
|
||||||
|
::: netbox.models.features.JournalingMixin
|
||||||
|
|
||||||
|
::: netbox.models.features.TagsMixin
|
||||||
|
|
||||||
|
::: netbox.models.features.WebhooksMixin
|
@ -1,7 +0,0 @@
|
|||||||
# File inclusion plugin for Python-Markdown
|
|
||||||
# https://github.com/cmacmackin/markdown-include
|
|
||||||
markdown-include
|
|
||||||
|
|
||||||
# MkDocs Material theme (for documentation build)
|
|
||||||
# https://github.com/squidfunk/mkdocs-material
|
|
||||||
mkdocs-material
|
|
20
mkdocs.yml
20
mkdocs.yml
@ -16,6 +16,21 @@ theme:
|
|||||||
toggle:
|
toggle:
|
||||||
icon: material/lightbulb
|
icon: material/lightbulb
|
||||||
name: Switch to Light Mode
|
name: Switch to Light Mode
|
||||||
|
plugins:
|
||||||
|
- mkdocstrings:
|
||||||
|
handlers:
|
||||||
|
python:
|
||||||
|
setup_commands:
|
||||||
|
- import os
|
||||||
|
- import django
|
||||||
|
- os.chdir('netbox/')
|
||||||
|
- os.environ.setdefault("DJANGO_SETTINGS_MODULE", "netbox.settings")
|
||||||
|
- django.setup()
|
||||||
|
rendering:
|
||||||
|
heading_level: 3
|
||||||
|
show_root_heading: true
|
||||||
|
show_root_full_path: false
|
||||||
|
show_root_toc_entry: false
|
||||||
extra:
|
extra:
|
||||||
social:
|
social:
|
||||||
- icon: fontawesome/brands/github
|
- icon: fontawesome/brands/github
|
||||||
@ -84,7 +99,10 @@ nav:
|
|||||||
- Webhooks: 'additional-features/webhooks.md'
|
- Webhooks: 'additional-features/webhooks.md'
|
||||||
- Plugins:
|
- Plugins:
|
||||||
- Using Plugins: 'plugins/index.md'
|
- Using Plugins: 'plugins/index.md'
|
||||||
- Developing Plugins: 'plugins/development.md'
|
- Developing Plugins:
|
||||||
|
- Introduction: 'plugins/development/index.md'
|
||||||
|
- Model Features: 'plugins/development/model-features.md'
|
||||||
|
- Developing Plugins (Old): 'plugins/development.md'
|
||||||
- Administration:
|
- Administration:
|
||||||
- Authentication: 'administration/authentication.md'
|
- Authentication: 'administration/authentication.md'
|
||||||
- Permissions: 'administration/permissions.md'
|
- Permissions: 'administration/permissions.md'
|
||||||
|
@ -5,8 +5,8 @@ from django.urls import reverse
|
|||||||
|
|
||||||
from circuits.choices import *
|
from circuits.choices import *
|
||||||
from dcim.models import LinkTermination
|
from dcim.models import LinkTermination
|
||||||
from extras.utils import extras_features
|
|
||||||
from netbox.models import ChangeLoggedModel, OrganizationalModel, PrimaryModel
|
from netbox.models import ChangeLoggedModel, OrganizationalModel, PrimaryModel
|
||||||
|
from netbox.models.features import WebhooksMixin
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'Circuit',
|
'Circuit',
|
||||||
@ -15,7 +15,6 @@ __all__ = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class CircuitType(OrganizationalModel):
|
class CircuitType(OrganizationalModel):
|
||||||
"""
|
"""
|
||||||
Circuits can be organized by their functional role. For example, a user might wish to define CircuitTypes named
|
Circuits can be organized by their functional role. For example, a user might wish to define CircuitTypes named
|
||||||
@ -44,7 +43,6 @@ class CircuitType(OrganizationalModel):
|
|||||||
return reverse('circuits:circuittype', args=[self.pk])
|
return reverse('circuits:circuittype', args=[self.pk])
|
||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class Circuit(PrimaryModel):
|
class Circuit(PrimaryModel):
|
||||||
"""
|
"""
|
||||||
A communications circuit connects two points. Each Circuit belongs to a Provider; Providers may have multiple
|
A communications circuit connects two points. Each Circuit belongs to a Provider; Providers may have multiple
|
||||||
@ -138,8 +136,7 @@ class Circuit(PrimaryModel):
|
|||||||
return CircuitStatusChoices.colors.get(self.status, 'secondary')
|
return CircuitStatusChoices.colors.get(self.status, 'secondary')
|
||||||
|
|
||||||
|
|
||||||
@extras_features('webhooks')
|
class CircuitTermination(WebhooksMixin, ChangeLoggedModel, LinkTermination):
|
||||||
class CircuitTermination(ChangeLoggedModel, LinkTermination):
|
|
||||||
circuit = models.ForeignKey(
|
circuit = models.ForeignKey(
|
||||||
to='circuits.Circuit',
|
to='circuits.Circuit',
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
|
@ -3,7 +3,6 @@ from django.db import models
|
|||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
from dcim.fields import ASNField
|
from dcim.fields import ASNField
|
||||||
from extras.utils import extras_features
|
|
||||||
from netbox.models import PrimaryModel
|
from netbox.models import PrimaryModel
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
@ -12,7 +11,6 @@ __all__ = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class Provider(PrimaryModel):
|
class Provider(PrimaryModel):
|
||||||
"""
|
"""
|
||||||
Each Circuit belongs to a Provider. This is usually a telecommunications company or similar organization. This model
|
Each Circuit belongs to a Provider. This is usually a telecommunications company or similar organization. This model
|
||||||
@ -72,7 +70,6 @@ class Provider(PrimaryModel):
|
|||||||
return reverse('circuits:provider', args=[self.pk])
|
return reverse('circuits:provider', args=[self.pk])
|
||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class ProviderNetwork(PrimaryModel):
|
class ProviderNetwork(PrimaryModel):
|
||||||
"""
|
"""
|
||||||
This represents a provider network which exists outside of NetBox, the details of which are unknown or
|
This represents a provider network which exists outside of NetBox, the details of which are unknown or
|
||||||
|
@ -11,7 +11,6 @@ from dcim.choices import *
|
|||||||
from dcim.constants import *
|
from dcim.constants import *
|
||||||
from dcim.fields import PathField
|
from dcim.fields import PathField
|
||||||
from dcim.utils import decompile_path_node, object_to_path_node, path_node_to_object
|
from dcim.utils import decompile_path_node, object_to_path_node, path_node_to_object
|
||||||
from extras.utils import extras_features
|
|
||||||
from netbox.models import BigIDModel, PrimaryModel
|
from netbox.models import BigIDModel, PrimaryModel
|
||||||
from utilities.fields import ColorField
|
from utilities.fields import ColorField
|
||||||
from utilities.utils import to_meters
|
from utilities.utils import to_meters
|
||||||
@ -29,7 +28,6 @@ __all__ = (
|
|||||||
# Cables
|
# Cables
|
||||||
#
|
#
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class Cable(PrimaryModel):
|
class Cable(PrimaryModel):
|
||||||
"""
|
"""
|
||||||
A physical connection between two endpoints.
|
A physical connection between two endpoints.
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.core.exceptions import ObjectDoesNotExist, ValidationError
|
from django.core.exceptions import ObjectDoesNotExist, ValidationError
|
||||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||||
@ -7,8 +7,8 @@ from mptt.models import MPTTModel, TreeForeignKey
|
|||||||
|
|
||||||
from dcim.choices import *
|
from dcim.choices import *
|
||||||
from dcim.constants import *
|
from dcim.constants import *
|
||||||
from extras.utils import extras_features
|
|
||||||
from netbox.models import ChangeLoggedModel
|
from netbox.models import ChangeLoggedModel
|
||||||
|
from netbox.models.features import WebhooksMixin
|
||||||
from utilities.fields import ColorField, NaturalOrderingField
|
from utilities.fields import ColorField, NaturalOrderingField
|
||||||
from utilities.mptt import TreeManager
|
from utilities.mptt import TreeManager
|
||||||
from utilities.ordering import naturalize_interface
|
from utilities.ordering import naturalize_interface
|
||||||
@ -32,7 +32,7 @@ __all__ = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ComponentTemplateModel(ChangeLoggedModel):
|
class ComponentTemplateModel(WebhooksMixin, ChangeLoggedModel):
|
||||||
device_type = models.ForeignKey(
|
device_type = models.ForeignKey(
|
||||||
to='dcim.DeviceType',
|
to='dcim.DeviceType',
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
@ -135,7 +135,6 @@ class ModularComponentTemplateModel(ComponentTemplateModel):
|
|||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
@extras_features('webhooks')
|
|
||||||
class ConsolePortTemplate(ModularComponentTemplateModel):
|
class ConsolePortTemplate(ModularComponentTemplateModel):
|
||||||
"""
|
"""
|
||||||
A template for a ConsolePort to be created for a new Device.
|
A template for a ConsolePort to be created for a new Device.
|
||||||
@ -164,7 +163,6 @@ class ConsolePortTemplate(ModularComponentTemplateModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@extras_features('webhooks')
|
|
||||||
class ConsoleServerPortTemplate(ModularComponentTemplateModel):
|
class ConsoleServerPortTemplate(ModularComponentTemplateModel):
|
||||||
"""
|
"""
|
||||||
A template for a ConsoleServerPort to be created for a new Device.
|
A template for a ConsoleServerPort to be created for a new Device.
|
||||||
@ -193,7 +191,6 @@ class ConsoleServerPortTemplate(ModularComponentTemplateModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@extras_features('webhooks')
|
|
||||||
class PowerPortTemplate(ModularComponentTemplateModel):
|
class PowerPortTemplate(ModularComponentTemplateModel):
|
||||||
"""
|
"""
|
||||||
A template for a PowerPort to be created for a new Device.
|
A template for a PowerPort to be created for a new Device.
|
||||||
@ -245,7 +242,6 @@ class PowerPortTemplate(ModularComponentTemplateModel):
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@extras_features('webhooks')
|
|
||||||
class PowerOutletTemplate(ModularComponentTemplateModel):
|
class PowerOutletTemplate(ModularComponentTemplateModel):
|
||||||
"""
|
"""
|
||||||
A template for a PowerOutlet to be created for a new Device.
|
A template for a PowerOutlet to be created for a new Device.
|
||||||
@ -307,7 +303,6 @@ class PowerOutletTemplate(ModularComponentTemplateModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@extras_features('webhooks')
|
|
||||||
class InterfaceTemplate(ModularComponentTemplateModel):
|
class InterfaceTemplate(ModularComponentTemplateModel):
|
||||||
"""
|
"""
|
||||||
A template for a physical data interface on a new Device.
|
A template for a physical data interface on a new Device.
|
||||||
@ -347,7 +342,6 @@ class InterfaceTemplate(ModularComponentTemplateModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@extras_features('webhooks')
|
|
||||||
class FrontPortTemplate(ModularComponentTemplateModel):
|
class FrontPortTemplate(ModularComponentTemplateModel):
|
||||||
"""
|
"""
|
||||||
Template for a pass-through port on the front of a new Device.
|
Template for a pass-through port on the front of a new Device.
|
||||||
@ -420,7 +414,6 @@ class FrontPortTemplate(ModularComponentTemplateModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@extras_features('webhooks')
|
|
||||||
class RearPortTemplate(ModularComponentTemplateModel):
|
class RearPortTemplate(ModularComponentTemplateModel):
|
||||||
"""
|
"""
|
||||||
Template for a pass-through port on the rear of a new Device.
|
Template for a pass-through port on the rear of a new Device.
|
||||||
@ -460,7 +453,6 @@ class RearPortTemplate(ModularComponentTemplateModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@extras_features('webhooks')
|
|
||||||
class ModuleBayTemplate(ComponentTemplateModel):
|
class ModuleBayTemplate(ComponentTemplateModel):
|
||||||
"""
|
"""
|
||||||
A template for a ModuleBay to be created for a new parent Device.
|
A template for a ModuleBay to be created for a new parent Device.
|
||||||
@ -486,7 +478,6 @@ class ModuleBayTemplate(ComponentTemplateModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@extras_features('webhooks')
|
|
||||||
class DeviceBayTemplate(ComponentTemplateModel):
|
class DeviceBayTemplate(ComponentTemplateModel):
|
||||||
"""
|
"""
|
||||||
A template for a DeviceBay to be created for a new parent Device.
|
A template for a DeviceBay to be created for a new parent Device.
|
||||||
@ -511,7 +502,6 @@ class DeviceBayTemplate(ComponentTemplateModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@extras_features('webhooks')
|
|
||||||
class InventoryItemTemplate(MPTTModel, ComponentTemplateModel):
|
class InventoryItemTemplate(MPTTModel, ComponentTemplateModel):
|
||||||
"""
|
"""
|
||||||
A template for an InventoryItem to be created for a new parent Device.
|
A template for an InventoryItem to be created for a new parent Device.
|
||||||
|
@ -11,7 +11,6 @@ from dcim.choices import *
|
|||||||
from dcim.constants import *
|
from dcim.constants import *
|
||||||
from dcim.fields import MACAddressField, WWNField
|
from dcim.fields import MACAddressField, WWNField
|
||||||
from dcim.svg import CableTraceSVG
|
from dcim.svg import CableTraceSVG
|
||||||
from extras.utils import extras_features
|
|
||||||
from netbox.models import OrganizationalModel, PrimaryModel
|
from netbox.models import OrganizationalModel, PrimaryModel
|
||||||
from utilities.choices import ColorChoices
|
from utilities.choices import ColorChoices
|
||||||
from utilities.fields import ColorField, NaturalOrderingField
|
from utilities.fields import ColorField, NaturalOrderingField
|
||||||
@ -254,7 +253,6 @@ class PathEndpoint(models.Model):
|
|||||||
# Console components
|
# Console components
|
||||||
#
|
#
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class ConsolePort(ModularComponentModel, LinkTermination, PathEndpoint):
|
class ConsolePort(ModularComponentModel, LinkTermination, PathEndpoint):
|
||||||
"""
|
"""
|
||||||
A physical console port within a Device. ConsolePorts connect to ConsoleServerPorts.
|
A physical console port within a Device. ConsolePorts connect to ConsoleServerPorts.
|
||||||
@ -282,7 +280,6 @@ class ConsolePort(ModularComponentModel, LinkTermination, PathEndpoint):
|
|||||||
return reverse('dcim:consoleport', kwargs={'pk': self.pk})
|
return reverse('dcim:consoleport', kwargs={'pk': self.pk})
|
||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class ConsoleServerPort(ModularComponentModel, LinkTermination, PathEndpoint):
|
class ConsoleServerPort(ModularComponentModel, LinkTermination, PathEndpoint):
|
||||||
"""
|
"""
|
||||||
A physical port within a Device (typically a designated console server) which provides access to ConsolePorts.
|
A physical port within a Device (typically a designated console server) which provides access to ConsolePorts.
|
||||||
@ -314,7 +311,6 @@ class ConsoleServerPort(ModularComponentModel, LinkTermination, PathEndpoint):
|
|||||||
# Power components
|
# Power components
|
||||||
#
|
#
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class PowerPort(ModularComponentModel, LinkTermination, PathEndpoint):
|
class PowerPort(ModularComponentModel, LinkTermination, PathEndpoint):
|
||||||
"""
|
"""
|
||||||
A physical power supply (intake) port within a Device. PowerPorts connect to PowerOutlets.
|
A physical power supply (intake) port within a Device. PowerPorts connect to PowerOutlets.
|
||||||
@ -407,7 +403,6 @@ class PowerPort(ModularComponentModel, LinkTermination, PathEndpoint):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class PowerOutlet(ModularComponentModel, LinkTermination, PathEndpoint):
|
class PowerOutlet(ModularComponentModel, LinkTermination, PathEndpoint):
|
||||||
"""
|
"""
|
||||||
A physical power outlet (output) within a Device which provides power to a PowerPort.
|
A physical power outlet (output) within a Device which provides power to a PowerPort.
|
||||||
@ -522,7 +517,6 @@ class BaseInterface(models.Model):
|
|||||||
return self.fhrp_group_assignments.count()
|
return self.fhrp_group_assignments.count()
|
||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class Interface(ModularComponentModel, BaseInterface, LinkTermination, PathEndpoint):
|
class Interface(ModularComponentModel, BaseInterface, LinkTermination, PathEndpoint):
|
||||||
"""
|
"""
|
||||||
A network interface within a Device. A physical Interface can connect to exactly one other Interface.
|
A network interface within a Device. A physical Interface can connect to exactly one other Interface.
|
||||||
@ -793,7 +787,6 @@ class Interface(ModularComponentModel, BaseInterface, LinkTermination, PathEndpo
|
|||||||
# Pass-through ports
|
# Pass-through ports
|
||||||
#
|
#
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class FrontPort(ModularComponentModel, LinkTermination):
|
class FrontPort(ModularComponentModel, LinkTermination):
|
||||||
"""
|
"""
|
||||||
A pass-through port on the front of a Device.
|
A pass-through port on the front of a Device.
|
||||||
@ -847,7 +840,6 @@ class FrontPort(ModularComponentModel, LinkTermination):
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class RearPort(ModularComponentModel, LinkTermination):
|
class RearPort(ModularComponentModel, LinkTermination):
|
||||||
"""
|
"""
|
||||||
A pass-through port on the rear of a Device.
|
A pass-through port on the rear of a Device.
|
||||||
@ -891,7 +883,6 @@ class RearPort(ModularComponentModel, LinkTermination):
|
|||||||
# Bays
|
# Bays
|
||||||
#
|
#
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class ModuleBay(ComponentModel):
|
class ModuleBay(ComponentModel):
|
||||||
"""
|
"""
|
||||||
An empty space within a Device which can house a child device
|
An empty space within a Device which can house a child device
|
||||||
@ -912,7 +903,6 @@ class ModuleBay(ComponentModel):
|
|||||||
return reverse('dcim:modulebay', kwargs={'pk': self.pk})
|
return reverse('dcim:modulebay', kwargs={'pk': self.pk})
|
||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class DeviceBay(ComponentModel):
|
class DeviceBay(ComponentModel):
|
||||||
"""
|
"""
|
||||||
An empty space within a Device which can house a child device
|
An empty space within a Device which can house a child device
|
||||||
@ -963,7 +953,6 @@ class DeviceBay(ComponentModel):
|
|||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class InventoryItemRole(OrganizationalModel):
|
class InventoryItemRole(OrganizationalModel):
|
||||||
"""
|
"""
|
||||||
Inventory items may optionally be assigned a functional role.
|
Inventory items may optionally be assigned a functional role.
|
||||||
@ -994,7 +983,6 @@ class InventoryItemRole(OrganizationalModel):
|
|||||||
return reverse('dcim:inventoryitemrole', args=[self.pk])
|
return reverse('dcim:inventoryitemrole', args=[self.pk])
|
||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class InventoryItem(MPTTModel, ComponentModel):
|
class InventoryItem(MPTTModel, ComponentModel):
|
||||||
"""
|
"""
|
||||||
An InventoryItem represents a serialized piece of hardware within a Device, such as a line card or power supply.
|
An InventoryItem represents a serialized piece of hardware within a Device, such as a line card or power supply.
|
||||||
|
@ -13,7 +13,6 @@ from dcim.choices import *
|
|||||||
from dcim.constants import *
|
from dcim.constants import *
|
||||||
from extras.models import ConfigContextModel
|
from extras.models import ConfigContextModel
|
||||||
from extras.querysets import ConfigContextModelQuerySet
|
from extras.querysets import ConfigContextModelQuerySet
|
||||||
from extras.utils import extras_features
|
|
||||||
from netbox.config import ConfigItem
|
from netbox.config import ConfigItem
|
||||||
from netbox.models import OrganizationalModel, PrimaryModel
|
from netbox.models import OrganizationalModel, PrimaryModel
|
||||||
from utilities.choices import ColorChoices
|
from utilities.choices import ColorChoices
|
||||||
@ -37,7 +36,6 @@ __all__ = (
|
|||||||
# Device Types
|
# Device Types
|
||||||
#
|
#
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class Manufacturer(OrganizationalModel):
|
class Manufacturer(OrganizationalModel):
|
||||||
"""
|
"""
|
||||||
A Manufacturer represents a company which produces hardware devices; for example, Juniper or Dell.
|
A Manufacturer represents a company which produces hardware devices; for example, Juniper or Dell.
|
||||||
@ -70,7 +68,6 @@ class Manufacturer(OrganizationalModel):
|
|||||||
return reverse('dcim:manufacturer', args=[self.pk])
|
return reverse('dcim:manufacturer', args=[self.pk])
|
||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class DeviceType(PrimaryModel):
|
class DeviceType(PrimaryModel):
|
||||||
"""
|
"""
|
||||||
A DeviceType represents a particular make (Manufacturer) and model of device. It specifies rack height and depth, as
|
A DeviceType represents a particular make (Manufacturer) and model of device. It specifies rack height and depth, as
|
||||||
@ -353,7 +350,6 @@ class DeviceType(PrimaryModel):
|
|||||||
return self.subdevice_role == SubdeviceRoleChoices.ROLE_CHILD
|
return self.subdevice_role == SubdeviceRoleChoices.ROLE_CHILD
|
||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class ModuleType(PrimaryModel):
|
class ModuleType(PrimaryModel):
|
||||||
"""
|
"""
|
||||||
A ModuleType represents a hardware element that can be installed within a device and which houses additional
|
A ModuleType represents a hardware element that can be installed within a device and which houses additional
|
||||||
@ -487,7 +483,6 @@ class ModuleType(PrimaryModel):
|
|||||||
# Devices
|
# Devices
|
||||||
#
|
#
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class DeviceRole(OrganizationalModel):
|
class DeviceRole(OrganizationalModel):
|
||||||
"""
|
"""
|
||||||
Devices are organized by functional role; for example, "Core Switch" or "File Server". Each DeviceRole is assigned a
|
Devices are organized by functional role; for example, "Core Switch" or "File Server". Each DeviceRole is assigned a
|
||||||
@ -525,7 +520,6 @@ class DeviceRole(OrganizationalModel):
|
|||||||
return reverse('dcim:devicerole', args=[self.pk])
|
return reverse('dcim:devicerole', args=[self.pk])
|
||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class Platform(OrganizationalModel):
|
class Platform(OrganizationalModel):
|
||||||
"""
|
"""
|
||||||
Platform refers to the software or firmware running on a Device. For example, "Cisco IOS-XR" or "Juniper Junos".
|
Platform refers to the software or firmware running on a Device. For example, "Cisco IOS-XR" or "Juniper Junos".
|
||||||
@ -575,7 +569,6 @@ class Platform(OrganizationalModel):
|
|||||||
return reverse('dcim:platform', args=[self.pk])
|
return reverse('dcim:platform', args=[self.pk])
|
||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class Device(PrimaryModel, ConfigContextModel):
|
class Device(PrimaryModel, ConfigContextModel):
|
||||||
"""
|
"""
|
||||||
A Device represents a piece of physical hardware mounted within a Rack. Each Device is assigned a DeviceType,
|
A Device represents a piece of physical hardware mounted within a Rack. Each Device is assigned a DeviceType,
|
||||||
@ -1012,7 +1005,6 @@ class Device(PrimaryModel, ConfigContextModel):
|
|||||||
return DeviceStatusChoices.colors.get(self.status, 'secondary')
|
return DeviceStatusChoices.colors.get(self.status, 'secondary')
|
||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class Module(PrimaryModel, ConfigContextModel):
|
class Module(PrimaryModel, ConfigContextModel):
|
||||||
"""
|
"""
|
||||||
A Module represents a field-installable component within a Device which may itself hold multiple device components
|
A Module represents a field-installable component within a Device which may itself hold multiple device components
|
||||||
@ -1095,7 +1087,6 @@ class Module(PrimaryModel, ConfigContextModel):
|
|||||||
# Virtual chassis
|
# Virtual chassis
|
||||||
#
|
#
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class VirtualChassis(PrimaryModel):
|
class VirtualChassis(PrimaryModel):
|
||||||
"""
|
"""
|
||||||
A collection of Devices which operate with a shared control plane (e.g. a switch stack).
|
A collection of Devices which operate with a shared control plane (e.g. a switch stack).
|
||||||
|
@ -6,7 +6,6 @@ from django.urls import reverse
|
|||||||
|
|
||||||
from dcim.choices import *
|
from dcim.choices import *
|
||||||
from dcim.constants import *
|
from dcim.constants import *
|
||||||
from extras.utils import extras_features
|
|
||||||
from netbox.models import PrimaryModel
|
from netbox.models import PrimaryModel
|
||||||
from utilities.validators import ExclusionValidator
|
from utilities.validators import ExclusionValidator
|
||||||
from .device_components import LinkTermination, PathEndpoint
|
from .device_components import LinkTermination, PathEndpoint
|
||||||
@ -21,7 +20,6 @@ __all__ = (
|
|||||||
# Power
|
# Power
|
||||||
#
|
#
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class PowerPanel(PrimaryModel):
|
class PowerPanel(PrimaryModel):
|
||||||
"""
|
"""
|
||||||
A distribution point for electrical power; e.g. a data center RPP.
|
A distribution point for electrical power; e.g. a data center RPP.
|
||||||
@ -68,7 +66,6 @@ class PowerPanel(PrimaryModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class PowerFeed(PrimaryModel, PathEndpoint, LinkTermination):
|
class PowerFeed(PrimaryModel, PathEndpoint, LinkTermination):
|
||||||
"""
|
"""
|
||||||
An electrical circuit delivered from a PowerPanel.
|
An electrical circuit delivered from a PowerPanel.
|
||||||
|
@ -13,7 +13,6 @@ from django.urls import reverse
|
|||||||
from dcim.choices import *
|
from dcim.choices import *
|
||||||
from dcim.constants import *
|
from dcim.constants import *
|
||||||
from dcim.svg import RackElevationSVG
|
from dcim.svg import RackElevationSVG
|
||||||
from extras.utils import extras_features
|
|
||||||
from netbox.config import get_config
|
from netbox.config import get_config
|
||||||
from netbox.models import OrganizationalModel, PrimaryModel
|
from netbox.models import OrganizationalModel, PrimaryModel
|
||||||
from utilities.choices import ColorChoices
|
from utilities.choices import ColorChoices
|
||||||
@ -34,7 +33,6 @@ __all__ = (
|
|||||||
# Racks
|
# Racks
|
||||||
#
|
#
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class RackRole(OrganizationalModel):
|
class RackRole(OrganizationalModel):
|
||||||
"""
|
"""
|
||||||
Racks can be organized by functional role, similar to Devices.
|
Racks can be organized by functional role, similar to Devices.
|
||||||
@ -65,7 +63,6 @@ class RackRole(OrganizationalModel):
|
|||||||
return reverse('dcim:rackrole', args=[self.pk])
|
return reverse('dcim:rackrole', args=[self.pk])
|
||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class Rack(PrimaryModel):
|
class Rack(PrimaryModel):
|
||||||
"""
|
"""
|
||||||
Devices are housed within Racks. Each rack has a defined height measured in rack units, and a front and rear face.
|
Devices are housed within Racks. Each rack has a defined height measured in rack units, and a front and rear face.
|
||||||
@ -438,7 +435,6 @@ class Rack(PrimaryModel):
|
|||||||
return int(allocated_draw_total / available_power_total * 100)
|
return int(allocated_draw_total / available_power_total * 100)
|
||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class RackReservation(PrimaryModel):
|
class RackReservation(PrimaryModel):
|
||||||
"""
|
"""
|
||||||
One or more reserved units within a Rack.
|
One or more reserved units within a Rack.
|
||||||
|
@ -7,8 +7,6 @@ from timezone_field import TimeZoneField
|
|||||||
|
|
||||||
from dcim.choices import *
|
from dcim.choices import *
|
||||||
from dcim.constants import *
|
from dcim.constants import *
|
||||||
from dcim.fields import ASNField
|
|
||||||
from extras.utils import extras_features
|
|
||||||
from netbox.models import NestedGroupModel, PrimaryModel
|
from netbox.models import NestedGroupModel, PrimaryModel
|
||||||
from utilities.fields import NaturalOrderingField
|
from utilities.fields import NaturalOrderingField
|
||||||
|
|
||||||
@ -24,7 +22,6 @@ __all__ = (
|
|||||||
# Regions
|
# Regions
|
||||||
#
|
#
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class Region(NestedGroupModel):
|
class Region(NestedGroupModel):
|
||||||
"""
|
"""
|
||||||
A region represents a geographic collection of sites. For example, you might create regions representing countries,
|
A region represents a geographic collection of sites. For example, you might create regions representing countries,
|
||||||
@ -111,7 +108,6 @@ class Region(NestedGroupModel):
|
|||||||
# Site groups
|
# Site groups
|
||||||
#
|
#
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class SiteGroup(NestedGroupModel):
|
class SiteGroup(NestedGroupModel):
|
||||||
"""
|
"""
|
||||||
A site group is an arbitrary grouping of sites. For example, you might have corporate sites and customer sites; and
|
A site group is an arbitrary grouping of sites. For example, you might have corporate sites and customer sites; and
|
||||||
@ -198,7 +194,6 @@ class SiteGroup(NestedGroupModel):
|
|||||||
# Sites
|
# Sites
|
||||||
#
|
#
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class Site(PrimaryModel):
|
class Site(PrimaryModel):
|
||||||
"""
|
"""
|
||||||
A Site represents a geographic location within a network; typically a building or campus. The optional facility
|
A Site represents a geographic location within a network; typically a building or campus. The optional facility
|
||||||
@ -322,7 +317,6 @@ class Site(PrimaryModel):
|
|||||||
# Locations
|
# Locations
|
||||||
#
|
#
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class Location(NestedGroupModel):
|
class Location(NestedGroupModel):
|
||||||
"""
|
"""
|
||||||
A Location represents a subgroup of Racks and/or Devices within a Site. A Location may represent a building within a
|
A Location represents a subgroup of Racks and/or Devices within a Site. A Location may represent a building within a
|
||||||
|
@ -7,6 +7,7 @@ EXTRAS_FEATURES = [
|
|||||||
'custom_links',
|
'custom_links',
|
||||||
'export_templates',
|
'export_templates',
|
||||||
'job_results',
|
'job_results',
|
||||||
|
'journaling',
|
||||||
'tags',
|
'tags',
|
||||||
'webhooks'
|
'webhooks'
|
||||||
]
|
]
|
||||||
|
@ -5,8 +5,8 @@ from django.db import models
|
|||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
from extras.querysets import ConfigContextQuerySet
|
from extras.querysets import ConfigContextQuerySet
|
||||||
from extras.utils import extras_features
|
|
||||||
from netbox.models import ChangeLoggedModel
|
from netbox.models import ChangeLoggedModel
|
||||||
|
from netbox.models.features import WebhooksMixin
|
||||||
from utilities.utils import deepmerge
|
from utilities.utils import deepmerge
|
||||||
|
|
||||||
|
|
||||||
@ -20,8 +20,7 @@ __all__ = (
|
|||||||
# Config contexts
|
# Config contexts
|
||||||
#
|
#
|
||||||
|
|
||||||
@extras_features('webhooks')
|
class ConfigContext(WebhooksMixin, ChangeLoggedModel):
|
||||||
class ConfigContext(ChangeLoggedModel):
|
|
||||||
"""
|
"""
|
||||||
A ConfigContext represents a set of arbitrary data available to any Device or VirtualMachine matching its assigned
|
A ConfigContext represents a set of arbitrary data available to any Device or VirtualMachine matching its assigned
|
||||||
qualifiers (region, site, etc.). For example, the data stored in a ConfigContext assigned to site A and tenant B
|
qualifiers (region, site, etc.). For example, the data stored in a ConfigContext assigned to site A and tenant B
|
||||||
|
@ -12,8 +12,9 @@ from django.utils.html import escape
|
|||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
|
|
||||||
from extras.choices import *
|
from extras.choices import *
|
||||||
from extras.utils import FeatureQuery, extras_features
|
from extras.utils import FeatureQuery
|
||||||
from netbox.models import ChangeLoggedModel
|
from netbox.models import ChangeLoggedModel
|
||||||
|
from netbox.models.features import ExportTemplatesMixin, WebhooksMixin
|
||||||
from utilities import filters
|
from utilities import filters
|
||||||
from utilities.forms import (
|
from utilities.forms import (
|
||||||
CSVChoiceField, CSVMultipleChoiceField, DatePicker, DynamicModelChoiceField, DynamicModelMultipleChoiceField,
|
CSVChoiceField, CSVMultipleChoiceField, DatePicker, DynamicModelChoiceField, DynamicModelMultipleChoiceField,
|
||||||
@ -40,8 +41,7 @@ class CustomFieldManager(models.Manager.from_queryset(RestrictedQuerySet)):
|
|||||||
return self.get_queryset().filter(content_types=content_type)
|
return self.get_queryset().filter(content_types=content_type)
|
||||||
|
|
||||||
|
|
||||||
@extras_features('webhooks', 'export_templates')
|
class CustomField(ExportTemplatesMixin, WebhooksMixin, ChangeLoggedModel):
|
||||||
class CustomField(ChangeLoggedModel):
|
|
||||||
content_types = models.ManyToManyField(
|
content_types = models.ManyToManyField(
|
||||||
to=ContentType,
|
to=ContentType,
|
||||||
related_name='custom_fields',
|
related_name='custom_fields',
|
||||||
|
@ -17,8 +17,9 @@ from rest_framework.utils.encoders import JSONEncoder
|
|||||||
from extras.choices import *
|
from extras.choices import *
|
||||||
from extras.constants import *
|
from extras.constants import *
|
||||||
from extras.conditions import ConditionSet
|
from extras.conditions import ConditionSet
|
||||||
from extras.utils import extras_features, FeatureQuery, image_upload
|
from extras.utils import FeatureQuery, image_upload
|
||||||
from netbox.models import BigIDModel, ChangeLoggedModel
|
from netbox.models import BigIDModel, ChangeLoggedModel
|
||||||
|
from netbox.models.features import ExportTemplatesMixin, JobResultsMixin, WebhooksMixin
|
||||||
from utilities.querysets import RestrictedQuerySet
|
from utilities.querysets import RestrictedQuerySet
|
||||||
from utilities.utils import render_jinja2
|
from utilities.utils import render_jinja2
|
||||||
|
|
||||||
@ -35,8 +36,7 @@ __all__ = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@extras_features('webhooks', 'export_templates')
|
class Webhook(ExportTemplatesMixin, WebhooksMixin, ChangeLoggedModel):
|
||||||
class Webhook(ChangeLoggedModel):
|
|
||||||
"""
|
"""
|
||||||
A Webhook defines a request that will be sent to a remote application when an object is created, updated, and/or
|
A Webhook defines a request that will be sent to a remote application when an object is created, updated, and/or
|
||||||
delete in NetBox. The request will contain a representation of the object, which the remote application can act on.
|
delete in NetBox. The request will contain a representation of the object, which the remote application can act on.
|
||||||
@ -184,8 +184,7 @@ class Webhook(ChangeLoggedModel):
|
|||||||
return render_jinja2(self.payload_url, context)
|
return render_jinja2(self.payload_url, context)
|
||||||
|
|
||||||
|
|
||||||
@extras_features('webhooks', 'export_templates')
|
class CustomLink(ExportTemplatesMixin, WebhooksMixin, ChangeLoggedModel):
|
||||||
class CustomLink(ChangeLoggedModel):
|
|
||||||
"""
|
"""
|
||||||
A custom link to an external representation of a NetBox object. The link text and URL fields accept Jinja2 template
|
A custom link to an external representation of a NetBox object. The link text and URL fields accept Jinja2 template
|
||||||
code to be rendered with an object as context.
|
code to be rendered with an object as context.
|
||||||
@ -258,8 +257,7 @@ class CustomLink(ChangeLoggedModel):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@extras_features('webhooks', 'export_templates')
|
class ExportTemplate(ExportTemplatesMixin, WebhooksMixin, ChangeLoggedModel):
|
||||||
class ExportTemplate(ChangeLoggedModel):
|
|
||||||
content_type = models.ForeignKey(
|
content_type = models.ForeignKey(
|
||||||
to=ContentType,
|
to=ContentType,
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
@ -345,8 +343,7 @@ class ExportTemplate(ChangeLoggedModel):
|
|||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
@extras_features('webhooks')
|
class ImageAttachment(WebhooksMixin, ChangeLoggedModel):
|
||||||
class ImageAttachment(ChangeLoggedModel):
|
|
||||||
"""
|
"""
|
||||||
An uploaded image which is associated with an object.
|
An uploaded image which is associated with an object.
|
||||||
"""
|
"""
|
||||||
@ -424,8 +421,7 @@ class ImageAttachment(ChangeLoggedModel):
|
|||||||
return super().to_objectchange(action, related_object=self.parent)
|
return super().to_objectchange(action, related_object=self.parent)
|
||||||
|
|
||||||
|
|
||||||
@extras_features('webhooks')
|
class JournalEntry(WebhooksMixin, ChangeLoggedModel):
|
||||||
class JournalEntry(ChangeLoggedModel):
|
|
||||||
"""
|
"""
|
||||||
A historical remark concerning an object; collectively, these form an object's journal. The journal is used to
|
A historical remark concerning an object; collectively, these form an object's journal. The journal is used to
|
||||||
preserve historical context around an object, and complements NetBox's built-in change logging. For example, you
|
preserve historical context around an object, and complements NetBox's built-in change logging. For example, you
|
||||||
@ -603,8 +599,7 @@ class ConfigRevision(models.Model):
|
|||||||
# Custom scripts & reports
|
# Custom scripts & reports
|
||||||
#
|
#
|
||||||
|
|
||||||
@extras_features('job_results')
|
class Script(JobResultsMixin, models.Model):
|
||||||
class Script(models.Model):
|
|
||||||
"""
|
"""
|
||||||
Dummy model used to generate permissions for custom scripts. Does not exist in the database.
|
Dummy model used to generate permissions for custom scripts. Does not exist in the database.
|
||||||
"""
|
"""
|
||||||
@ -616,8 +611,7 @@ class Script(models.Model):
|
|||||||
# Reports
|
# Reports
|
||||||
#
|
#
|
||||||
|
|
||||||
@extras_features('job_results')
|
class Report(JobResultsMixin, models.Model):
|
||||||
class Report(models.Model):
|
|
||||||
"""
|
"""
|
||||||
Dummy model used to generate permissions for reports. Does not exist in the database.
|
Dummy model used to generate permissions for reports. Does not exist in the database.
|
||||||
"""
|
"""
|
||||||
|
@ -3,8 +3,8 @@ from django.urls import reverse
|
|||||||
from django.utils.text import slugify
|
from django.utils.text import slugify
|
||||||
from taggit.models import TagBase, GenericTaggedItemBase
|
from taggit.models import TagBase, GenericTaggedItemBase
|
||||||
|
|
||||||
from extras.utils import extras_features
|
|
||||||
from netbox.models import BigIDModel, ChangeLoggedModel
|
from netbox.models import BigIDModel, ChangeLoggedModel
|
||||||
|
from netbox.models.features import ExportTemplatesMixin, WebhooksMixin
|
||||||
from utilities.choices import ColorChoices
|
from utilities.choices import ColorChoices
|
||||||
from utilities.fields import ColorField
|
from utilities.fields import ColorField
|
||||||
|
|
||||||
@ -13,8 +13,7 @@ from utilities.fields import ColorField
|
|||||||
# Tags
|
# Tags
|
||||||
#
|
#
|
||||||
|
|
||||||
@extras_features('webhooks', 'export_templates')
|
class Tag(ExportTemplatesMixin, WebhooksMixin, ChangeLoggedModel, TagBase):
|
||||||
class Tag(ChangeLoggedModel, TagBase):
|
|
||||||
color = ColorField(
|
color = ColorField(
|
||||||
default=ColorChoices.COLOR_GREY
|
default=ColorChoices.COLOR_GREY
|
||||||
)
|
)
|
||||||
|
@ -1,3 +1,8 @@
|
|||||||
|
import collections
|
||||||
|
|
||||||
|
from extras.constants import EXTRAS_FEATURES
|
||||||
|
|
||||||
|
|
||||||
class Registry(dict):
|
class Registry(dict):
|
||||||
"""
|
"""
|
||||||
Central registry for registration of functionality. Once a store (key) is defined, it cannot be overwritten or
|
Central registry for registration of functionality. Once a store (key) is defined, it cannot be overwritten or
|
||||||
@ -7,15 +12,19 @@ class Registry(dict):
|
|||||||
try:
|
try:
|
||||||
return super().__getitem__(key)
|
return super().__getitem__(key)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise KeyError("Invalid store: {}".format(key))
|
raise KeyError(f"Invalid store: {key}")
|
||||||
|
|
||||||
def __setitem__(self, key, value):
|
def __setitem__(self, key, value):
|
||||||
if key in self:
|
if key in self:
|
||||||
raise KeyError("Store already set: {}".format(key))
|
raise KeyError(f"Store already set: {key}")
|
||||||
super().__setitem__(key, value)
|
super().__setitem__(key, value)
|
||||||
|
|
||||||
def __delitem__(self, key):
|
def __delitem__(self, key):
|
||||||
raise TypeError("Cannot delete stores from registry")
|
raise TypeError("Cannot delete stores from registry")
|
||||||
|
|
||||||
|
|
||||||
|
# Initialize the global registry
|
||||||
registry = Registry()
|
registry = Registry()
|
||||||
|
registry['model_features'] = {
|
||||||
|
feature: collections.defaultdict(set) for feature in EXTRAS_FEATURES
|
||||||
|
}
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
import collections
|
|
||||||
|
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.utils.deconstruct import deconstructible
|
from django.utils.deconstruct import deconstructible
|
||||||
from taggit.managers import _TaggableManager
|
from taggit.managers import _TaggableManager
|
||||||
@ -57,21 +55,9 @@ class FeatureQuery:
|
|||||||
return query
|
return query
|
||||||
|
|
||||||
|
|
||||||
def extras_features(*features):
|
def register_features(model, features):
|
||||||
"""
|
for feature in features:
|
||||||
Decorator used to register extras provided features to a model
|
if feature not in EXTRAS_FEATURES:
|
||||||
"""
|
raise ValueError(f"{feature} is not a valid extras feature!")
|
||||||
def wrapper(model_class):
|
app_label, model_name = model._meta.label_lower.split('.')
|
||||||
# Initialize the model_features store if not already defined
|
registry['model_features'][feature][app_label].add(model_name)
|
||||||
if 'model_features' not in registry:
|
|
||||||
registry['model_features'] = {
|
|
||||||
f: collections.defaultdict(list) for f in EXTRAS_FEATURES
|
|
||||||
}
|
|
||||||
for feature in features:
|
|
||||||
if feature in EXTRAS_FEATURES:
|
|
||||||
app_label, model_name = model_class._meta.label_lower.split('.')
|
|
||||||
registry['model_features'][feature][app_label].append(model_name)
|
|
||||||
else:
|
|
||||||
raise ValueError('{} is not a valid extras feature!'.format(feature))
|
|
||||||
return model_class
|
|
||||||
return wrapper
|
|
||||||
|
@ -4,8 +4,8 @@ from django.core.validators import MaxValueValidator, MinValueValidator
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
from extras.utils import extras_features
|
|
||||||
from netbox.models import ChangeLoggedModel, PrimaryModel
|
from netbox.models import ChangeLoggedModel, PrimaryModel
|
||||||
|
from netbox.models.features import WebhooksMixin
|
||||||
from ipam.choices import *
|
from ipam.choices import *
|
||||||
from ipam.constants import *
|
from ipam.constants import *
|
||||||
|
|
||||||
@ -15,7 +15,6 @@ __all__ = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class FHRPGroup(PrimaryModel):
|
class FHRPGroup(PrimaryModel):
|
||||||
"""
|
"""
|
||||||
A grouping of next hope resolution protocol (FHRP) peers. (For instance, VRRP or HSRP.)
|
A grouping of next hope resolution protocol (FHRP) peers. (For instance, VRRP or HSRP.)
|
||||||
@ -70,8 +69,7 @@ class FHRPGroup(PrimaryModel):
|
|||||||
return reverse('ipam:fhrpgroup', args=[self.pk])
|
return reverse('ipam:fhrpgroup', args=[self.pk])
|
||||||
|
|
||||||
|
|
||||||
@extras_features('webhooks')
|
class FHRPGroupAssignment(WebhooksMixin, ChangeLoggedModel):
|
||||||
class FHRPGroupAssignment(ChangeLoggedModel):
|
|
||||||
interface_type = models.ForeignKey(
|
interface_type = models.ForeignKey(
|
||||||
to=ContentType,
|
to=ContentType,
|
||||||
on_delete=models.CASCADE
|
on_delete=models.CASCADE
|
||||||
|
@ -9,7 +9,6 @@ from django.utils.functional import cached_property
|
|||||||
|
|
||||||
from dcim.fields import ASNField
|
from dcim.fields import ASNField
|
||||||
from dcim.models import Device
|
from dcim.models import Device
|
||||||
from extras.utils import extras_features
|
|
||||||
from netbox.models import OrganizationalModel, PrimaryModel
|
from netbox.models import OrganizationalModel, PrimaryModel
|
||||||
from ipam.choices import *
|
from ipam.choices import *
|
||||||
from ipam.constants import *
|
from ipam.constants import *
|
||||||
@ -54,7 +53,6 @@ class GetAvailablePrefixesMixin:
|
|||||||
return available_prefixes.iter_cidrs()[0]
|
return available_prefixes.iter_cidrs()[0]
|
||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class RIR(OrganizationalModel):
|
class RIR(OrganizationalModel):
|
||||||
"""
|
"""
|
||||||
A Regional Internet Registry (RIR) is responsible for the allocation of a large portion of the global IP address
|
A Regional Internet Registry (RIR) is responsible for the allocation of a large portion of the global IP address
|
||||||
@ -90,7 +88,6 @@ class RIR(OrganizationalModel):
|
|||||||
return reverse('ipam:rir', args=[self.pk])
|
return reverse('ipam:rir', args=[self.pk])
|
||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class ASN(PrimaryModel):
|
class ASN(PrimaryModel):
|
||||||
"""
|
"""
|
||||||
An autonomous system (AS) number is typically used to represent an independent routing domain. A site can have
|
An autonomous system (AS) number is typically used to represent an independent routing domain. A site can have
|
||||||
@ -150,7 +147,6 @@ class ASN(PrimaryModel):
|
|||||||
return self.asn
|
return self.asn
|
||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class Aggregate(GetAvailablePrefixesMixin, PrimaryModel):
|
class Aggregate(GetAvailablePrefixesMixin, PrimaryModel):
|
||||||
"""
|
"""
|
||||||
An aggregate exists at the root level of the IP address space hierarchy in NetBox. Aggregates are used to organize
|
An aggregate exists at the root level of the IP address space hierarchy in NetBox. Aggregates are used to organize
|
||||||
@ -253,7 +249,6 @@ class Aggregate(GetAvailablePrefixesMixin, PrimaryModel):
|
|||||||
return min(utilization, 100)
|
return min(utilization, 100)
|
||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class Role(OrganizationalModel):
|
class Role(OrganizationalModel):
|
||||||
"""
|
"""
|
||||||
A Role represents the functional role of a Prefix or VLAN; for example, "Customer," "Infrastructure," or
|
A Role represents the functional role of a Prefix or VLAN; for example, "Customer," "Infrastructure," or
|
||||||
@ -285,7 +280,6 @@ class Role(OrganizationalModel):
|
|||||||
return reverse('ipam:role', args=[self.pk])
|
return reverse('ipam:role', args=[self.pk])
|
||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class Prefix(GetAvailablePrefixesMixin, PrimaryModel):
|
class Prefix(GetAvailablePrefixesMixin, PrimaryModel):
|
||||||
"""
|
"""
|
||||||
A Prefix represents an IPv4 or IPv6 network, including mask length. Prefixes can optionally be assigned to Sites and
|
A Prefix represents an IPv4 or IPv6 network, including mask length. Prefixes can optionally be assigned to Sites and
|
||||||
@ -563,7 +557,6 @@ class Prefix(GetAvailablePrefixesMixin, PrimaryModel):
|
|||||||
return min(utilization, 100)
|
return min(utilization, 100)
|
||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class IPRange(PrimaryModel):
|
class IPRange(PrimaryModel):
|
||||||
"""
|
"""
|
||||||
A range of IP addresses, defined by start and end addresses.
|
A range of IP addresses, defined by start and end addresses.
|
||||||
@ -759,7 +752,6 @@ class IPRange(PrimaryModel):
|
|||||||
return int(float(child_count) / self.size * 100)
|
return int(float(child_count) / self.size * 100)
|
||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class IPAddress(PrimaryModel):
|
class IPAddress(PrimaryModel):
|
||||||
"""
|
"""
|
||||||
An IPAddress represents an individual IPv4 or IPv6 address and its mask. The mask length should match what is
|
An IPAddress represents an individual IPv4 or IPv6 address and its mask. The mask length should match what is
|
||||||
|
@ -4,7 +4,6 @@ from django.core.validators import MaxValueValidator, MinValueValidator
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
from extras.utils import extras_features
|
|
||||||
from ipam.choices import *
|
from ipam.choices import *
|
||||||
from ipam.constants import *
|
from ipam.constants import *
|
||||||
from netbox.models import PrimaryModel
|
from netbox.models import PrimaryModel
|
||||||
@ -47,7 +46,6 @@ class ServiceBase(models.Model):
|
|||||||
return array_to_string(self.ports)
|
return array_to_string(self.ports)
|
||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class ServiceTemplate(ServiceBase, PrimaryModel):
|
class ServiceTemplate(ServiceBase, PrimaryModel):
|
||||||
"""
|
"""
|
||||||
A template for a Service to be applied to a device or virtual machine.
|
A template for a Service to be applied to a device or virtual machine.
|
||||||
@ -64,7 +62,6 @@ class ServiceTemplate(ServiceBase, PrimaryModel):
|
|||||||
return reverse('ipam:servicetemplate', args=[self.pk])
|
return reverse('ipam:servicetemplate', args=[self.pk])
|
||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class Service(ServiceBase, PrimaryModel):
|
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
|
A Service represents a layer-four service (e.g. HTTP or SSH) running on a Device or VirtualMachine. A Service may
|
||||||
|
@ -6,7 +6,6 @@ from django.db import models
|
|||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
from dcim.models import Interface
|
from dcim.models import Interface
|
||||||
from extras.utils import extras_features
|
|
||||||
from ipam.choices import *
|
from ipam.choices import *
|
||||||
from ipam.constants import *
|
from ipam.constants import *
|
||||||
from ipam.querysets import VLANQuerySet
|
from ipam.querysets import VLANQuerySet
|
||||||
@ -20,7 +19,6 @@ __all__ = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class VLANGroup(OrganizationalModel):
|
class VLANGroup(OrganizationalModel):
|
||||||
"""
|
"""
|
||||||
A VLAN group is an arbitrary collection of VLANs within which VLAN IDs and names must be unique.
|
A VLAN group is an arbitrary collection of VLANs within which VLAN IDs and names must be unique.
|
||||||
@ -118,7 +116,6 @@ class VLANGroup(OrganizationalModel):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class VLAN(PrimaryModel):
|
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
|
A VLAN is a distinct layer two forwarding domain identified by a 12-bit integer (1-4094). Each VLAN must be assigned
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
from extras.utils import extras_features
|
|
||||||
from ipam.constants import *
|
from ipam.constants import *
|
||||||
from netbox.models import PrimaryModel
|
from netbox.models import PrimaryModel
|
||||||
|
|
||||||
@ -12,7 +11,6 @@ __all__ = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class VRF(PrimaryModel):
|
class VRF(PrimaryModel):
|
||||||
"""
|
"""
|
||||||
A virtual routing and forwarding (VRF) table represents a discrete layer three forwarding domain (e.g. a routing
|
A virtual routing and forwarding (VRF) table represents a discrete layer three forwarding domain (e.g. a routing
|
||||||
@ -75,7 +73,6 @@ class VRF(PrimaryModel):
|
|||||||
return reverse('ipam:vrf', args=[self.pk])
|
return reverse('ipam:vrf', args=[self.pk])
|
||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class RouteTarget(PrimaryModel):
|
class RouteTarget(PrimaryModel):
|
||||||
"""
|
"""
|
||||||
A BGP extended community used to control the redistribution of routes among VRFs, as defined in RFC 4364.
|
A BGP extended community used to control the redistribution of routes among VRFs, as defined in RFC 4364.
|
||||||
|
135
netbox/netbox/models/__init__.py
Normal file
135
netbox/netbox/models/__init__.py
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
from django.core.validators import ValidationError
|
||||||
|
from django.db import models
|
||||||
|
from mptt.models import MPTTModel, TreeForeignKey
|
||||||
|
|
||||||
|
from utilities.mptt import TreeManager
|
||||||
|
from utilities.querysets import RestrictedQuerySet
|
||||||
|
from netbox.models.features import *
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
'BigIDModel',
|
||||||
|
'ChangeLoggedModel',
|
||||||
|
'NestedGroupModel',
|
||||||
|
'OrganizationalModel',
|
||||||
|
'PrimaryModel',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Base model classes
|
||||||
|
#
|
||||||
|
|
||||||
|
class BaseModel(
|
||||||
|
CustomFieldsMixin,
|
||||||
|
CustomLinksMixin,
|
||||||
|
CustomValidationMixin,
|
||||||
|
ExportTemplatesMixin,
|
||||||
|
JournalingMixin,
|
||||||
|
TagsMixin,
|
||||||
|
WebhooksMixin,
|
||||||
|
):
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
|
||||||
|
class BigIDModel(models.Model):
|
||||||
|
"""
|
||||||
|
Abstract base model for all data objects. Ensures the use of a 64-bit PK.
|
||||||
|
"""
|
||||||
|
id = models.BigAutoField(
|
||||||
|
primary_key=True
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
|
||||||
|
class ChangeLoggedModel(ChangeLoggingMixin, CustomValidationMixin, BigIDModel):
|
||||||
|
"""
|
||||||
|
Base model for all objects which support change logging.
|
||||||
|
"""
|
||||||
|
objects = RestrictedQuerySet.as_manager()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
|
||||||
|
class PrimaryModel(BaseModel, ChangeLoggingMixin, BigIDModel):
|
||||||
|
"""
|
||||||
|
Primary models represent real objects within the infrastructure being modeled.
|
||||||
|
"""
|
||||||
|
objects = RestrictedQuerySet.as_manager()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
|
||||||
|
class NestedGroupModel(BaseModel, ChangeLoggingMixin, BigIDModel, MPTTModel):
|
||||||
|
"""
|
||||||
|
Base model for objects which are used to form a hierarchy (regions, locations, etc.). These models nest
|
||||||
|
recursively using MPTT. Within each parent, each child instance must have a unique name.
|
||||||
|
"""
|
||||||
|
parent = TreeForeignKey(
|
||||||
|
to='self',
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name='children',
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
db_index=True
|
||||||
|
)
|
||||||
|
name = models.CharField(
|
||||||
|
max_length=100
|
||||||
|
)
|
||||||
|
description = models.CharField(
|
||||||
|
max_length=200,
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
|
|
||||||
|
objects = TreeManager()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
class MPTTMeta:
|
||||||
|
order_insertion_by = ('name',)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
super().clean()
|
||||||
|
|
||||||
|
# An MPTT model cannot be its own parent
|
||||||
|
if self.pk and self.parent_id == self.pk:
|
||||||
|
raise ValidationError({
|
||||||
|
"parent": "Cannot assign self as parent."
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
class OrganizationalModel(BaseModel, ChangeLoggingMixin, BigIDModel):
|
||||||
|
"""
|
||||||
|
Organizational models are those which are used solely to categorize and qualify other objects, and do not convey
|
||||||
|
any real information about the infrastructure being modeled (for example, functional device roles). Organizational
|
||||||
|
models provide the following standard attributes:
|
||||||
|
- Unique name
|
||||||
|
- Unique slug (automatically derived from name)
|
||||||
|
- Optional description
|
||||||
|
"""
|
||||||
|
name = models.CharField(
|
||||||
|
max_length=100,
|
||||||
|
unique=True
|
||||||
|
)
|
||||||
|
slug = models.SlugField(
|
||||||
|
max_length=100,
|
||||||
|
unique=True
|
||||||
|
)
|
||||||
|
description = models.CharField(
|
||||||
|
max_length=200,
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
|
|
||||||
|
objects = RestrictedQuerySet.as_manager()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
ordering = ('name',)
|
@ -1,34 +1,39 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from django.contrib.contenttypes.fields import GenericRelation
|
from django.contrib.contenttypes.fields import GenericRelation
|
||||||
|
from django.db.models.signals import class_prepared
|
||||||
|
from django.dispatch import receiver
|
||||||
|
|
||||||
from django.core.serializers.json import DjangoJSONEncoder
|
from django.core.serializers.json import DjangoJSONEncoder
|
||||||
from django.core.validators import ValidationError
|
from django.core.validators import ValidationError
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from mptt.models import MPTTModel, TreeForeignKey
|
|
||||||
from taggit.managers import TaggableManager
|
from taggit.managers import TaggableManager
|
||||||
|
|
||||||
from extras.choices import ObjectChangeActionChoices
|
from extras.choices import ObjectChangeActionChoices
|
||||||
|
from extras.utils import register_features
|
||||||
from netbox.signals import post_clean
|
from netbox.signals import post_clean
|
||||||
from utilities.mptt import TreeManager
|
|
||||||
from utilities.querysets import RestrictedQuerySet
|
|
||||||
from utilities.utils import serialize_object
|
from utilities.utils import serialize_object
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'BigIDModel',
|
'ChangeLoggingMixin',
|
||||||
'ChangeLoggedModel',
|
'CustomFieldsMixin',
|
||||||
'NestedGroupModel',
|
'CustomLinksMixin',
|
||||||
'OrganizationalModel',
|
'CustomValidationMixin',
|
||||||
'PrimaryModel',
|
'ExportTemplatesMixin',
|
||||||
|
'JobResultsMixin',
|
||||||
|
'JournalingMixin',
|
||||||
|
'TagsMixin',
|
||||||
|
'WebhooksMixin',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Mixins
|
# Feature mixins
|
||||||
#
|
#
|
||||||
|
|
||||||
class ChangeLoggingMixin(models.Model):
|
class ChangeLoggingMixin(models.Model):
|
||||||
"""
|
"""
|
||||||
Provides change logging support.
|
Provides change logging support for a model. Adds the `created` and `last_updated` fields.
|
||||||
"""
|
"""
|
||||||
created = models.DateField(
|
created = models.DateField(
|
||||||
auto_now_add=True,
|
auto_now_add=True,
|
||||||
@ -74,7 +79,7 @@ class ChangeLoggingMixin(models.Model):
|
|||||||
|
|
||||||
class CustomFieldsMixin(models.Model):
|
class CustomFieldsMixin(models.Model):
|
||||||
"""
|
"""
|
||||||
Provides support for custom fields.
|
Enables support for custom fields.
|
||||||
"""
|
"""
|
||||||
custom_field_data = models.JSONField(
|
custom_field_data = models.JSONField(
|
||||||
encoder=DjangoJSONEncoder,
|
encoder=DjangoJSONEncoder,
|
||||||
@ -88,13 +93,25 @@ class CustomFieldsMixin(models.Model):
|
|||||||
@property
|
@property
|
||||||
def cf(self):
|
def cf(self):
|
||||||
"""
|
"""
|
||||||
Convenience wrapper for custom field data.
|
A pass-through convenience alias for accessing `custom_field_data` (read-only).
|
||||||
|
|
||||||
|
```python
|
||||||
|
>>> tenant = Tenant.objects.first()
|
||||||
|
>>> tenant.cf
|
||||||
|
{'cust_id': 'CYB01'}
|
||||||
|
```
|
||||||
"""
|
"""
|
||||||
return self.custom_field_data
|
return self.custom_field_data
|
||||||
|
|
||||||
def get_custom_fields(self):
|
def get_custom_fields(self):
|
||||||
"""
|
"""
|
||||||
Return a dictionary of custom fields for a single object in the form {<field>: value}.
|
Return a dictionary of custom fields for a single object in the form `{field: value}`.
|
||||||
|
|
||||||
|
```python
|
||||||
|
>>> tenant = Tenant.objects.first()
|
||||||
|
>>> tenant.get_custom_fields()
|
||||||
|
{<CustomField: Customer ID>: 'CYB01'}
|
||||||
|
```
|
||||||
"""
|
"""
|
||||||
from extras.models import CustomField
|
from extras.models import CustomField
|
||||||
|
|
||||||
@ -128,9 +145,17 @@ class CustomFieldsMixin(models.Model):
|
|||||||
raise ValidationError(f"Missing required custom field '{cf.name}'.")
|
raise ValidationError(f"Missing required custom field '{cf.name}'.")
|
||||||
|
|
||||||
|
|
||||||
|
class CustomLinksMixin(models.Model):
|
||||||
|
"""
|
||||||
|
Enables support for custom links.
|
||||||
|
"""
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
|
||||||
class CustomValidationMixin(models.Model):
|
class CustomValidationMixin(models.Model):
|
||||||
"""
|
"""
|
||||||
Enables user-configured validation rules for built-in models by extending the clean() method.
|
Enables user-configured validation rules for models.
|
||||||
"""
|
"""
|
||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
@ -142,9 +167,41 @@ class CustomValidationMixin(models.Model):
|
|||||||
post_clean.send(sender=self.__class__, instance=self)
|
post_clean.send(sender=self.__class__, instance=self)
|
||||||
|
|
||||||
|
|
||||||
|
class ExportTemplatesMixin(models.Model):
|
||||||
|
"""
|
||||||
|
Enables support for export templates.
|
||||||
|
"""
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
|
||||||
|
class JobResultsMixin(models.Model):
|
||||||
|
"""
|
||||||
|
Enables support for job results.
|
||||||
|
"""
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
|
||||||
|
class JournalingMixin(models.Model):
|
||||||
|
"""
|
||||||
|
Enables support for object journaling. Adds a generic relation (`journal_entries`)
|
||||||
|
to NetBox's JournalEntry model.
|
||||||
|
"""
|
||||||
|
journal_entries = GenericRelation(
|
||||||
|
to='extras.JournalEntry',
|
||||||
|
object_id_field='assigned_object_id',
|
||||||
|
content_type_field='assigned_object_type'
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
|
||||||
class TagsMixin(models.Model):
|
class TagsMixin(models.Model):
|
||||||
"""
|
"""
|
||||||
Enable the assignment of Tags.
|
Enables support for tag assignment. Assigned tags can be managed via the `tags` attribute,
|
||||||
|
which is a `TaggableManager` instance.
|
||||||
"""
|
"""
|
||||||
tags = TaggableManager(
|
tags = TaggableManager(
|
||||||
through='extras.TaggedItem'
|
through='extras.TaggedItem'
|
||||||
@ -154,113 +211,28 @@ class TagsMixin(models.Model):
|
|||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
|
|
||||||
#
|
class WebhooksMixin(models.Model):
|
||||||
# Base model classes
|
|
||||||
|
|
||||||
class BigIDModel(models.Model):
|
|
||||||
"""
|
"""
|
||||||
Abstract base model for all data objects. Ensures the use of a 64-bit PK.
|
Enables support for webhooks.
|
||||||
"""
|
"""
|
||||||
id = models.BigAutoField(
|
|
||||||
primary_key=True
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
|
|
||||||
class ChangeLoggedModel(ChangeLoggingMixin, CustomValidationMixin, BigIDModel):
|
FEATURES_MAP = (
|
||||||
"""
|
('custom_fields', CustomFieldsMixin),
|
||||||
Base model for all objects which support change logging.
|
('custom_links', CustomLinksMixin),
|
||||||
"""
|
('export_templates', ExportTemplatesMixin),
|
||||||
objects = RestrictedQuerySet.as_manager()
|
('job_results', JobResultsMixin),
|
||||||
|
('journaling', JournalingMixin),
|
||||||
class Meta:
|
('tags', TagsMixin),
|
||||||
abstract = True
|
('webhooks', WebhooksMixin),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class PrimaryModel(ChangeLoggingMixin, CustomFieldsMixin, CustomValidationMixin, TagsMixin, BigIDModel):
|
@receiver(class_prepared)
|
||||||
"""
|
def _register_features(sender, **kwargs):
|
||||||
Primary models represent real objects within the infrastructure being modeled.
|
features = {
|
||||||
"""
|
feature for feature, cls in FEATURES_MAP if issubclass(sender, cls)
|
||||||
journal_entries = GenericRelation(
|
}
|
||||||
to='extras.JournalEntry',
|
register_features(sender, features)
|
||||||
object_id_field='assigned_object_id',
|
|
||||||
content_type_field='assigned_object_type'
|
|
||||||
)
|
|
||||||
|
|
||||||
objects = RestrictedQuerySet.as_manager()
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
|
|
||||||
class NestedGroupModel(ChangeLoggingMixin, CustomFieldsMixin, CustomValidationMixin, TagsMixin, BigIDModel, MPTTModel):
|
|
||||||
"""
|
|
||||||
Base model for objects which are used to form a hierarchy (regions, locations, etc.). These models nest
|
|
||||||
recursively using MPTT. Within each parent, each child instance must have a unique name.
|
|
||||||
"""
|
|
||||||
parent = TreeForeignKey(
|
|
||||||
to='self',
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
related_name='children',
|
|
||||||
blank=True,
|
|
||||||
null=True,
|
|
||||||
db_index=True
|
|
||||||
)
|
|
||||||
name = models.CharField(
|
|
||||||
max_length=100
|
|
||||||
)
|
|
||||||
description = models.CharField(
|
|
||||||
max_length=200,
|
|
||||||
blank=True
|
|
||||||
)
|
|
||||||
|
|
||||||
objects = TreeManager()
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
abstract = True
|
|
||||||
|
|
||||||
class MPTTMeta:
|
|
||||||
order_insertion_by = ('name',)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
def clean(self):
|
|
||||||
super().clean()
|
|
||||||
|
|
||||||
# An MPTT model cannot be its own parent
|
|
||||||
if self.pk and self.parent_id == self.pk:
|
|
||||||
raise ValidationError({
|
|
||||||
"parent": "Cannot assign self as parent."
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
class OrganizationalModel(ChangeLoggingMixin, CustomFieldsMixin, CustomValidationMixin, TagsMixin, BigIDModel):
|
|
||||||
"""
|
|
||||||
Organizational models are those which are used solely to categorize and qualify other objects, and do not convey
|
|
||||||
any real information about the infrastructure being modeled (for example, functional device roles). Organizational
|
|
||||||
models provide the following standard attributes:
|
|
||||||
- Unique name
|
|
||||||
- Unique slug (automatically derived from name)
|
|
||||||
- Optional description
|
|
||||||
"""
|
|
||||||
name = models.CharField(
|
|
||||||
max_length=100,
|
|
||||||
unique=True
|
|
||||||
)
|
|
||||||
slug = models.SlugField(
|
|
||||||
max_length=100,
|
|
||||||
unique=True
|
|
||||||
)
|
|
||||||
description = models.CharField(
|
|
||||||
max_length=200,
|
|
||||||
blank=True
|
|
||||||
)
|
|
||||||
|
|
||||||
objects = RestrictedQuerySet.as_manager()
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
abstract = True
|
|
||||||
ordering = ('name',)
|
|
@ -4,8 +4,8 @@ from django.db import models
|
|||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from mptt.models import TreeForeignKey
|
from mptt.models import TreeForeignKey
|
||||||
|
|
||||||
from extras.utils import extras_features
|
|
||||||
from netbox.models import ChangeLoggedModel, NestedGroupModel, OrganizationalModel, PrimaryModel
|
from netbox.models import ChangeLoggedModel, NestedGroupModel, OrganizationalModel, PrimaryModel
|
||||||
|
from netbox.models.features import WebhooksMixin
|
||||||
from tenancy.choices import *
|
from tenancy.choices import *
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
@ -16,7 +16,6 @@ __all__ = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class ContactGroup(NestedGroupModel):
|
class ContactGroup(NestedGroupModel):
|
||||||
"""
|
"""
|
||||||
An arbitrary collection of Contacts.
|
An arbitrary collection of Contacts.
|
||||||
@ -50,7 +49,6 @@ class ContactGroup(NestedGroupModel):
|
|||||||
return reverse('tenancy:contactgroup', args=[self.pk])
|
return reverse('tenancy:contactgroup', args=[self.pk])
|
||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class ContactRole(OrganizationalModel):
|
class ContactRole(OrganizationalModel):
|
||||||
"""
|
"""
|
||||||
Functional role for a Contact assigned to an object.
|
Functional role for a Contact assigned to an object.
|
||||||
@ -78,7 +76,6 @@ class ContactRole(OrganizationalModel):
|
|||||||
return reverse('tenancy:contactrole', args=[self.pk])
|
return reverse('tenancy:contactrole', args=[self.pk])
|
||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class Contact(PrimaryModel):
|
class Contact(PrimaryModel):
|
||||||
"""
|
"""
|
||||||
Contact information for a particular object(s) in NetBox.
|
Contact information for a particular object(s) in NetBox.
|
||||||
@ -129,8 +126,7 @@ class Contact(PrimaryModel):
|
|||||||
return reverse('tenancy:contact', args=[self.pk])
|
return reverse('tenancy:contact', args=[self.pk])
|
||||||
|
|
||||||
|
|
||||||
@extras_features('webhooks')
|
class ContactAssignment(WebhooksMixin, ChangeLoggedModel):
|
||||||
class ContactAssignment(ChangeLoggedModel):
|
|
||||||
content_type = models.ForeignKey(
|
content_type = models.ForeignKey(
|
||||||
to=ContentType,
|
to=ContentType,
|
||||||
on_delete=models.CASCADE
|
on_delete=models.CASCADE
|
||||||
|
@ -3,7 +3,6 @@ from django.db import models
|
|||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from mptt.models import TreeForeignKey
|
from mptt.models import TreeForeignKey
|
||||||
|
|
||||||
from extras.utils import extras_features
|
|
||||||
from netbox.models import NestedGroupModel, PrimaryModel
|
from netbox.models import NestedGroupModel, PrimaryModel
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
@ -12,7 +11,6 @@ __all__ = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class TenantGroup(NestedGroupModel):
|
class TenantGroup(NestedGroupModel):
|
||||||
"""
|
"""
|
||||||
An arbitrary collection of Tenants.
|
An arbitrary collection of Tenants.
|
||||||
@ -45,7 +43,6 @@ class TenantGroup(NestedGroupModel):
|
|||||||
return reverse('tenancy:tenantgroup', args=[self.pk])
|
return reverse('tenancy:tenantgroup', args=[self.pk])
|
||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class Tenant(PrimaryModel):
|
class Tenant(PrimaryModel):
|
||||||
"""
|
"""
|
||||||
A Tenant represents an organization served by the NetBox owner. This is typically a customer or an internal
|
A Tenant represents an organization served by the NetBox owner. This is typically a customer or an internal
|
||||||
|
@ -7,7 +7,6 @@ from django.urls import reverse
|
|||||||
from dcim.models import BaseInterface, Device
|
from dcim.models import BaseInterface, Device
|
||||||
from extras.models import ConfigContextModel
|
from extras.models import ConfigContextModel
|
||||||
from extras.querysets import ConfigContextModelQuerySet
|
from extras.querysets import ConfigContextModelQuerySet
|
||||||
from extras.utils import extras_features
|
|
||||||
from netbox.config import get_config
|
from netbox.config import get_config
|
||||||
from netbox.models import OrganizationalModel, PrimaryModel
|
from netbox.models import OrganizationalModel, PrimaryModel
|
||||||
from utilities.fields import NaturalOrderingField
|
from utilities.fields import NaturalOrderingField
|
||||||
@ -15,7 +14,6 @@ from utilities.ordering import naturalize_interface
|
|||||||
from utilities.query_functions import CollateAsChar
|
from utilities.query_functions import CollateAsChar
|
||||||
from .choices import *
|
from .choices import *
|
||||||
|
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'Cluster',
|
'Cluster',
|
||||||
'ClusterGroup',
|
'ClusterGroup',
|
||||||
@ -29,7 +27,6 @@ __all__ = (
|
|||||||
# Cluster types
|
# Cluster types
|
||||||
#
|
#
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class ClusterType(OrganizationalModel):
|
class ClusterType(OrganizationalModel):
|
||||||
"""
|
"""
|
||||||
A type of Cluster.
|
A type of Cluster.
|
||||||
@ -61,7 +58,6 @@ class ClusterType(OrganizationalModel):
|
|||||||
# Cluster groups
|
# Cluster groups
|
||||||
#
|
#
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class ClusterGroup(OrganizationalModel):
|
class ClusterGroup(OrganizationalModel):
|
||||||
"""
|
"""
|
||||||
An organizational group of Clusters.
|
An organizational group of Clusters.
|
||||||
@ -104,7 +100,6 @@ class ClusterGroup(OrganizationalModel):
|
|||||||
# Clusters
|
# Clusters
|
||||||
#
|
#
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class Cluster(PrimaryModel):
|
class Cluster(PrimaryModel):
|
||||||
"""
|
"""
|
||||||
A cluster of VirtualMachines. Each Cluster may optionally be associated with one or more Devices.
|
A cluster of VirtualMachines. Each Cluster may optionally be associated with one or more Devices.
|
||||||
@ -188,7 +183,6 @@ class Cluster(PrimaryModel):
|
|||||||
# Virtual machines
|
# Virtual machines
|
||||||
#
|
#
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class VirtualMachine(PrimaryModel, ConfigContextModel):
|
class VirtualMachine(PrimaryModel, ConfigContextModel):
|
||||||
"""
|
"""
|
||||||
A virtual machine which runs inside a Cluster.
|
A virtual machine which runs inside a Cluster.
|
||||||
@ -351,7 +345,6 @@ class VirtualMachine(PrimaryModel, ConfigContextModel):
|
|||||||
# Interfaces
|
# Interfaces
|
||||||
#
|
#
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class VMInterface(PrimaryModel, BaseInterface):
|
class VMInterface(PrimaryModel, BaseInterface):
|
||||||
virtual_machine = models.ForeignKey(
|
virtual_machine = models.ForeignKey(
|
||||||
to='virtualization.VirtualMachine',
|
to='virtualization.VirtualMachine',
|
||||||
|
@ -5,7 +5,6 @@ from mptt.models import MPTTModel, TreeForeignKey
|
|||||||
|
|
||||||
from dcim.choices import LinkStatusChoices
|
from dcim.choices import LinkStatusChoices
|
||||||
from dcim.constants import WIRELESS_IFACE_TYPES
|
from dcim.constants import WIRELESS_IFACE_TYPES
|
||||||
from extras.utils import extras_features
|
|
||||||
from netbox.models import BigIDModel, NestedGroupModel, PrimaryModel
|
from netbox.models import BigIDModel, NestedGroupModel, PrimaryModel
|
||||||
from .choices import *
|
from .choices import *
|
||||||
from .constants import *
|
from .constants import *
|
||||||
@ -41,7 +40,6 @@ class WirelessAuthenticationBase(models.Model):
|
|||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class WirelessLANGroup(NestedGroupModel):
|
class WirelessLANGroup(NestedGroupModel):
|
||||||
"""
|
"""
|
||||||
A nested grouping of WirelessLANs
|
A nested grouping of WirelessLANs
|
||||||
@ -81,7 +79,6 @@ class WirelessLANGroup(NestedGroupModel):
|
|||||||
return reverse('wireless:wirelesslangroup', args=[self.pk])
|
return reverse('wireless:wirelesslangroup', args=[self.pk])
|
||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class WirelessLAN(WirelessAuthenticationBase, PrimaryModel):
|
class WirelessLAN(WirelessAuthenticationBase, PrimaryModel):
|
||||||
"""
|
"""
|
||||||
A wireless network formed among an arbitrary number of access point and clients.
|
A wireless network formed among an arbitrary number of access point and clients.
|
||||||
@ -120,7 +117,6 @@ class WirelessLAN(WirelessAuthenticationBase, PrimaryModel):
|
|||||||
return reverse('wireless:wirelesslan', args=[self.pk])
|
return reverse('wireless:wirelesslan', args=[self.pk])
|
||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
|
|
||||||
class WirelessLink(WirelessAuthenticationBase, PrimaryModel):
|
class WirelessLink(WirelessAuthenticationBase, PrimaryModel):
|
||||||
"""
|
"""
|
||||||
A point-to-point connection between two wireless Interfaces.
|
A point-to-point connection between two wireless Interfaces.
|
||||||
|
@ -19,6 +19,7 @@ Jinja2==3.0.3
|
|||||||
Markdown==3.3.6
|
Markdown==3.3.6
|
||||||
markdown-include==0.6.0
|
markdown-include==0.6.0
|
||||||
mkdocs-material==8.1.7
|
mkdocs-material==8.1.7
|
||||||
|
mkdocstrings==0.17.0
|
||||||
netaddr==0.8.0
|
netaddr==0.8.0
|
||||||
Pillow==8.4.0
|
Pillow==8.4.0
|
||||||
psycopg2-binary==2.9.3
|
psycopg2-binary==2.9.3
|
||||||
|
Loading…
Reference in New Issue
Block a user