diff --git a/netbox/core/models/change_logging.py b/netbox/core/models/change_logging.py index 1d1bbc07c..bf9a32f00 100644 --- a/netbox/core/models/change_logging.py +++ b/netbox/core/models/change_logging.py @@ -11,8 +11,8 @@ from mptt.models import MPTTModel from core.choices import ObjectChangeActionChoices from core.querysets import ObjectChangeQuerySet from netbox.models.features import ChangeLoggingMixin +from netbox.models.features import has_feature from utilities.data import shallow_compare_dict -from .contenttypes import ObjectType __all__ = ( 'ObjectChange', @@ -118,7 +118,7 @@ class ObjectChange(models.Model): super().clean() # 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( _("Change logging is not supported for this object type ({type}).").format( type=self.changed_object_type diff --git a/netbox/core/models/jobs.py b/netbox/core/models/jobs.py index 050f71921..0377ffbb1 100644 --- a/netbox/core/models/jobs.py +++ b/netbox/core/models/jobs.py @@ -20,6 +20,7 @@ from core.choices import JobStatusChoices from core.dataclasses import JobLogEntry from core.models import ObjectType from core.signals import job_end, job_start +from netbox.models.features import has_feature from utilities.json import JobLogDecoder from utilities.querysets import RestrictedQuerySet from utilities.rqworker import get_queue_for_model @@ -148,7 +149,7 @@ class Job(models.Model): super().clean() # 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( _("Jobs cannot be assigned to this object type ({type}).").format(type=self.object_type) ) diff --git a/netbox/extras/models/models.py b/netbox/extras/models/models.py index 2fdc1ffe3..a486ec1ee 100644 --- a/netbox/extras/models/models.py +++ b/netbox/extras/models/models.py @@ -12,17 +12,16 @@ from django.utils import timezone from django.utils.translation import gettext_lazy as _ from rest_framework.utils.encoders import JSONEncoder -from core.models import ObjectType from extras.choices import * from extras.conditions import ConditionSet, InvalidCondition from extras.constants import * -from extras.utils import image_upload from extras.models.mixins import RenderTemplateMixin +from extras.utils import image_upload from netbox.config import get_config from netbox.events import get_event_type_choices from netbox.models import ChangeLoggedModel 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.jinja2 import render_jinja2 @@ -707,7 +706,7 @@ class ImageAttachment(ChangeLoggedModel): super().clean() # 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( _("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() # 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( _("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() # 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( _("Bookmarks cannot be assigned to this object type ({type}).").format(type=self.object_type) ) diff --git a/netbox/extras/models/notifications.py b/netbox/extras/models/notifications.py index 44874a4c8..c8e8c4fd8 100644 --- a/netbox/extras/models/notifications.py +++ b/netbox/extras/models/notifications.py @@ -7,9 +7,9 @@ from django.db import models from django.urls import reverse from django.utils.translation import gettext_lazy as _ -from core.models import ObjectType from extras.querysets import NotificationQuerySet from netbox.models import ChangeLoggedModel +from netbox.models.features import has_feature from netbox.registry import registry from users.models import User from utilities.querysets import RestrictedQuerySet @@ -94,7 +94,7 @@ class Notification(models.Model): super().clean() # 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( _("Objects of this type ({type}) do not support notifications.").format(type=self.object_type) ) @@ -235,7 +235,7 @@ class Subscription(models.Model): super().clean() # 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( _("Objects of this type ({type}) do not support notifications.").format(type=self.object_type) ) diff --git a/netbox/netbox/models/features.py b/netbox/netbox/models/features.py index c81727d68..ca6cbcbd0 100644 --- a/netbox/netbox/models/features.py +++ b/netbox/netbox/models/features.py @@ -3,6 +3,7 @@ from collections import defaultdict from functools import cached_property from django.contrib.contenttypes.fields import GenericRelation +from django.contrib.contenttypes.models import ContentType from django.core.validators import ValidationError from django.db import models from django.db.models import Q @@ -39,6 +40,7 @@ __all__ = ( 'NotificationsMixin', 'SyncedDataMixin', 'TagsMixin', + 'has_feature', '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): """ Register one or more models in NetBox. This entails: diff --git a/netbox/tenancy/models/contacts.py b/netbox/tenancy/models/contacts.py index 34e444ee7..19ffb2b0b 100644 --- a/netbox/tenancy/models/contacts.py +++ b/netbox/tenancy/models/contacts.py @@ -4,9 +4,8 @@ from django.db import models from django.urls import reverse from django.utils.translation import gettext_lazy as _ -from core.models import ObjectType 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 * __all__ = ( @@ -151,7 +150,7 @@ class ContactAssignment(CustomFieldsMixin, ExportTemplatesMixin, TagsMixin, Chan super().clean() # 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( _("Contacts cannot be assigned to this object type ({type}).").format(type=self.object_type) )