diff --git a/netbox/extras/admin.py b/netbox/extras/admin.py index 6e421e679..90e7541e2 100644 --- a/netbox/extras/admin.py +++ b/netbox/extras/admin.py @@ -1,16 +1,10 @@ from django import forms from django.contrib import admin +from django.contrib.contenttypes.models import ContentType -from utilities.forms import LaxURLField +from utilities.forms import ContentTypeChoiceField, ContentTypeMultipleChoiceField, LaxURLField from .models import CustomField, CustomLink, ExportTemplate, JobResult, Webhook - - -def order_content_types(field): - """ - Order the list of available ContentTypes by application - """ - queryset = field.queryset.order_by('app_label', 'model') - field.choices = [(ct.pk, '{} > {}'.format(ct.app_label, ct.name)) for ct in queryset] +from .utils import FeatureQuery # @@ -18,6 +12,10 @@ def order_content_types(field): # class WebhookForm(forms.ModelForm): + content_types = ContentTypeMultipleChoiceField( + queryset=ContentType.objects.all(), + limit_choices_to=FeatureQuery('webhooks') + ) payload_url = LaxURLField( label='URL' ) @@ -26,12 +24,6 @@ class WebhookForm(forms.ModelForm): model = Webhook exclude = () - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - if 'content_types' in self.fields: - order_content_types(self.fields['content_types']) - @admin.register(Webhook) class WebhookAdmin(admin.ModelAdmin): @@ -70,6 +62,10 @@ class WebhookAdmin(admin.ModelAdmin): # class CustomFieldForm(forms.ModelForm): + content_types = ContentTypeMultipleChoiceField( + queryset=ContentType.objects.all(), + limit_choices_to=FeatureQuery('custom_fields') + ) class Meta: model = CustomField @@ -84,11 +80,6 @@ class CustomFieldForm(forms.ModelForm): ) } - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - order_content_types(self.fields['content_types']) - @admin.register(CustomField) class CustomFieldAdmin(admin.ModelAdmin): @@ -127,6 +118,10 @@ class CustomFieldAdmin(admin.ModelAdmin): # class CustomLinkForm(forms.ModelForm): + content_type = ContentTypeChoiceField( + queryset=ContentType.objects.all(), + limit_choices_to=FeatureQuery('custom_links') + ) class Meta: model = CustomLink @@ -143,13 +138,6 @@ class CustomLinkForm(forms.ModelForm): 'link_url': 'Jinja2 template code for the link URL. Reference the object as {{ obj }}.', } - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - # Format ContentType choices - order_content_types(self.fields['content_type']) - self.fields['content_type'].choices.insert(0, ('', '---------')) - @admin.register(CustomLink) class CustomLinkAdmin(admin.ModelAdmin): @@ -176,18 +164,15 @@ class CustomLinkAdmin(admin.ModelAdmin): # class ExportTemplateForm(forms.ModelForm): + content_type = ContentTypeChoiceField( + queryset=ContentType.objects.all(), + limit_choices_to=FeatureQuery('custom_links') + ) class Meta: model = ExportTemplate exclude = [] - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - # Format ContentType choices - order_content_types(self.fields['content_type']) - self.fields['content_type'].choices.insert(0, ('', '---------')) - @admin.register(ExportTemplate) class ExportTemplateAdmin(admin.ModelAdmin): diff --git a/netbox/ipam/tests/test_views.py b/netbox/ipam/tests/test_views.py index 387bdd2b5..2704ac1ca 100644 --- a/netbox/ipam/tests/test_views.py +++ b/netbox/ipam/tests/test_views.py @@ -329,7 +329,6 @@ class VLANGroupTestCase(ViewTestCases.OrganizationalObjectViewTestCase): cls.form_data = { 'name': 'VLAN Group X', 'slug': 'vlan-group-x', - 'site': sites[1].pk, 'description': 'A new VLAN group', } diff --git a/netbox/users/admin.py b/netbox/users/admin.py index e1625e1ed..5eff6ec22 100644 --- a/netbox/users/admin.py +++ b/netbox/users/admin.py @@ -4,9 +4,9 @@ from django.contrib.auth.admin import UserAdmin as UserAdmin_ from django.contrib.auth.models import Group, User from django.contrib.contenttypes.models import ContentType from django.core.exceptions import FieldError, ValidationError -from django.db.models import Q -from extras.admin import order_content_types +from utilities.forms.fields import ContentTypeMultipleChoiceField +from .constants import * from .models import AdminGroup, AdminUser, ObjectPermission, Token, UserConfig @@ -126,6 +126,10 @@ class TokenAdmin(admin.ModelAdmin): # class ObjectPermissionForm(forms.ModelForm): + object_types = ContentTypeMultipleChoiceField( + queryset=ContentType.objects.all(), + limit_choices_to=OBJECTPERMISSION_OBJECT_TYPES + ) can_view = forms.BooleanField(required=False) can_add = forms.BooleanField(required=False) can_change = forms.BooleanField(required=False) @@ -153,10 +157,6 @@ class ObjectPermissionForm(forms.ModelForm): # Make the actions field optional since the admin form uses it only for non-CRUD actions self.fields['actions'].required = False - # Format ContentType choices - order_content_types(self.fields['object_types']) - self.fields['object_types'].choices.insert(0, ('', '---------')) - # Order group and user fields self.fields['groups'].queryset = self.fields['groups'].queryset.order_by('name') self.fields['users'].queryset = self.fields['users'].queryset.order_by('username') diff --git a/netbox/users/constants.py b/netbox/users/constants.py new file mode 100644 index 000000000..e6917c482 --- /dev/null +++ b/netbox/users/constants.py @@ -0,0 +1,8 @@ +from django.db.models import Q + + +OBJECTPERMISSION_OBJECT_TYPES = Q( + ~Q(app_label__in=['admin', 'auth', 'contenttypes', 'sessions', 'taggit', 'users']) | + Q(app_label='auth', model__in=['group', 'user']) | + Q(app_label='users', model__in=['objectpermission', 'token']) +) diff --git a/netbox/users/models.py b/netbox/users/models.py index 00e18148c..2252d15b6 100644 --- a/netbox/users/models.py +++ b/netbox/users/models.py @@ -6,7 +6,6 @@ from django.contrib.contenttypes.models import ContentType from django.contrib.postgres.fields import ArrayField from django.core.validators import MinLengthValidator from django.db import models -from django.db.models import Q from django.db.models.signals import post_save from django.dispatch import receiver from django.utils import timezone @@ -14,6 +13,7 @@ from django.utils import timezone from netbox.models import BigIDModel from utilities.querysets import RestrictedQuerySet from utilities.utils import flatten_dict +from .constants import * __all__ = ( @@ -251,11 +251,7 @@ class ObjectPermission(BigIDModel): ) object_types = models.ManyToManyField( to=ContentType, - limit_choices_to=Q( - ~Q(app_label__in=['admin', 'auth', 'contenttypes', 'sessions', 'taggit', 'users']) | - Q(app_label='auth', model__in=['group', 'user']) | - Q(app_label='users', model__in=['objectpermission', 'token']) - ), + limit_choices_to=OBJECTPERMISSION_OBJECT_TYPES, related_name='object_permissions' ) groups = models.ManyToManyField( diff --git a/netbox/utilities/forms/fields.py b/netbox/utilities/forms/fields.py index 001f50bfa..f0be33195 100644 --- a/netbox/utilities/forms/fields.py +++ b/netbox/utilities/forms/fields.py @@ -21,6 +21,7 @@ from .utils import expand_alphanumeric_pattern, expand_ipaddress_pattern __all__ = ( 'CommentField', 'ContentTypeChoiceField', + 'ContentTypeMultipleChoiceField', 'CSVChoiceField', 'CSVContentTypeField', 'CSVDataField', @@ -114,7 +115,7 @@ class JSONField(_JSONField): return json.dumps(value, sort_keys=True, indent=4) -class ContentTypeChoiceField(forms.ModelChoiceField): +class ContentTypeChoiceMixin: def __init__(self, queryset, *args, **kwargs): # Order ContentTypes by app_label @@ -126,6 +127,14 @@ class ContentTypeChoiceField(forms.ModelChoiceField): return f'{meta.app_config.verbose_name} > {meta.verbose_name}' +class ContentTypeChoiceField(ContentTypeChoiceMixin, forms.ModelChoiceField): + pass + + +class ContentTypeMultipleChoiceField(ContentTypeChoiceMixin, forms.ModelMultipleChoiceField): + pass + + # # CSV fields #