Skip to content

Commit

Permalink
Merge pull request #8414 from netbox-community/8392-plugins-features
Browse files Browse the repository at this point in the history
Closes #8392: Enable NetBox features for plugin models
  • Loading branch information
jeremystretch committed Jan 20, 2022
2 parents bf6345a + e6acae5 commit 7002319
Show file tree
Hide file tree
Showing 33 changed files with 341 additions and 252 deletions.
4 changes: 4 additions & 0 deletions base_requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ markdown-include
# https://github.com/squidfunk/mkdocs-material
mkdocs-material

# Introspection for embedded code
# https://github.com/mkdocstrings/mkdocstrings
mkdocstrings

# Library for manipulating IP prefixes and addresses
# https://github.com/drkjam/netaddr
netaddr
Expand Down
3 changes: 3 additions & 0 deletions docs/plugins/development/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Plugins Development

TODO
64 changes: 64 additions & 0 deletions docs/plugins/development/model-features.md
Original file line number Diff line number Diff line change
@@ -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
7 changes: 0 additions & 7 deletions docs/requirements.txt

This file was deleted.

20 changes: 19 additions & 1 deletion mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,21 @@ theme:
toggle:
icon: material/lightbulb
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:
social:
- icon: fontawesome/brands/github
Expand Down Expand Up @@ -84,7 +99,10 @@ nav:
- Webhooks: 'additional-features/webhooks.md'
- Plugins:
- 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:
- Authentication: 'administration/authentication.md'
- Permissions: 'administration/permissions.md'
Expand Down
7 changes: 2 additions & 5 deletions netbox/circuits/models/circuits.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

from circuits.choices import *
from dcim.models import LinkTermination
from extras.utils import extras_features
from netbox.models import ChangeLoggedModel, OrganizationalModel, PrimaryModel
from netbox.models.features import WebhooksMixin

__all__ = (
'Circuit',
Expand All @@ -15,7 +15,6 @@
)


@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
class CircuitType(OrganizationalModel):
"""
Circuits can be organized by their functional role. For example, a user might wish to define CircuitTypes named
Expand Down Expand Up @@ -44,7 +43,6 @@ def get_absolute_url(self):
return reverse('circuits:circuittype', args=[self.pk])


@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
class Circuit(PrimaryModel):
"""
A communications circuit connects two points. Each Circuit belongs to a Provider; Providers may have multiple
Expand Down Expand Up @@ -138,8 +136,7 @@ def get_status_class(self):
return CircuitStatusChoices.colors.get(self.status, 'secondary')


@extras_features('webhooks')
class CircuitTermination(ChangeLoggedModel, LinkTermination):
class CircuitTermination(WebhooksMixin, ChangeLoggedModel, LinkTermination):
circuit = models.ForeignKey(
to='circuits.Circuit',
on_delete=models.CASCADE,
Expand Down
3 changes: 0 additions & 3 deletions netbox/circuits/models/providers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from django.urls import reverse

from dcim.fields import ASNField
from extras.utils import extras_features
from netbox.models import PrimaryModel

__all__ = (
Expand All @@ -12,7 +11,6 @@
)


@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
class Provider(PrimaryModel):
"""
Each Circuit belongs to a Provider. This is usually a telecommunications company or similar organization. This model
Expand Down Expand Up @@ -72,7 +70,6 @@ def get_absolute_url(self):
return reverse('circuits:provider', args=[self.pk])


@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
class ProviderNetwork(PrimaryModel):
"""
This represents a provider network which exists outside of NetBox, the details of which are unknown or
Expand Down
2 changes: 0 additions & 2 deletions netbox/dcim/models/cables.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
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 extras.utils import extras_features
from netbox.models import BigIDModel, PrimaryModel
from utilities.fields import ColorField
from utilities.utils import to_meters
Expand All @@ -29,7 +28,6 @@
# Cables
#

@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
class Cable(PrimaryModel):
"""
A physical connection between two endpoints.
Expand Down
16 changes: 3 additions & 13 deletions netbox/dcim/models/device_component_templates.py
Original file line number Diff line number Diff line change
@@ -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.core.exceptions import ObjectDoesNotExist, ValidationError
from django.core.validators import MaxValueValidator, MinValueValidator
Expand All @@ -7,8 +7,8 @@

from dcim.choices import *
from dcim.constants import *
from extras.utils import extras_features
from netbox.models import ChangeLoggedModel
from netbox.models.features import WebhooksMixin
from utilities.fields import ColorField, NaturalOrderingField
from utilities.mptt import TreeManager
from utilities.ordering import naturalize_interface
Expand All @@ -32,7 +32,7 @@
)


class ComponentTemplateModel(ChangeLoggedModel):
class ComponentTemplateModel(WebhooksMixin, ChangeLoggedModel):
device_type = models.ForeignKey(
to='dcim.DeviceType',
on_delete=models.CASCADE,
Expand Down Expand Up @@ -135,7 +135,6 @@ def resolve_name(self, module):
return self.name


@extras_features('webhooks')
class ConsolePortTemplate(ModularComponentTemplateModel):
"""
A template for a ConsolePort to be created for a new Device.
Expand Down Expand Up @@ -164,7 +163,6 @@ def instantiate(self, **kwargs):
)


@extras_features('webhooks')
class ConsoleServerPortTemplate(ModularComponentTemplateModel):
"""
A template for a ConsoleServerPort to be created for a new Device.
Expand Down Expand Up @@ -193,7 +191,6 @@ def instantiate(self, **kwargs):
)


@extras_features('webhooks')
class PowerPortTemplate(ModularComponentTemplateModel):
"""
A template for a PowerPort to be created for a new Device.
Expand Down Expand Up @@ -245,7 +242,6 @@ def clean(self):
})


@extras_features('webhooks')
class PowerOutletTemplate(ModularComponentTemplateModel):
"""
A template for a PowerOutlet to be created for a new Device.
Expand Down Expand Up @@ -307,7 +303,6 @@ def instantiate(self, **kwargs):
)


@extras_features('webhooks')
class InterfaceTemplate(ModularComponentTemplateModel):
"""
A template for a physical data interface on a new Device.
Expand Down Expand Up @@ -347,7 +342,6 @@ def instantiate(self, **kwargs):
)


@extras_features('webhooks')
class FrontPortTemplate(ModularComponentTemplateModel):
"""
Template for a pass-through port on the front of a new Device.
Expand Down Expand Up @@ -420,7 +414,6 @@ def instantiate(self, **kwargs):
)


@extras_features('webhooks')
class RearPortTemplate(ModularComponentTemplateModel):
"""
Template for a pass-through port on the rear of a new Device.
Expand Down Expand Up @@ -460,7 +453,6 @@ def instantiate(self, **kwargs):
)


@extras_features('webhooks')
class ModuleBayTemplate(ComponentTemplateModel):
"""
A template for a ModuleBay to be created for a new parent Device.
Expand All @@ -486,7 +478,6 @@ def instantiate(self, device):
)


@extras_features('webhooks')
class DeviceBayTemplate(ComponentTemplateModel):
"""
A template for a DeviceBay to be created for a new parent Device.
Expand All @@ -511,7 +502,6 @@ def clean(self):
)


@extras_features('webhooks')
class InventoryItemTemplate(MPTTModel, ComponentTemplateModel):
"""
A template for an InventoryItem to be created for a new parent Device.
Expand Down
12 changes: 0 additions & 12 deletions netbox/dcim/models/device_components.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
from dcim.constants import *
from dcim.fields import MACAddressField, WWNField
from dcim.svg import CableTraceSVG
from extras.utils import extras_features
from netbox.models import OrganizationalModel, PrimaryModel
from utilities.choices import ColorChoices
from utilities.fields import ColorField, NaturalOrderingField
Expand Down Expand Up @@ -254,7 +253,6 @@ def connected_endpoint(self):
# Console components
#

@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
class ConsolePort(ModularComponentModel, LinkTermination, PathEndpoint):
"""
A physical console port within a Device. ConsolePorts connect to ConsoleServerPorts.
Expand Down Expand Up @@ -282,7 +280,6 @@ def get_absolute_url(self):
return reverse('dcim:consoleport', kwargs={'pk': self.pk})


@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
class ConsoleServerPort(ModularComponentModel, LinkTermination, PathEndpoint):
"""
A physical port within a Device (typically a designated console server) which provides access to ConsolePorts.
Expand Down Expand Up @@ -314,7 +311,6 @@ def get_absolute_url(self):
# Power components
#

@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
class PowerPort(ModularComponentModel, LinkTermination, PathEndpoint):
"""
A physical power supply (intake) port within a Device. PowerPorts connect to PowerOutlets.
Expand Down Expand Up @@ -407,7 +403,6 @@ def get_power_draw(self):
}


@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
class PowerOutlet(ModularComponentModel, LinkTermination, PathEndpoint):
"""
A physical power outlet (output) within a Device which provides power to a PowerPort.
Expand Down Expand Up @@ -522,7 +517,6 @@ def count_fhrp_groups(self):
return self.fhrp_group_assignments.count()


@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
class Interface(ModularComponentModel, BaseInterface, LinkTermination, PathEndpoint):
"""
A network interface within a Device. A physical Interface can connect to exactly one other Interface.
Expand Down Expand Up @@ -793,7 +787,6 @@ def link(self):
# Pass-through ports
#

@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
class FrontPort(ModularComponentModel, LinkTermination):
"""
A pass-through port on the front of a Device.
Expand Down Expand Up @@ -847,7 +840,6 @@ def clean(self):
})


@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
class RearPort(ModularComponentModel, LinkTermination):
"""
A pass-through port on the rear of a Device.
Expand Down Expand Up @@ -891,7 +883,6 @@ def clean(self):
# Bays
#

@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
class ModuleBay(ComponentModel):
"""
An empty space within a Device which can house a child device
Expand All @@ -912,7 +903,6 @@ def get_absolute_url(self):
return reverse('dcim:modulebay', kwargs={'pk': self.pk})


@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
class DeviceBay(ComponentModel):
"""
An empty space within a Device which can house a child device
Expand Down Expand Up @@ -963,7 +953,6 @@ def clean(self):
#


@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
class InventoryItemRole(OrganizationalModel):
"""
Inventory items may optionally be assigned a functional role.
Expand Down Expand Up @@ -994,7 +983,6 @@ def get_absolute_url(self):
return reverse('dcim:inventoryitemrole', args=[self.pk])


@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
class InventoryItem(MPTTModel, ComponentModel):
"""
An InventoryItem represents a serialized piece of hardware within a Device, such as a line card or power supply.
Expand Down
Loading

0 comments on commit 7002319

Please sign in to comment.