mirror of
https://github.com/netbox-community/netbox.git
synced 2025-09-06 14:23:36 -06:00
Closes #20129: Enable dynamic model feature registration
This commit is contained in:
parent
6d4cc16ca4
commit
a9a97ebec1
@ -22,24 +22,9 @@ Stores registration made using `netbox.denormalized.register()`. For each model,
|
||||
|
||||
### `model_features`
|
||||
|
||||
A dictionary of particular features (e.g. custom fields) mapped to the NetBox models which support them, arranged by app. For example:
|
||||
A dictionary of model features (e.g. custom fields, tags, etc.) mapped to the functions used to qualify a model as supporting each feature. Model features are registered using the `register_model_feature()` function in `netbox.utils`.
|
||||
|
||||
```python
|
||||
{
|
||||
'custom_fields': {
|
||||
'circuits': ['provider', 'circuit'],
|
||||
'dcim': ['site', 'rack', 'devicetype', ...],
|
||||
...
|
||||
},
|
||||
'event_rules': {
|
||||
'extras': ['configcontext', 'tag', ...],
|
||||
'dcim': ['site', 'rack', 'devicetype', ...],
|
||||
},
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Supported model features are listed in the [features matrix](./models.md#features-matrix).
|
||||
Core model features are listed in the [features matrix](./models.md#features-matrix).
|
||||
|
||||
### `models`
|
||||
|
||||
|
@ -11,18 +11,25 @@ The Django [content types](https://docs.djangoproject.com/en/stable/ref/contrib/
|
||||
Depending on its classification, each NetBox model may support various features which enhance its operation. Each feature is enabled by inheriting from its designated mixin class, and some features also make use of the [application registry](./application-registry.md#model_features).
|
||||
|
||||
| Feature | Feature Mixin | Registry Key | Description |
|
||||
|------------------------------------------------------------|-------------------------|--------------------|-----------------------------------------------------------------------------------------|
|
||||
| [Change logging](../features/change-logging.md) | `ChangeLoggingMixin` | - | Changes to these objects are automatically recorded in the change log |
|
||||
| Cloning | `CloningMixin` | - | Provides the `clone()` method to prepare a copy |
|
||||
|------------------------------------------------------------|-------------------------|---------------------|-----------------------------------------------------------------------------------------|
|
||||
| [Bookmarks](../features/customization.md#bookmarks) | `BookmarksMixin` | `bookmarks` | These models can be bookmarked natively in the user interface |
|
||||
| [Change logging](../features/change-logging.md) | `ChangeLoggingMixin` | `change_logging` | Changes to these objects are automatically recorded in the change log |
|
||||
| Cloning | `CloningMixin` | `cloning` | Provides the `clone()` method to prepare a copy |
|
||||
| [Contacts](../features/contacts.md) | `ContactsMixin` | `contacts` | Contacts can be associated with these models |
|
||||
| [Custom fields](../customization/custom-fields.md) | `CustomFieldsMixin` | `custom_fields` | These models support the addition of user-defined fields |
|
||||
| [Custom links](../customization/custom-links.md) | `CustomLinksMixin` | `custom_links` | These models support the assignment of custom links |
|
||||
| [Custom validation](../customization/custom-validation.md) | `CustomValidationMixin` | - | Supports the enforcement of custom validation rules |
|
||||
| [Event rules](../features/event-rules.md) | `EventRulesMixin` | `event_rules` | Event rules can send webhooks or run custom scripts automatically in response to events |
|
||||
| [Export templates](../customization/export-templates.md) | `ExportTemplatesMixin` | `export_templates` | Users can create custom export templates for these models |
|
||||
| [Job results](../features/background-jobs.md) | `JobsMixin` | `jobs` | Background jobs can be scheduled for these models |
|
||||
| [Image attachments](../models/extras/imageattachment.md) | `ImageAttachmentsMixin` | `image_attachments` | Image uploads can be attached to these models |
|
||||
| [Jobs](../features/background-jobs.md) | `JobsMixin` | `jobs` | Background jobs can be scheduled for these models |
|
||||
| [Journaling](../features/journaling.md) | `JournalingMixin` | `journaling` | These models support persistent historical commentary |
|
||||
| [Notifications](../features/notifications.md) | `NotificationsMixin` | `notifications` | These models support user notifications |
|
||||
| [Synchronized data](../integrations/synchronized-data.md) | `SyncedDataMixin` | `synced_data` | Certain model data can be automatically synchronized from a remote data source |
|
||||
| [Tagging](../models/extras/tag.md) | `TagsMixin` | `tags` | The models can be tagged with user-defined tags |
|
||||
| [Event rules](../features/event-rules.md) | `EventRulesMixin` | `event_rules` | Event rules can send webhooks or run custom scripts automatically in response to events |
|
||||
|
||||
!!! note
|
||||
The above listed features are supported natively by NetBox. Beginning with NetBox v4.4.0, plugins can register their own model features as well.
|
||||
|
||||
## Models Index
|
||||
|
||||
|
@ -24,20 +24,7 @@ Every model includes by default a numeric primary key. This value is generated a
|
||||
|
||||
## Enabling NetBox Features
|
||||
|
||||
Plugin models can leverage certain NetBox features by inheriting from NetBox's `NetBoxModel` class. This class extends the plugin model to enable features unique to NetBox, including:
|
||||
|
||||
* Bookmarks
|
||||
* Change logging
|
||||
* Cloning
|
||||
* Custom fields
|
||||
* Custom links
|
||||
* Custom validation
|
||||
* Export templates
|
||||
* Journaling
|
||||
* Tags
|
||||
* Webhooks
|
||||
|
||||
This class performs two crucial functions:
|
||||
Plugin models can leverage certain [model features](../development/models.md#features-matrix) (such as tags, custom fields, event rules, etc.) by inheriting from NetBox's `NetBoxModel` class. This class performs two crucial functions:
|
||||
|
||||
1. Apply any fields, methods, and/or attributes necessary to the operation of these features
|
||||
2. Register the model with NetBox as utilizing these features
|
||||
@ -135,6 +122,27 @@ For more information about database migrations, see the [Django documentation](h
|
||||
|
||||
::: netbox.models.features.TagsMixin
|
||||
|
||||
## Custom Model Features
|
||||
|
||||
In addition to utilizing the model features provided natively by NetBox (listed above), plugins can register their own model features. This is done using the `register_model_feature()` function from `netbox.models.features`. This function takes two arguments: a feature name, and a callable which accepts a model class. The callable must return a boolean value indicting whether the given model supports the named feature.
|
||||
|
||||
This function can be used as a decorator:
|
||||
|
||||
```python
|
||||
@register_model_feature('foo')
|
||||
def supports_foo(model):
|
||||
# Your logic here
|
||||
```
|
||||
|
||||
Or it can be called directly:
|
||||
|
||||
```python
|
||||
register_model_feature('foo', supports_foo)
|
||||
```
|
||||
|
||||
!!! tip
|
||||
Consider performing feature registration inside your PluginConfig's `ready()` method.
|
||||
|
||||
## Choice Sets
|
||||
|
||||
For model fields which support the selection of one or more values from a predefined list of choices, NetBox provides the `ChoiceSet` utility class. This can be used in place of a regular choices tuple to provide enhanced functionality, namely dynamic configuration and colorization. (See [Django's documentation](https://docs.djangoproject.com/en/stable/ref/models/fields/#choices) on the `choices` parameter for supported model fields.)
|
||||
|
@ -135,9 +135,9 @@ class ObjectTypeManager(models.Manager):
|
||||
"""
|
||||
Return ObjectTypes only for models which support the given feature.
|
||||
|
||||
Only ObjectTypes which list the specified feature will be included. Supported features are declared in
|
||||
netbox.models.features.FEATURES_MAP. For example, we can find all ObjectTypes for models which support event
|
||||
rules with:
|
||||
Only ObjectTypes which list the specified feature will be included. Supported features are declared in the
|
||||
application registry under `registry["model_features"]`. For example, we can find all ObjectTypes for models
|
||||
which support event rules with:
|
||||
|
||||
ObjectType.objects.with_feature('event_rules')
|
||||
"""
|
||||
|
@ -22,6 +22,7 @@ from netbox.models.deletion import DeleteMixin
|
||||
from netbox.plugins import PluginConfig
|
||||
from netbox.registry import registry
|
||||
from netbox.signals import post_clean
|
||||
from netbox.utils import register_model_feature
|
||||
from utilities.json import CustomFieldJSONEncoder
|
||||
from utilities.serialization import serialize_object
|
||||
|
||||
@ -35,7 +36,6 @@ __all__ = (
|
||||
'CustomValidationMixin',
|
||||
'EventRulesMixin',
|
||||
'ExportTemplatesMixin',
|
||||
'FEATURES_MAP',
|
||||
'ImageAttachmentsMixin',
|
||||
'JobsMixin',
|
||||
'JournalingMixin',
|
||||
@ -628,28 +628,21 @@ class SyncedDataMixin(models.Model):
|
||||
# Feature registration
|
||||
#
|
||||
|
||||
FEATURES_MAP = {
|
||||
'bookmarks': BookmarksMixin,
|
||||
'change_logging': ChangeLoggingMixin,
|
||||
'cloning': CloningMixin,
|
||||
'contacts': ContactsMixin,
|
||||
'custom_fields': CustomFieldsMixin,
|
||||
'custom_links': CustomLinksMixin,
|
||||
'custom_validation': CustomValidationMixin,
|
||||
'event_rules': EventRulesMixin,
|
||||
'export_templates': ExportTemplatesMixin,
|
||||
'image_attachments': ImageAttachmentsMixin,
|
||||
'jobs': JobsMixin,
|
||||
'journaling': JournalingMixin,
|
||||
'notifications': NotificationsMixin,
|
||||
'synced_data': SyncedDataMixin,
|
||||
'tags': TagsMixin,
|
||||
}
|
||||
|
||||
# TODO: Remove in NetBox v4.5
|
||||
registry['model_features'].update({
|
||||
feature: defaultdict(set) for feature in FEATURES_MAP.keys()
|
||||
})
|
||||
register_model_feature('bookmarks', lambda model: issubclass(model, BookmarksMixin))
|
||||
register_model_feature('change_logging', lambda model: issubclass(model, ChangeLoggingMixin))
|
||||
register_model_feature('cloning', lambda model: issubclass(model, CloningMixin))
|
||||
register_model_feature('contacts', lambda model: issubclass(model, ContactsMixin))
|
||||
register_model_feature('custom_fields', lambda model: issubclass(model, CustomFieldsMixin))
|
||||
register_model_feature('custom_links', lambda model: issubclass(model, CustomLinksMixin))
|
||||
register_model_feature('custom_validation', lambda model: issubclass(model, CustomValidationMixin))
|
||||
register_model_feature('event_rules', lambda model: issubclass(model, EventRulesMixin))
|
||||
register_model_feature('export_templates', lambda model: issubclass(model, ExportTemplatesMixin))
|
||||
register_model_feature('image_attachments', lambda model: issubclass(model, ImageAttachmentsMixin))
|
||||
register_model_feature('jobs', lambda model: issubclass(model, JobsMixin))
|
||||
register_model_feature('journaling', lambda model: issubclass(model, JournalingMixin))
|
||||
register_model_feature('notifications', lambda model: issubclass(model, NotificationsMixin))
|
||||
register_model_feature('synced_data', lambda model: issubclass(model, SyncedDataMixin))
|
||||
register_model_feature('tags', lambda model: issubclass(model, TagsMixin))
|
||||
|
||||
|
||||
def model_is_public(model):
|
||||
@ -665,8 +658,11 @@ def model_is_public(model):
|
||||
|
||||
|
||||
def get_model_features(model):
|
||||
"""
|
||||
Return all features supported by the given model.
|
||||
"""
|
||||
return [
|
||||
feature for feature, cls in FEATURES_MAP.items() if issubclass(model, cls)
|
||||
feature for feature, test_func in registry['model_features'].items() if test_func(model)
|
||||
]
|
||||
|
||||
|
||||
@ -710,19 +706,6 @@ def register_models(*models):
|
||||
if not getattr(model, '_netbox_private', False):
|
||||
registry['models'][app_label].add(model_name)
|
||||
|
||||
# TODO: Remove in NetBox v4.5
|
||||
# Record each applicable feature for the model in the registry
|
||||
features = {
|
||||
feature for feature, cls in FEATURES_MAP.items() if issubclass(model, cls)
|
||||
}
|
||||
for feature in features:
|
||||
try:
|
||||
registry['model_features'][feature][app_label].add(model_name)
|
||||
except KeyError:
|
||||
raise KeyError(
|
||||
f"{feature} is not a valid model feature! Valid keys are: {registry['model_features'].keys()}"
|
||||
)
|
||||
|
||||
# Register applicable feature views for the model
|
||||
if issubclass(model, ContactsMixin):
|
||||
register_model_view(model, 'contacts', kwargs={'model': model})(
|
||||
|
@ -3,6 +3,7 @@ from netbox.registry import registry
|
||||
__all__ = (
|
||||
'get_data_backend_choices',
|
||||
'register_data_backend',
|
||||
'register_model_feature',
|
||||
'register_request_processor',
|
||||
)
|
||||
|
||||
@ -27,6 +28,35 @@ def register_data_backend():
|
||||
return _wrapper
|
||||
|
||||
|
||||
def register_model_feature(name, func=None):
|
||||
"""
|
||||
Register a model feature with its qualifying function.
|
||||
|
||||
The qualifying function must accept a single `model` argument. It will be called to determine whether the given
|
||||
model supports the corresponding feature.
|
||||
|
||||
This function can be used directly:
|
||||
|
||||
register_model_feature('my_feature', my_func)
|
||||
|
||||
Or as a decorator:
|
||||
|
||||
@register_model_feature('my_feature')
|
||||
def my_func(model):
|
||||
...
|
||||
"""
|
||||
def decorator(f):
|
||||
registry['model_features'][name] = f
|
||||
return f
|
||||
|
||||
if name in registry['model_features']:
|
||||
raise ValueError(f"A model feature named {name} is already registered.")
|
||||
|
||||
if func is None:
|
||||
return decorator
|
||||
return decorator(func)
|
||||
|
||||
|
||||
def register_request_processor(func):
|
||||
"""
|
||||
Decorator for registering a request processor.
|
||||
|
Loading…
Reference in New Issue
Block a user