This commit is contained in:
Jeremy Stretch 2023-11-03 13:25:51 -04:00
parent 8dcbd66de6
commit 7d5c36c573
7 changed files with 45 additions and 45 deletions

View File

@ -1,12 +1,10 @@
from django import forms from django import forms
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from core.choices import * from core.choices import *
from core.models import * from core.models import *
from extras.forms.mixins import SavedFiltersMixin from extras.forms.mixins import SavedFiltersMixin
from extras.utils import FeatureQuery
from netbox.forms import NetBoxModelFilterSetForm from netbox.forms import NetBoxModelFilterSetForm
from netbox.utils import get_data_backend_choices from netbox.utils import get_data_backend_choices
from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES, FilterForm from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES, FilterForm
@ -69,7 +67,7 @@ class JobFilterForm(SavedFiltersMixin, FilterForm):
) )
object_type = ContentTypeChoiceField( object_type = ContentTypeChoiceField(
label=_('Object Type'), label=_('Object Type'),
queryset=ContentType.objects.filter(FeatureQuery('jobs').get_query()), queryset=ContentType.objects.with_feature('jobs'),
required=False, required=False,
) )
status = forms.MultipleChoiceField( status = forms.MultipleChoiceField(

View File

@ -21,6 +21,24 @@ class ContentTypeManager(ContentTypeManager_):
q |= Q(app_label=app_label, model__in=models) q |= Q(app_label=app_label, model__in=models)
return self.get_queryset().filter(q) return self.get_queryset().filter(q)
def with_feature(self, feature):
"""
Return the ContentTypes only for models which are registered as supporting the specified feature. For example,
we can find all ContentTypes for models which support webhooks with
ContentType.objects.with_feature('webhooks')
"""
if feature not in registry['model_features']:
raise KeyError(
f"{feature} is not a registered model feature! Valid features are: {registry['model_features'].keys()}"
)
q = Q()
for app_label, models in registry['model_features'][feature].items():
q |= Q(app_label=app_label, model__in=models)
return self.get_queryset().filter(q)
class ContentType(ContentType_): class ContentType(ContentType_):
""" """

View File

@ -1,10 +1,10 @@
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from rest_framework import serializers from rest_framework import serializers
from core.api.serializers import JobSerializer from core.api.serializers import JobSerializer
from core.api.nested_serializers import NestedDataSourceSerializer, NestedDataFileSerializer, NestedJobSerializer from core.api.nested_serializers import NestedDataSourceSerializer, NestedDataFileSerializer, NestedJobSerializer
from core.models import ContentType
from dcim.api.nested_serializers import ( from dcim.api.nested_serializers import (
NestedDeviceRoleSerializer, NestedDeviceTypeSerializer, NestedLocationSerializer, NestedPlatformSerializer, NestedDeviceRoleSerializer, NestedDeviceTypeSerializer, NestedLocationSerializer, NestedPlatformSerializer,
NestedRegionSerializer, NestedSiteSerializer, NestedSiteGroupSerializer, NestedRegionSerializer, NestedSiteSerializer, NestedSiteGroupSerializer,
@ -14,7 +14,6 @@ from drf_spectacular.utils import extend_schema_field
from drf_spectacular.types import OpenApiTypes from drf_spectacular.types import OpenApiTypes
from extras.choices import * from extras.choices import *
from extras.models import * from extras.models import *
from extras.utils import FeatureQuery
from netbox.api.exceptions import SerializerNotFound from netbox.api.exceptions import SerializerNotFound
from netbox.api.fields import ChoiceField, ContentTypeField, SerializedPKRelatedField from netbox.api.fields import ChoiceField, ContentTypeField, SerializedPKRelatedField
from netbox.api.serializers import BaseModelSerializer, NetBoxModelSerializer, ValidatedModelSerializer from netbox.api.serializers import BaseModelSerializer, NetBoxModelSerializer, ValidatedModelSerializer
@ -64,7 +63,7 @@ __all__ = (
class WebhookSerializer(NetBoxModelSerializer): class WebhookSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='extras-api:webhook-detail') url = serializers.HyperlinkedIdentityField(view_name='extras-api:webhook-detail')
content_types = ContentTypeField( content_types = ContentTypeField(
queryset=ContentType.objects.filter(FeatureQuery('webhooks').get_query()), queryset=ContentType.objects.with_feature('webhooks'),
many=True many=True
) )
@ -85,7 +84,7 @@ class WebhookSerializer(NetBoxModelSerializer):
class CustomFieldSerializer(ValidatedModelSerializer): class CustomFieldSerializer(ValidatedModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='extras-api:customfield-detail') url = serializers.HyperlinkedIdentityField(view_name='extras-api:customfield-detail')
content_types = ContentTypeField( content_types = ContentTypeField(
queryset=ContentType.objects.filter(FeatureQuery('custom_fields').get_query()), queryset=ContentType.objects.with_feature('custom_fields'),
many=True many=True
) )
type = ChoiceField(choices=CustomFieldTypeChoices) type = ChoiceField(choices=CustomFieldTypeChoices)
@ -151,7 +150,7 @@ class CustomFieldChoiceSetSerializer(ValidatedModelSerializer):
class CustomLinkSerializer(ValidatedModelSerializer): class CustomLinkSerializer(ValidatedModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='extras-api:customlink-detail') url = serializers.HyperlinkedIdentityField(view_name='extras-api:customlink-detail')
content_types = ContentTypeField( content_types = ContentTypeField(
queryset=ContentType.objects.filter(FeatureQuery('custom_links').get_query()), queryset=ContentType.objects.with_feature('custom_links'),
many=True many=True
) )
@ -170,7 +169,7 @@ class CustomLinkSerializer(ValidatedModelSerializer):
class ExportTemplateSerializer(ValidatedModelSerializer): class ExportTemplateSerializer(ValidatedModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='extras-api:exporttemplate-detail') url = serializers.HyperlinkedIdentityField(view_name='extras-api:exporttemplate-detail')
content_types = ContentTypeField( content_types = ContentTypeField(
queryset=ContentType.objects.filter(FeatureQuery('export_templates').get_query()), queryset=ContentType.objects.with_feature('export_templates'),
many=True many=True
) )
data_source = NestedDataSourceSerializer( data_source = NestedDataSourceSerializer(
@ -215,7 +214,7 @@ class SavedFilterSerializer(ValidatedModelSerializer):
class BookmarkSerializer(ValidatedModelSerializer): class BookmarkSerializer(ValidatedModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='extras-api:bookmark-detail') url = serializers.HyperlinkedIdentityField(view_name='extras-api:bookmark-detail')
object_type = ContentTypeField( object_type = ContentTypeField(
queryset=ContentType.objects.filter(FeatureQuery('bookmarks').get_query()), queryset=ContentType.objects.with_feature('bookmarks'),
) )
object = serializers.SerializerMethodField(read_only=True) object = serializers.SerializerMethodField(read_only=True)
user = NestedUserSerializer() user = NestedUserSerializer()
@ -239,7 +238,7 @@ class BookmarkSerializer(ValidatedModelSerializer):
class TagSerializer(ValidatedModelSerializer): class TagSerializer(ValidatedModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='extras-api:tag-detail') url = serializers.HyperlinkedIdentityField(view_name='extras-api:tag-detail')
object_types = ContentTypeField( object_types = ContentTypeField(
queryset=ContentType.objects.filter(FeatureQuery('tags').get_query()), queryset=ContentType.objects.with_feature('tags'),
many=True, many=True,
required=False required=False
) )

View File

@ -6,7 +6,6 @@ from django.utils.translation import gettext_lazy as _
from core.models import ContentType from core.models import ContentType
from extras.choices import * from extras.choices import *
from extras.models import * from extras.models import *
from extras.utils import FeatureQuery
from netbox.forms import NetBoxModelImportForm from netbox.forms import NetBoxModelImportForm
from utilities.forms import CSVModelForm from utilities.forms import CSVModelForm
from utilities.forms.fields import ( from utilities.forms.fields import (
@ -29,8 +28,7 @@ __all__ = (
class CustomFieldImportForm(CSVModelForm): class CustomFieldImportForm(CSVModelForm):
content_types = CSVMultipleContentTypeField( content_types = CSVMultipleContentTypeField(
label=_('Content types'), label=_('Content types'),
queryset=ContentType.objects.all(), queryset=ContentType.objects.with_feature('custom_fields'),
limit_choices_to=FeatureQuery('custom_fields'),
help_text=_("One or more assigned object types") help_text=_("One or more assigned object types")
) )
type = CSVChoiceField( type = CSVChoiceField(
@ -88,8 +86,7 @@ class CustomFieldChoiceSetImportForm(CSVModelForm):
class CustomLinkImportForm(CSVModelForm): class CustomLinkImportForm(CSVModelForm):
content_types = CSVMultipleContentTypeField( content_types = CSVMultipleContentTypeField(
label=_('Content types'), label=_('Content types'),
queryset=ContentType.objects.all(), queryset=ContentType.objects.with_feature('custom_links'),
limit_choices_to=FeatureQuery('custom_links'),
help_text=_("One or more assigned object types") help_text=_("One or more assigned object types")
) )
@ -104,8 +101,7 @@ class CustomLinkImportForm(CSVModelForm):
class ExportTemplateImportForm(CSVModelForm): class ExportTemplateImportForm(CSVModelForm):
content_types = CSVMultipleContentTypeField( content_types = CSVMultipleContentTypeField(
label=_('Content types'), label=_('Content types'),
queryset=ContentType.objects.all(), queryset=ContentType.objects.with_feature('export_templates'),
limit_choices_to=FeatureQuery('export_templates'),
help_text=_("One or more assigned object types") help_text=_("One or more assigned object types")
) )
@ -142,8 +138,7 @@ class SavedFilterImportForm(CSVModelForm):
class WebhookImportForm(NetBoxModelImportForm): class WebhookImportForm(NetBoxModelImportForm):
content_types = CSVMultipleContentTypeField( content_types = CSVMultipleContentTypeField(
label=_('Content types'), label=_('Content types'),
queryset=ContentType.objects.all(), queryset=ContentType.objects.with_feature('webhooks'),
limit_choices_to=FeatureQuery('webhooks'),
help_text=_("One or more assigned object types") help_text=_("One or more assigned object types")
) )

View File

@ -6,7 +6,6 @@ from core.models import ContentType, DataFile, DataSource
from dcim.models import DeviceRole, DeviceType, Location, Platform, Region, Site, SiteGroup from dcim.models import DeviceRole, DeviceType, Location, Platform, Region, Site, SiteGroup
from extras.choices import * from extras.choices import *
from extras.models import * from extras.models import *
from extras.utils import FeatureQuery
from netbox.forms.base import NetBoxModelFilterSetForm from netbox.forms.base import NetBoxModelFilterSetForm
from tenancy.models import Tenant, TenantGroup from tenancy.models import Tenant, TenantGroup
from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES, FilterForm, add_blank_choice from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES, FilterForm, add_blank_choice
@ -44,7 +43,7 @@ class CustomFieldFilterForm(SavedFiltersMixin, FilterForm):
)), )),
) )
content_type_id = ContentTypeMultipleChoiceField( content_type_id = ContentTypeMultipleChoiceField(
queryset=ContentType.objects.filter(FeatureQuery('custom_fields').get_query()), queryset=ContentType.objects.with_feature('custom_fields'),
required=False, required=False,
label=_('Object type') label=_('Object type')
) )
@ -108,7 +107,7 @@ class CustomLinkFilterForm(SavedFiltersMixin, FilterForm):
) )
content_types = ContentTypeMultipleChoiceField( content_types = ContentTypeMultipleChoiceField(
label=_('Content types'), label=_('Content types'),
queryset=ContentType.objects.filter(FeatureQuery('custom_links').get_query()), queryset=ContentType.objects.with_feature('custom_links'),
required=False required=False
) )
enabled = forms.NullBooleanField( enabled = forms.NullBooleanField(
@ -151,7 +150,7 @@ class ExportTemplateFilterForm(SavedFiltersMixin, FilterForm):
} }
) )
content_type_id = ContentTypeMultipleChoiceField( content_type_id = ContentTypeMultipleChoiceField(
queryset=ContentType.objects.filter(FeatureQuery('export_templates').get_query()), queryset=ContentType.objects.with_feature('export_templates'),
required=False, required=False,
label=_('Content types') label=_('Content types')
) )
@ -179,7 +178,7 @@ class ImageAttachmentFilterForm(SavedFiltersMixin, FilterForm):
) )
content_type_id = ContentTypeChoiceField( content_type_id = ContentTypeChoiceField(
label=_('Content type'), label=_('Content type'),
queryset=ContentType.objects.filter(FeatureQuery('image_attachments').get_query()), queryset=ContentType.objects.with_feature('image_attachments'),
required=False required=False
) )
name = forms.CharField( name = forms.CharField(
@ -228,7 +227,7 @@ class WebhookFilterForm(NetBoxModelFilterSetForm):
(_('Events'), ('type_create', 'type_update', 'type_delete', 'type_job_start', 'type_job_end')), (_('Events'), ('type_create', 'type_update', 'type_delete', 'type_job_start', 'type_job_end')),
) )
content_type_id = ContentTypeMultipleChoiceField( content_type_id = ContentTypeMultipleChoiceField(
queryset=ContentType.objects.filter(FeatureQuery('webhooks').get_query()), queryset=ContentType.objects.with_feature('webhooks'),
required=False, required=False,
label=_('Object type') label=_('Object type')
) )
@ -284,12 +283,12 @@ class WebhookFilterForm(NetBoxModelFilterSetForm):
class TagFilterForm(SavedFiltersMixin, FilterForm): class TagFilterForm(SavedFiltersMixin, FilterForm):
model = Tag model = Tag
content_type_id = ContentTypeMultipleChoiceField( content_type_id = ContentTypeMultipleChoiceField(
queryset=ContentType.objects.filter(FeatureQuery('tags').get_query()), queryset=ContentType.objects.with_feature('tags'),
required=False, required=False,
label=_('Tagged object type') label=_('Tagged object type')
) )
for_object_type_id = ContentTypeChoiceField( for_object_type_id = ContentTypeChoiceField(
queryset=ContentType.objects.filter(FeatureQuery('tags').get_query()), queryset=ContentType.objects.with_feature('tags'),
required=False, required=False,
label=_('Allowed object type') label=_('Allowed object type')
) )

View File

@ -10,7 +10,6 @@ from core.models import ContentType
from dcim.models import DeviceRole, DeviceType, Location, Platform, Region, Site, SiteGroup from dcim.models import DeviceRole, DeviceType, Location, Platform, Region, Site, SiteGroup
from extras.choices import * from extras.choices import *
from extras.models import * from extras.models import *
from extras.utils import FeatureQuery
from netbox.config import get_config, PARAMS from netbox.config import get_config, PARAMS
from netbox.forms import NetBoxModelForm from netbox.forms import NetBoxModelForm
from tenancy.models import Tenant, TenantGroup from tenancy.models import Tenant, TenantGroup
@ -43,8 +42,7 @@ __all__ = (
class CustomFieldForm(BootstrapMixin, forms.ModelForm): class CustomFieldForm(BootstrapMixin, forms.ModelForm):
content_types = ContentTypeMultipleChoiceField( content_types = ContentTypeMultipleChoiceField(
label=_('Content types'), label=_('Content types'),
queryset=ContentType.objects.all(), queryset=ContentType.objects.with_feature('custom_fields')
limit_choices_to=FeatureQuery('custom_fields'),
) )
object_type = ContentTypeChoiceField( object_type = ContentTypeChoiceField(
label=_('Object type'), label=_('Object type'),
@ -114,8 +112,7 @@ class CustomFieldChoiceSetForm(BootstrapMixin, forms.ModelForm):
class CustomLinkForm(BootstrapMixin, forms.ModelForm): class CustomLinkForm(BootstrapMixin, forms.ModelForm):
content_types = ContentTypeMultipleChoiceField( content_types = ContentTypeMultipleChoiceField(
label=_('Content types'), label=_('Content types'),
queryset=ContentType.objects.all(), queryset=ContentType.objects.with_feature('custom_links')
limit_choices_to=FeatureQuery('custom_links')
) )
fieldsets = ( fieldsets = (
@ -142,8 +139,7 @@ class CustomLinkForm(BootstrapMixin, forms.ModelForm):
class ExportTemplateForm(BootstrapMixin, SyncedDataMixin, forms.ModelForm): class ExportTemplateForm(BootstrapMixin, SyncedDataMixin, forms.ModelForm):
content_types = ContentTypeMultipleChoiceField( content_types = ContentTypeMultipleChoiceField(
label=_('Content types'), label=_('Content types'),
queryset=ContentType.objects.all(), queryset=ContentType.objects.with_feature('export_templates')
limit_choices_to=FeatureQuery('export_templates')
) )
template_code = forms.CharField( template_code = forms.CharField(
label=_('Template code'), label=_('Template code'),
@ -210,8 +206,7 @@ class SavedFilterForm(BootstrapMixin, forms.ModelForm):
class BookmarkForm(BootstrapMixin, forms.ModelForm): class BookmarkForm(BootstrapMixin, forms.ModelForm):
object_type = ContentTypeChoiceField( object_type = ContentTypeChoiceField(
label=_('Object type'), label=_('Object type'),
queryset=ContentType.objects.all(), queryset=ContentType.objects.with_feature('bookmarks')
limit_choices_to=FeatureQuery('bookmarks').get_query()
) )
class Meta: class Meta:
@ -222,8 +217,7 @@ class BookmarkForm(BootstrapMixin, forms.ModelForm):
class WebhookForm(NetBoxModelForm): class WebhookForm(NetBoxModelForm):
content_types = ContentTypeMultipleChoiceField( content_types = ContentTypeMultipleChoiceField(
label=_('Content types'), label=_('Content types'),
queryset=ContentType.objects.all(), queryset=ContentType.objects.with_feature('webhooks')
limit_choices_to=FeatureQuery('webhooks')
) )
fieldsets = ( fieldsets = (
@ -257,8 +251,7 @@ class TagForm(BootstrapMixin, forms.ModelForm):
slug = SlugField() slug = SlugField()
object_types = ContentTypeMultipleChoiceField( object_types = ContentTypeMultipleChoiceField(
label=_('Object types'), label=_('Object types'),
queryset=ContentType.objects.all(), queryset=ContentType.objects.with_feature('tags'),
limit_choices_to=FeatureQuery('tags'),
required=False required=False
) )

View File

@ -1,8 +1,7 @@
from django import forms from django import forms
from django.contrib.contenttypes.models import ContentType
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from extras.utils import FeatureQuery from core.models import ContentType
from netbox.forms import NetBoxModelFilterSetForm from netbox.forms import NetBoxModelFilterSetForm
from tenancy.choices import * from tenancy.choices import *
from tenancy.models import * from tenancy.models import *
@ -87,8 +86,7 @@ class ContactAssignmentFilterForm(NetBoxModelFilterSetForm):
(_('Assignment'), ('content_type_id', 'group_id', 'contact_id', 'role_id', 'priority')), (_('Assignment'), ('content_type_id', 'group_id', 'contact_id', 'role_id', 'priority')),
) )
content_type_id = ContentTypeMultipleChoiceField( content_type_id = ContentTypeMultipleChoiceField(
queryset=ContentType.objects.all(), queryset=ContentType.objects.with_feature('contacts'),
limit_choices_to=FeatureQuery('contacts'),
required=False, required=False,
label=_('Object type') label=_('Object type')
) )