Introduce has_feature() utility function

This commit is contained in:
Jeremy Stretch 2025-07-23 14:40:42 -04:00
parent e38ba29928
commit 0d8b6c78ae
6 changed files with 25 additions and 15 deletions

View File

@ -11,8 +11,8 @@ from mptt.models import MPTTModel
from core.choices import ObjectChangeActionChoices from core.choices import ObjectChangeActionChoices
from core.querysets import ObjectChangeQuerySet from core.querysets import ObjectChangeQuerySet
from netbox.models.features import ChangeLoggingMixin from netbox.models.features import ChangeLoggingMixin
from netbox.models.features import has_feature
from utilities.data import shallow_compare_dict from utilities.data import shallow_compare_dict
from .contenttypes import ObjectType
__all__ = ( __all__ = (
'ObjectChange', 'ObjectChange',
@ -118,7 +118,7 @@ class ObjectChange(models.Model):
super().clean() super().clean()
# Validate the assigned object type # Validate the assigned object type
if self.changed_object_type not in ObjectType.objects.with_feature('change_logging'): if not has_feature(self.changed_object_type, 'change_logging'):
raise ValidationError( raise ValidationError(
_("Change logging is not supported for this object type ({type}).").format( _("Change logging is not supported for this object type ({type}).").format(
type=self.changed_object_type type=self.changed_object_type

View File

@ -20,6 +20,7 @@ from core.choices import JobStatusChoices
from core.dataclasses import JobLogEntry from core.dataclasses import JobLogEntry
from core.models import ObjectType from core.models import ObjectType
from core.signals import job_end, job_start from core.signals import job_end, job_start
from netbox.models.features import has_feature
from utilities.json import JobLogDecoder from utilities.json import JobLogDecoder
from utilities.querysets import RestrictedQuerySet from utilities.querysets import RestrictedQuerySet
from utilities.rqworker import get_queue_for_model from utilities.rqworker import get_queue_for_model
@ -148,7 +149,7 @@ class Job(models.Model):
super().clean() super().clean()
# Validate the assigned object type # Validate the assigned object type
if self.object_type and self.object_type not in ObjectType.objects.with_feature('jobs'): if self.object_type and not has_feature(self.object_type, 'jobs'):
raise ValidationError( raise ValidationError(
_("Jobs cannot be assigned to this object type ({type}).").format(type=self.object_type) _("Jobs cannot be assigned to this object type ({type}).").format(type=self.object_type)
) )

View File

@ -12,17 +12,16 @@ from django.utils import timezone
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from rest_framework.utils.encoders import JSONEncoder from rest_framework.utils.encoders import JSONEncoder
from core.models import ObjectType
from extras.choices import * from extras.choices import *
from extras.conditions import ConditionSet, InvalidCondition from extras.conditions import ConditionSet, InvalidCondition
from extras.constants import * from extras.constants import *
from extras.utils import image_upload
from extras.models.mixins import RenderTemplateMixin from extras.models.mixins import RenderTemplateMixin
from extras.utils import image_upload
from netbox.config import get_config from netbox.config import get_config
from netbox.events import get_event_type_choices from netbox.events import get_event_type_choices
from netbox.models import ChangeLoggedModel from netbox.models import ChangeLoggedModel
from netbox.models.features import ( from netbox.models.features import (
CloningMixin, CustomFieldsMixin, CustomLinksMixin, ExportTemplatesMixin, SyncedDataMixin, TagsMixin CloningMixin, CustomFieldsMixin, CustomLinksMixin, ExportTemplatesMixin, SyncedDataMixin, TagsMixin, has_feature
) )
from utilities.html import clean_html from utilities.html import clean_html
from utilities.jinja2 import render_jinja2 from utilities.jinja2 import render_jinja2
@ -707,7 +706,7 @@ class ImageAttachment(ChangeLoggedModel):
super().clean() super().clean()
# Validate the assigned object type # Validate the assigned object type
if self.object_type not in ObjectType.objects.with_feature('image_attachments'): if not has_feature(self.object_type, 'image_attachments'):
raise ValidationError( raise ValidationError(
_("Image attachments cannot be assigned to this object type ({type}).").format(type=self.object_type) _("Image attachments cannot be assigned to this object type ({type}).").format(type=self.object_type)
) )
@ -807,7 +806,7 @@ class JournalEntry(CustomFieldsMixin, CustomLinksMixin, TagsMixin, ExportTemplat
super().clean() super().clean()
# Validate the assigned object type # Validate the assigned object type
if self.assigned_object_type not in ObjectType.objects.with_feature('journaling'): if not has_feature(self.assigned_object_type, 'journaling'):
raise ValidationError( raise ValidationError(
_("Journaling is not supported for this object type ({type}).").format(type=self.assigned_object_type) _("Journaling is not supported for this object type ({type}).").format(type=self.assigned_object_type)
) )
@ -863,7 +862,7 @@ class Bookmark(models.Model):
super().clean() super().clean()
# Validate the assigned object type # Validate the assigned object type
if self.object_type not in ObjectType.objects.with_feature('bookmarks'): if not has_feature(self.object_type, 'bookmarks'):
raise ValidationError( raise ValidationError(
_("Bookmarks cannot be assigned to this object type ({type}).").format(type=self.object_type) _("Bookmarks cannot be assigned to this object type ({type}).").format(type=self.object_type)
) )

View File

@ -7,9 +7,9 @@ from django.db import models
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from core.models import ObjectType
from extras.querysets import NotificationQuerySet from extras.querysets import NotificationQuerySet
from netbox.models import ChangeLoggedModel from netbox.models import ChangeLoggedModel
from netbox.models.features import has_feature
from netbox.registry import registry from netbox.registry import registry
from users.models import User from users.models import User
from utilities.querysets import RestrictedQuerySet from utilities.querysets import RestrictedQuerySet
@ -94,7 +94,7 @@ class Notification(models.Model):
super().clean() super().clean()
# Validate the assigned object type # Validate the assigned object type
if self.object_type not in ObjectType.objects.with_feature('notifications'): if not has_feature(self.object_type, 'notifications'):
raise ValidationError( raise ValidationError(
_("Objects of this type ({type}) do not support notifications.").format(type=self.object_type) _("Objects of this type ({type}) do not support notifications.").format(type=self.object_type)
) )
@ -235,7 +235,7 @@ class Subscription(models.Model):
super().clean() super().clean()
# Validate the assigned object type # Validate the assigned object type
if self.object_type not in ObjectType.objects.with_feature('notifications'): if not has_feature(self.object_type, 'notifications'):
raise ValidationError( raise ValidationError(
_("Objects of this type ({type}) do not support notifications.").format(type=self.object_type) _("Objects of this type ({type}) do not support notifications.").format(type=self.object_type)
) )

View File

@ -3,6 +3,7 @@ from collections import defaultdict
from functools import cached_property from functools import cached_property
from django.contrib.contenttypes.fields import GenericRelation from django.contrib.contenttypes.fields import GenericRelation
from django.contrib.contenttypes.models import ContentType
from django.core.validators import ValidationError from django.core.validators import ValidationError
from django.db import models from django.db import models
from django.db.models import Q from django.db.models import Q
@ -39,6 +40,7 @@ __all__ = (
'NotificationsMixin', 'NotificationsMixin',
'SyncedDataMixin', 'SyncedDataMixin',
'TagsMixin', 'TagsMixin',
'has_feature',
'register_models', 'register_models',
) )
@ -640,6 +642,15 @@ registry['model_features'].update({
}) })
def has_feature(model, feature):
"""
Returns True if the model supports the specified feature.
"""
if type(model) is ContentType:
model = model.model_class()
return feature in ObjectType.objects.get_for_model(model).features
def register_models(*models): def register_models(*models):
""" """
Register one or more models in NetBox. This entails: Register one or more models in NetBox. This entails:

View File

@ -4,9 +4,8 @@ from django.db import models
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from core.models import ObjectType
from netbox.models import ChangeLoggedModel, NestedGroupModel, OrganizationalModel, PrimaryModel from netbox.models import ChangeLoggedModel, NestedGroupModel, OrganizationalModel, PrimaryModel
from netbox.models.features import CustomFieldsMixin, ExportTemplatesMixin, TagsMixin from netbox.models.features import CustomFieldsMixin, ExportTemplatesMixin, TagsMixin, has_feature
from tenancy.choices import * from tenancy.choices import *
__all__ = ( __all__ = (
@ -151,7 +150,7 @@ class ContactAssignment(CustomFieldsMixin, ExportTemplatesMixin, TagsMixin, Chan
super().clean() super().clean()
# Validate the assigned object type # Validate the assigned object type
if self.object_type not in ObjectType.objects.with_feature('contacts'): if not has_feature(self.object_type, 'contacts'):
raise ValidationError( raise ValidationError(
_("Contacts cannot be assigned to this object type ({type}).").format(type=self.object_type) _("Contacts cannot be assigned to this object type ({type}).").format(type=self.object_type)
) )