diff --git a/netbox/circuits/forms/model_forms.py b/netbox/circuits/forms/model_forms.py index 1b6c3feb4..4e8f54773 100644 --- a/netbox/circuits/forms/model_forms.py +++ b/netbox/circuits/forms/model_forms.py @@ -10,11 +10,11 @@ from circuits.constants import * from circuits.models import * from dcim.models import Interface, Site from ipam.models import ASN -from netbox.forms import NetBoxModelForm +from netbox.forms import NetBoxModelForm, OrganizationalModelForm, PrimaryModelForm from tenancy.forms import TenancyForm from utilities.forms import get_field_value from utilities.forms.fields import ( - CommentField, ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, SlugField, + ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, SlugField, ) from utilities.forms.mixins import DistanceValidationMixin from utilities.forms.rendering import FieldSet, InlineFields @@ -36,14 +36,13 @@ __all__ = ( ) -class ProviderForm(NetBoxModelForm): +class ProviderForm(PrimaryModelForm): slug = SlugField() asns = DynamicModelMultipleChoiceField( queryset=ASN.objects.all(), label=_('ASNs'), required=False ) - comments = CommentField() fieldsets = ( FieldSet('name', 'slug', 'asns', 'description', 'tags'), @@ -56,14 +55,13 @@ class ProviderForm(NetBoxModelForm): ] -class ProviderAccountForm(NetBoxModelForm): +class ProviderAccountForm(PrimaryModelForm): provider = DynamicModelChoiceField( label=_('Provider'), queryset=Provider.objects.all(), selector=True, quick_add=True ) - comments = CommentField() class Meta: model = ProviderAccount @@ -72,14 +70,13 @@ class ProviderAccountForm(NetBoxModelForm): ] -class ProviderNetworkForm(NetBoxModelForm): +class ProviderNetworkForm(PrimaryModelForm): provider = DynamicModelChoiceField( label=_('Provider'), queryset=Provider.objects.all(), selector=True, quick_add=True ) - comments = CommentField() fieldsets = ( FieldSet('provider', 'name', 'service_id', 'description', 'tags'), @@ -92,9 +89,7 @@ class ProviderNetworkForm(NetBoxModelForm): ] -class CircuitTypeForm(NetBoxModelForm): - slug = SlugField() - +class CircuitTypeForm(OrganizationalModelForm): fieldsets = ( FieldSet('name', 'slug', 'color', 'description', 'owner', 'tags'), ) @@ -106,7 +101,7 @@ class CircuitTypeForm(NetBoxModelForm): ] -class CircuitForm(DistanceValidationMixin, TenancyForm, NetBoxModelForm): +class CircuitForm(DistanceValidationMixin, TenancyForm, PrimaryModelForm): provider = DynamicModelChoiceField( label=_('Provider'), queryset=Provider.objects.all(), @@ -125,7 +120,6 @@ class CircuitForm(DistanceValidationMixin, TenancyForm, NetBoxModelForm): queryset=CircuitType.objects.all(), quick_add=True ) - comments = CommentField() fieldsets = ( FieldSet( @@ -233,9 +227,7 @@ class CircuitTerminationForm(NetBoxModelForm): self.instance.termination = self.cleaned_data.get('termination') -class CircuitGroupForm(TenancyForm, NetBoxModelForm): - slug = SlugField() - +class CircuitGroupForm(TenancyForm, OrganizationalModelForm): fieldsets = ( FieldSet('name', 'slug', 'description', 'tags', name=_('Circuit Group')), FieldSet('tenant_group', 'tenant', name=_('Tenancy')), @@ -307,9 +299,7 @@ class CircuitGroupAssignmentForm(NetBoxModelForm): self.instance.member = self.cleaned_data.get('member') -class VirtualCircuitTypeForm(NetBoxModelForm): - slug = SlugField() - +class VirtualCircuitTypeForm(OrganizationalModelForm): fieldsets = ( FieldSet('name', 'slug', 'color', 'description', 'tags'), ) @@ -321,7 +311,7 @@ class VirtualCircuitTypeForm(NetBoxModelForm): ] -class VirtualCircuitForm(TenancyForm, NetBoxModelForm): +class VirtualCircuitForm(TenancyForm, PrimaryModelForm): provider_network = DynamicModelChoiceField( label=_('Provider network'), queryset=ProviderNetwork.objects.all(), @@ -336,7 +326,6 @@ class VirtualCircuitForm(TenancyForm, NetBoxModelForm): queryset=VirtualCircuitType.objects.all(), quick_add=True ) - comments = CommentField() fieldsets = ( FieldSet( diff --git a/netbox/core/forms/model_forms.py b/netbox/core/forms/model_forms.py index 6796b4f85..7437c25d6 100644 --- a/netbox/core/forms/model_forms.py +++ b/netbox/core/forms/model_forms.py @@ -9,11 +9,11 @@ from django.utils.translation import gettext_lazy as _ from core.forms.mixins import SyncedDataMixin from core.models import * from netbox.config import get_config, PARAMS -from netbox.forms import NetBoxModelForm +from netbox.forms import NetBoxModelForm, PrimaryModelForm from netbox.registry import registry from netbox.utils import get_data_backend_choices from utilities.forms import get_field_value -from utilities.forms.fields import CommentField, JSONField +from utilities.forms.fields import JSONField from utilities.forms.rendering import FieldSet from utilities.forms.widgets import HTMXSelect @@ -26,12 +26,11 @@ __all__ = ( EMPTY_VALUES = ('', None, [], ()) -class DataSourceForm(NetBoxModelForm): +class DataSourceForm(PrimaryModelForm): type = forms.ChoiceField( choices=get_data_backend_choices, widget=HTMXSelect() ) - comments = CommentField() class Meta: model = DataSource diff --git a/netbox/dcim/forms/model_forms.py b/netbox/dcim/forms/model_forms.py index 4e3037cfb..a7af27e1d 100644 --- a/netbox/dcim/forms/model_forms.py +++ b/netbox/dcim/forms/model_forms.py @@ -10,13 +10,13 @@ from dcim.models import * from extras.models import ConfigTemplate from ipam.choices import VLANQinQRoleChoices from ipam.models import ASN, IPAddress, VLAN, VLANGroup, VLANTranslationPolicy, VRF -from netbox.forms import NetBoxModelForm +from netbox.forms import NestedGroupModelForm, NetBoxModelForm, OrganizationalModelForm, PrimaryModelForm from netbox.forms.mixins import ChangelogMessageMixin from tenancy.forms import TenancyForm from users.models import User from utilities.forms import add_blank_choice, get_field_value from utilities.forms.fields import ( - CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, JSONField, NumericArrayField, SlugField, + DynamicModelChoiceField, DynamicModelMultipleChoiceField, JSONField, NumericArrayField, SlugField, ) from utilities.forms.rendering import FieldSet, InlineFields, TabbedGroups from utilities.forms.widgets import APISelect, ClearableFileInput, HTMXSelect, NumberWithOptions, SelectWithPK @@ -75,14 +75,12 @@ __all__ = ( ) -class RegionForm(NetBoxModelForm): +class RegionForm(NestedGroupModelForm): parent = DynamicModelChoiceField( label=_('Parent'), queryset=Region.objects.all(), required=False ) - slug = SlugField() - comments = CommentField() fieldsets = ( FieldSet('parent', 'name', 'slug', 'description', 'tags'), @@ -95,14 +93,12 @@ class RegionForm(NetBoxModelForm): ) -class SiteGroupForm(NetBoxModelForm): +class SiteGroupForm(NestedGroupModelForm): parent = DynamicModelChoiceField( label=_('Parent'), queryset=SiteGroup.objects.all(), required=False ) - slug = SlugField() - comments = CommentField() fieldsets = ( FieldSet('parent', 'name', 'slug', 'description', 'tags'), @@ -115,7 +111,7 @@ class SiteGroupForm(NetBoxModelForm): ) -class SiteForm(TenancyForm, NetBoxModelForm): +class SiteForm(TenancyForm, PrimaryModelForm): region = DynamicModelChoiceField( label=_('Region'), queryset=Region.objects.all(), @@ -139,7 +135,6 @@ class SiteForm(TenancyForm, NetBoxModelForm): choices=add_blank_choice(TimeZoneFormField().choices), required=False ) - comments = CommentField() fieldsets = ( FieldSet( @@ -170,7 +165,7 @@ class SiteForm(TenancyForm, NetBoxModelForm): } -class LocationForm(TenancyForm, NetBoxModelForm): +class LocationForm(TenancyForm, NestedGroupModelForm): site = DynamicModelChoiceField( label=_('Site'), queryset=Site.objects.all(), @@ -184,8 +179,6 @@ class LocationForm(TenancyForm, NetBoxModelForm): 'site_id': '$site' } ) - slug = SlugField() - comments = CommentField() fieldsets = ( FieldSet('site', 'parent', 'name', 'slug', 'status', 'facility', 'description', 'tags', name=_('Location')), @@ -200,9 +193,7 @@ class LocationForm(TenancyForm, NetBoxModelForm): ) -class RackRoleForm(NetBoxModelForm): - slug = SlugField() - +class RackRoleForm(OrganizationalModelForm): fieldsets = ( FieldSet('name', 'slug', 'color', 'description', 'tags', name=_('Rack Role')), ) @@ -214,13 +205,12 @@ class RackRoleForm(NetBoxModelForm): ] -class RackTypeForm(NetBoxModelForm): +class RackTypeForm(PrimaryModelForm): manufacturer = DynamicModelChoiceField( label=_('Manufacturer'), queryset=Manufacturer.objects.all(), quick_add=True ) - comments = CommentField() slug = SlugField( label=_('Slug'), slug_source='model' @@ -246,7 +236,7 @@ class RackTypeForm(NetBoxModelForm): ] -class RackForm(TenancyForm, NetBoxModelForm): +class RackForm(TenancyForm, PrimaryModelForm): site = DynamicModelChoiceField( label=_('Site'), queryset=Site.objects.all(), @@ -271,7 +261,6 @@ class RackForm(TenancyForm, NetBoxModelForm): required=False, help_text=_("Select a pre-defined rack type, or set physical characteristics below.") ) - comments = CommentField() fieldsets = ( FieldSet( @@ -333,7 +322,6 @@ class RackReservationForm(TenancyForm, NetBoxModelForm): label=_('User'), queryset=User.objects.order_by('username') ) - comments = CommentField() fieldsets = ( FieldSet('rack', 'units', 'status', 'user', 'description', 'tags', name=_('Reservation')), @@ -347,9 +335,7 @@ class RackReservationForm(TenancyForm, NetBoxModelForm): ] -class ManufacturerForm(NetBoxModelForm): - slug = SlugField() - +class ManufacturerForm(OrganizationalModelForm): fieldsets = ( FieldSet('name', 'slug', 'description', 'tags', name=_('Manufacturer')), ) @@ -361,7 +347,7 @@ class ManufacturerForm(NetBoxModelForm): ] -class DeviceTypeForm(NetBoxModelForm): +class DeviceTypeForm(PrimaryModelForm): manufacturer = DynamicModelChoiceField( label=_('Manufacturer'), queryset=Manufacturer.objects.all(), @@ -380,7 +366,6 @@ class DeviceTypeForm(NetBoxModelForm): label=_('Slug'), slug_source='model' ) - comments = CommentField() fieldsets = ( FieldSet('manufacturer', 'model', 'slug', 'default_platform', 'description', 'tags', name=_('Device Type')), @@ -408,13 +393,12 @@ class DeviceTypeForm(NetBoxModelForm): } -class ModuleTypeProfileForm(NetBoxModelForm): +class ModuleTypeProfileForm(PrimaryModelForm): schema = JSONField( label=_('Schema'), required=False, help_text=_("Enter a valid JSON schema to define supported attributes.") ) - comments = CommentField() fieldsets = ( FieldSet('name', 'description', 'schema', 'tags', name=_('Profile')), @@ -427,7 +411,7 @@ class ModuleTypeProfileForm(NetBoxModelForm): ] -class ModuleTypeForm(NetBoxModelForm): +class ModuleTypeForm(PrimaryModelForm): profile = forms.ModelChoiceField( queryset=ModuleTypeProfile.objects.all(), label=_('Profile'), @@ -438,7 +422,6 @@ class ModuleTypeForm(NetBoxModelForm): label=_('Manufacturer'), queryset=Manufacturer.objects.all() ) - comments = CommentField() @property def fieldsets(self): @@ -507,19 +490,17 @@ class ModuleTypeForm(NetBoxModelForm): return super()._post_clean() -class DeviceRoleForm(NetBoxModelForm): +class DeviceRoleForm(NestedGroupModelForm): config_template = DynamicModelChoiceField( label=_('Config template'), queryset=ConfigTemplate.objects.all(), required=False ) - slug = SlugField() parent = DynamicModelChoiceField( label=_('Parent'), queryset=DeviceRole.objects.all(), required=False, ) - comments = CommentField() fieldsets = ( FieldSet( @@ -535,7 +516,7 @@ class DeviceRoleForm(NetBoxModelForm): ] -class PlatformForm(NetBoxModelForm): +class PlatformForm(NestedGroupModelForm): parent = DynamicModelChoiceField( label=_('Parent'), queryset=Platform.objects.all(), @@ -556,7 +537,6 @@ class PlatformForm(NetBoxModelForm): label=_('Slug'), max_length=64 ) - comments = CommentField() fieldsets = ( FieldSet( @@ -571,7 +551,7 @@ class PlatformForm(NetBoxModelForm): ] -class DeviceForm(TenancyForm, NetBoxModelForm): +class DeviceForm(TenancyForm, PrimaryModelForm): site = DynamicModelChoiceField( label=_('Site'), queryset=Site.objects.all(), @@ -641,7 +621,6 @@ class DeviceForm(TenancyForm, NetBoxModelForm): 'site_id': ['$site', 'null'] }, ) - comments = CommentField() local_context_data = JSONField( required=False, label='' @@ -742,7 +721,7 @@ class DeviceForm(TenancyForm, NetBoxModelForm): self.fields['position'].widget.choices = [(position, f'U{position}')] -class ModuleForm(ModuleCommonForm, NetBoxModelForm): +class ModuleForm(ModuleCommonForm, PrimaryModelForm): device = DynamicModelChoiceField( label=_('Device'), queryset=Device.objects.all(), @@ -765,7 +744,6 @@ class ModuleForm(ModuleCommonForm, NetBoxModelForm): }, selector=True ) - comments = CommentField() replicate_components = forms.BooleanField( label=_('Replicate components'), required=False, @@ -809,7 +787,7 @@ def get_termination_type_choices(): ]) -class CableForm(TenancyForm, NetBoxModelForm): +class CableForm(TenancyForm, PrimaryModelForm): a_terminations_type = forms.ChoiceField( choices=get_termination_type_choices, required=False, @@ -822,7 +800,6 @@ class CableForm(TenancyForm, NetBoxModelForm): widget=HTMXSelect(), label=_('Type') ) - comments = CommentField() class Meta: model = Cable @@ -832,7 +809,7 @@ class CableForm(TenancyForm, NetBoxModelForm): ] -class PowerPanelForm(NetBoxModelForm): +class PowerPanelForm(PrimaryModelForm): site = DynamicModelChoiceField( label=_('Site'), queryset=Site.objects.all(), @@ -846,7 +823,6 @@ class PowerPanelForm(NetBoxModelForm): 'site_id': '$site' } ) - comments = CommentField() fieldsets = ( FieldSet('site', 'location', 'name', 'description', 'tags', name=_('Power Panel')), @@ -859,7 +835,7 @@ class PowerPanelForm(NetBoxModelForm): ] -class PowerFeedForm(TenancyForm, NetBoxModelForm): +class PowerFeedForm(TenancyForm, PrimaryModelForm): power_panel = DynamicModelChoiceField( label=_('Power panel'), queryset=PowerPanel.objects.all(), @@ -872,7 +848,6 @@ class PowerFeedForm(TenancyForm, NetBoxModelForm): required=False, selector=True ) - comments = CommentField() fieldsets = ( FieldSet( @@ -895,13 +870,12 @@ class PowerFeedForm(TenancyForm, NetBoxModelForm): # Virtual chassis # -class VirtualChassisForm(NetBoxModelForm): +class VirtualChassisForm(PrimaryModelForm): master = forms.ModelChoiceField( label=_('Master'), queryset=Device.objects.all(), required=False, ) - comments = CommentField() class Meta: model = VirtualChassis @@ -1829,9 +1803,7 @@ class InventoryItemForm(DeviceComponentForm): self.instance.component = None -class InventoryItemRoleForm(NetBoxModelForm): - slug = SlugField() - +class InventoryItemRoleForm(OrganizationalModelForm): fieldsets = ( FieldSet('name', 'slug', 'color', 'description', 'tags', name=_('Inventory Item Role')), ) @@ -1843,7 +1815,7 @@ class InventoryItemRoleForm(NetBoxModelForm): ] -class VirtualDeviceContextForm(TenancyForm, NetBoxModelForm): +class VirtualDeviceContextForm(TenancyForm, PrimaryModelForm): device = DynamicModelChoiceField( label=_('Device'), queryset=Device.objects.all(), @@ -1888,7 +1860,7 @@ class VirtualDeviceContextForm(TenancyForm, NetBoxModelForm): # Addressing # -class MACAddressForm(NetBoxModelForm): +class MACAddressForm(PrimaryModelForm): mac_address = forms.CharField( required=True, label=_('MAC address') diff --git a/netbox/extras/forms/model_forms.py b/netbox/extras/forms/model_forms.py index 39f02f5b1..0bce793f8 100644 --- a/netbox/extras/forms/model_forms.py +++ b/netbox/extras/forms/model_forms.py @@ -12,8 +12,8 @@ from dcim.models import DeviceRole, DeviceType, Location, Platform, Region, Site from extras.choices import * from extras.models import * from netbox.events import get_event_type_choices -from netbox.forms import NetBoxModelForm -from netbox.forms.mixins import ChangelogMessageMixin +from netbox.forms import NetBoxModelForm, PrimaryModelForm +from netbox.forms.mixins import ChangelogMessageMixin, OwnerMixin from tenancy.models import Tenant, TenantGroup from users.models import Group, User from utilities.forms import get_field_value @@ -47,7 +47,7 @@ __all__ = ( ) -class CustomFieldForm(ChangelogMessageMixin, forms.ModelForm): +class CustomFieldForm(ChangelogMessageMixin, OwnerMixin, forms.ModelForm): object_types = ContentTypeMultipleChoiceField( label=_('Object types'), queryset=ObjectType.objects.with_feature('custom_fields'), @@ -166,7 +166,7 @@ class CustomFieldForm(ChangelogMessageMixin, forms.ModelForm): del self.fields['choice_set'] -class CustomFieldChoiceSetForm(ChangelogMessageMixin, forms.ModelForm): +class CustomFieldChoiceSetForm(ChangelogMessageMixin, OwnerMixin, forms.ModelForm): # TODO: The extra_choices field definition diverge from the CustomFieldChoiceSet model extra_choices = forms.CharField( widget=ChoicesWidget(), @@ -219,7 +219,7 @@ class CustomFieldChoiceSetForm(ChangelogMessageMixin, forms.ModelForm): return data -class CustomLinkForm(ChangelogMessageMixin, forms.ModelForm): +class CustomLinkForm(ChangelogMessageMixin, OwnerMixin, forms.ModelForm): object_types = ContentTypeMultipleChoiceField( label=_('Object types'), queryset=ObjectType.objects.with_feature('custom_links') @@ -251,7 +251,7 @@ class CustomLinkForm(ChangelogMessageMixin, forms.ModelForm): } -class ExportTemplateForm(ChangelogMessageMixin, SyncedDataMixin, forms.ModelForm): +class ExportTemplateForm(ChangelogMessageMixin, SyncedDataMixin, OwnerMixin, forms.ModelForm): object_types = ContentTypeMultipleChoiceField( label=_('Object types'), queryset=ObjectType.objects.with_feature('export_templates') @@ -293,7 +293,7 @@ class ExportTemplateForm(ChangelogMessageMixin, SyncedDataMixin, forms.ModelForm return self.cleaned_data -class SavedFilterForm(ChangelogMessageMixin, forms.ModelForm): +class SavedFilterForm(ChangelogMessageMixin, OwnerMixin, forms.ModelForm): slug = SlugField() object_types = ContentTypeMultipleChoiceField( label=_('Object types'), @@ -427,7 +427,7 @@ class SubscriptionForm(forms.ModelForm): fields = ('object_type', 'object_id') -class WebhookForm(NetBoxModelForm): +class WebhookForm(OwnerMixin, NetBoxModelForm): fieldsets = ( FieldSet('name', 'description', 'tags', name=_('Webhook')), @@ -447,7 +447,7 @@ class WebhookForm(NetBoxModelForm): } -class EventRuleForm(NetBoxModelForm): +class EventRuleForm(OwnerMixin, NetBoxModelForm): object_types = ContentTypeMultipleChoiceField( label=_('Object types'), queryset=ObjectType.objects.with_feature('event_rules'), @@ -563,7 +563,7 @@ class EventRuleForm(NetBoxModelForm): return self.cleaned_data -class TagForm(ChangelogMessageMixin, forms.ModelForm): +class TagForm(ChangelogMessageMixin, OwnerMixin, forms.ModelForm): slug = SlugField() object_types = ContentTypeMultipleChoiceField( label=_('Object types'), @@ -586,7 +586,7 @@ class TagForm(ChangelogMessageMixin, forms.ModelForm): ] -class ConfigContextProfileForm(SyncedDataMixin, NetBoxModelForm): +class ConfigContextProfileForm(SyncedDataMixin, PrimaryModelForm): schema = JSONField( label=_('Schema'), required=False, @@ -611,7 +611,7 @@ class ConfigContextProfileForm(SyncedDataMixin, NetBoxModelForm): ) -class ConfigContextForm(ChangelogMessageMixin, SyncedDataMixin, forms.ModelForm): +class ConfigContextForm(ChangelogMessageMixin, SyncedDataMixin, OwnerMixin, forms.ModelForm): profile = DynamicModelChoiceField( label=_('Profile'), queryset=ConfigContextProfile.objects.all(), @@ -728,7 +728,7 @@ class ConfigContextForm(ChangelogMessageMixin, SyncedDataMixin, forms.ModelForm) return self.cleaned_data -class ConfigTemplateForm(ChangelogMessageMixin, SyncedDataMixin, forms.ModelForm): +class ConfigTemplateForm(ChangelogMessageMixin, SyncedDataMixin, OwnerMixin, forms.ModelForm): tags = DynamicModelMultipleChoiceField( label=_('Tags'), queryset=Tag.objects.all(), diff --git a/netbox/ipam/forms/model_forms.py b/netbox/ipam/forms/model_forms.py index 1e2f23da6..f792d1bef 100644 --- a/netbox/ipam/forms/model_forms.py +++ b/netbox/ipam/forms/model_forms.py @@ -9,13 +9,13 @@ from ipam.choices import * from ipam.constants import * from ipam.formfields import IPNetworkFormField from ipam.models import * -from netbox.forms import NetBoxModelForm +from netbox.forms import NetBoxModelForm, OrganizationalModelForm, PrimaryModelForm from tenancy.forms import TenancyForm from utilities.exceptions import PermissionsViolation from utilities.forms import add_blank_choice from utilities.forms.fields import ( - CommentField, ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, NumericArrayField, - NumericRangeArrayField, SlugField + ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, NumericArrayField, + NumericRangeArrayField, ) from utilities.forms.rendering import FieldSet, InlineFields, ObjectAttribute, TabbedGroups from utilities.forms.utils import get_field_value @@ -49,7 +49,7 @@ __all__ = ( ) -class VRFForm(TenancyForm, NetBoxModelForm): +class VRFForm(TenancyForm, PrimaryModelForm): import_targets = DynamicModelMultipleChoiceField( label=_('Import targets'), queryset=RouteTarget.objects.all(), @@ -60,7 +60,6 @@ class VRFForm(TenancyForm, NetBoxModelForm): queryset=RouteTarget.objects.all(), required=False ) - comments = CommentField() fieldsets = ( FieldSet('name', 'rd', 'enforce_unique', 'description', 'tags', name=_('VRF')), @@ -79,12 +78,11 @@ class VRFForm(TenancyForm, NetBoxModelForm): } -class RouteTargetForm(TenancyForm, NetBoxModelForm): +class RouteTargetForm(TenancyForm, PrimaryModelForm): fieldsets = ( FieldSet('name', 'description', 'tags', name=_('Route Target')), FieldSet('tenant_group', 'tenant', name=_('Tenancy')), ) - comments = CommentField() class Meta: model = RouteTarget @@ -93,9 +91,7 @@ class RouteTargetForm(TenancyForm, NetBoxModelForm): ] -class RIRForm(NetBoxModelForm): - slug = SlugField() - +class RIRForm(OrganizationalModelForm): fieldsets = ( FieldSet('name', 'slug', 'is_private', 'description', 'tags', name=_('RIR')), ) @@ -107,13 +103,12 @@ class RIRForm(NetBoxModelForm): ] -class AggregateForm(TenancyForm, NetBoxModelForm): +class AggregateForm(TenancyForm, PrimaryModelForm): rir = DynamicModelChoiceField( queryset=RIR.objects.all(), label=_('RIR'), quick_add=True ) - comments = CommentField() fieldsets = ( FieldSet('prefix', 'rir', 'date_added', 'description', 'tags', name=_('Aggregate')), @@ -130,13 +125,12 @@ class AggregateForm(TenancyForm, NetBoxModelForm): } -class ASNRangeForm(TenancyForm, NetBoxModelForm): +class ASNRangeForm(TenancyForm, OrganizationalModelForm): rir = DynamicModelChoiceField( queryset=RIR.objects.all(), label=_('RIR'), quick_add=True ) - slug = SlugField() fieldsets = ( FieldSet('name', 'slug', 'rir', 'start', 'end', 'description', 'tags', name=_('ASN Range')), FieldSet('tenant_group', 'tenant', name=_('Tenancy')), @@ -149,7 +143,7 @@ class ASNRangeForm(TenancyForm, NetBoxModelForm): ] -class ASNForm(TenancyForm, NetBoxModelForm): +class ASNForm(TenancyForm, PrimaryModelForm): rir = DynamicModelChoiceField( queryset=RIR.objects.all(), label=_('RIR'), @@ -160,7 +154,6 @@ class ASNForm(TenancyForm, NetBoxModelForm): label=_('Sites'), required=False ) - comments = CommentField() fieldsets = ( FieldSet('asn', 'rir', 'sites', 'description', 'tags', name=_('ASN')), @@ -188,9 +181,7 @@ class ASNForm(TenancyForm, NetBoxModelForm): return instance -class RoleForm(NetBoxModelForm): - slug = SlugField() - +class RoleForm(OrganizationalModelForm): fieldsets = ( FieldSet('name', 'slug', 'weight', 'description', 'tags', name=_('Role')), ) @@ -202,7 +193,7 @@ class RoleForm(NetBoxModelForm): ] -class PrefixForm(TenancyForm, ScopedForm, NetBoxModelForm): +class PrefixForm(TenancyForm, ScopedForm, PrimaryModelForm): vrf = DynamicModelChoiceField( queryset=VRF.objects.all(), required=False, @@ -223,7 +214,6 @@ class PrefixForm(TenancyForm, ScopedForm, NetBoxModelForm): required=False, quick_add=True ) - comments = CommentField() fieldsets = ( FieldSet( @@ -250,7 +240,7 @@ class PrefixForm(TenancyForm, ScopedForm, NetBoxModelForm): self.fields['vlan'].widget.attrs.pop('data-dynamic-params', None) -class IPRangeForm(TenancyForm, NetBoxModelForm): +class IPRangeForm(TenancyForm, PrimaryModelForm): vrf = DynamicModelChoiceField( queryset=VRF.objects.all(), required=False, @@ -262,7 +252,6 @@ class IPRangeForm(TenancyForm, NetBoxModelForm): required=False, quick_add=True ) - comments = CommentField() fieldsets = ( FieldSet( @@ -280,7 +269,7 @@ class IPRangeForm(TenancyForm, NetBoxModelForm): ] -class IPAddressForm(TenancyForm, NetBoxModelForm): +class IPAddressForm(TenancyForm, PrimaryModelForm): interface = DynamicModelChoiceField( queryset=Interface.objects.all(), required=False, @@ -324,7 +313,6 @@ class IPAddressForm(TenancyForm, NetBoxModelForm): required=False, label=_('Make this the out-of-band IP for the device') ) - comments = CommentField() fieldsets = ( FieldSet('address', 'status', 'role', 'vrf', 'dns_name', 'description', 'tags', name=_('IP Address')), @@ -494,7 +482,7 @@ class IPAddressAssignForm(forms.Form): ) -class FHRPGroupForm(NetBoxModelForm): +class FHRPGroupForm(PrimaryModelForm): # Optionally create a new IPAddress along with the FHRPGroup ip_vrf = DynamicModelChoiceField( @@ -511,7 +499,6 @@ class FHRPGroupForm(NetBoxModelForm): required=False, label=_('Status') ) - comments = CommentField() fieldsets = ( FieldSet('protocol', 'group_id', 'name', 'description', 'tags', name=_('FHRP Group')), @@ -599,8 +586,7 @@ class FHRPGroupAssignmentForm(forms.ModelForm): return group -class VLANGroupForm(TenancyForm, NetBoxModelForm): - slug = SlugField() +class VLANGroupForm(TenancyForm, OrganizationalModelForm): vid_ranges = NumericRangeArrayField( label=_('VLAN IDs') ) @@ -662,7 +648,7 @@ class VLANGroupForm(TenancyForm, NetBoxModelForm): self.instance.scope = self.cleaned_data.get('scope') -class VLANForm(TenancyForm, NetBoxModelForm): +class VLANForm(TenancyForm, PrimaryModelForm): group = DynamicModelChoiceField( queryset=VLANGroup.objects.all(), required=False, @@ -698,7 +684,6 @@ class VLANForm(TenancyForm, NetBoxModelForm): 'qinq_role': VLANQinQRoleChoices.ROLE_SERVICE, } ) - comments = CommentField() class Meta: model = VLAN @@ -708,7 +693,7 @@ class VLANForm(TenancyForm, NetBoxModelForm): ] -class VLANTranslationPolicyForm(NetBoxModelForm): +class VLANTranslationPolicyForm(PrimaryModelForm): fieldsets = ( FieldSet('name', 'description', 'tags', name=_('VLAN Translation Policy')), @@ -739,7 +724,7 @@ class VLANTranslationRuleForm(NetBoxModelForm): ] -class ServiceTemplateForm(NetBoxModelForm): +class ServiceTemplateForm(PrimaryModelForm): ports = NumericArrayField( label=_('Ports'), base_field=forms.IntegerField( @@ -748,7 +733,6 @@ class ServiceTemplateForm(NetBoxModelForm): ), help_text=_("Comma-separated list of one or more port numbers. A range may be specified using a hyphen.") ) - comments = CommentField() fieldsets = ( FieldSet('name', 'protocol', 'ports', 'description', 'tags', name=_('Application Service Template')), @@ -759,7 +743,7 @@ class ServiceTemplateForm(NetBoxModelForm): fields = ('name', 'protocol', 'ports', 'description', 'owner', 'comments', 'tags') -class ServiceForm(NetBoxModelForm): +class ServiceForm(PrimaryModelForm): parent_object_type = ContentTypeChoiceField( queryset=ContentType.objects.filter(SERVICE_ASSIGNMENT_MODELS), widget=HTMXSelect(), @@ -786,7 +770,6 @@ class ServiceForm(NetBoxModelForm): required=False, label=_('IP Addresses'), ) - comments = CommentField() fieldsets = ( FieldSet( diff --git a/netbox/netbox/forms/model_forms.py b/netbox/netbox/forms/model_forms.py index 6766abc6d..c76dbd77b 100644 --- a/netbox/netbox/forms/model_forms.py +++ b/netbox/netbox/forms/model_forms.py @@ -4,11 +4,15 @@ from django import forms from django.contrib.contenttypes.models import ContentType from extras.choices import * +from utilities.forms.fields import CommentField, SlugField from utilities.forms.mixins import CheckLastUpdatedMixin from .mixins import ChangelogMessageMixin, CustomFieldsMixin, OwnerMixin, TagsMixin __all__ = ( + 'NestedGroupModelForm', 'NetBoxModelForm', + 'OrganizationalModelForm', + 'PrimaryModelForm', ) @@ -16,7 +20,6 @@ class NetBoxModelForm( ChangelogMessageMixin, CheckLastUpdatedMixin, CustomFieldsMixin, - OwnerMixin, TagsMixin, forms.ModelForm ): @@ -74,3 +77,25 @@ class NetBoxModelForm( self.instance._m2m_values[field.name] = list(self.cleaned_data[field.name]) return super()._post_clean() + + +class PrimaryModelForm(OwnerMixin, NetBoxModelForm): + """ + Form for models which inherit from PrimaryModel. + """ + comments = CommentField() + + +class OrganizationalModelForm(OwnerMixin, NetBoxModelForm): + """ + Form for models which inherit from OrganizationalModel. + """ + slug = SlugField() + + +class NestedGroupModelForm(OwnerMixin, NetBoxModelForm): + """ + Form for models which inherit from NestedGroupModel. + """ + slug = SlugField() + comments = CommentField() diff --git a/netbox/tenancy/forms/model_forms.py b/netbox/tenancy/forms/model_forms.py index e442a9418..719ea8a93 100644 --- a/netbox/tenancy/forms/model_forms.py +++ b/netbox/tenancy/forms/model_forms.py @@ -1,9 +1,9 @@ from django import forms from django.utils.translation import gettext_lazy as _ -from netbox.forms import NetBoxModelForm +from netbox.forms import NestedGroupModelForm, NetBoxModelForm, OrganizationalModelForm, PrimaryModelForm from tenancy.models import * -from utilities.forms.fields import CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, SlugField +from utilities.forms.fields import DynamicModelChoiceField, DynamicModelMultipleChoiceField, SlugField from utilities.forms.rendering import FieldSet, ObjectAttribute __all__ = ( @@ -20,14 +20,12 @@ __all__ = ( # Tenants # -class TenantGroupForm(NetBoxModelForm): +class TenantGroupForm(NestedGroupModelForm): parent = DynamicModelChoiceField( label=_('Parent'), queryset=TenantGroup.objects.all(), required=False ) - slug = SlugField() - comments = CommentField() fieldsets = ( FieldSet('parent', 'name', 'slug', 'description', 'tags', name=_('Tenant Group')), @@ -40,14 +38,13 @@ class TenantGroupForm(NetBoxModelForm): ] -class TenantForm(NetBoxModelForm): +class TenantForm(PrimaryModelForm): slug = SlugField() group = DynamicModelChoiceField( label=_('Group'), queryset=TenantGroup.objects.all(), required=False ) - comments = CommentField() fieldsets = ( FieldSet('name', 'slug', 'group', 'description', 'tags', name=_('Tenant')), @@ -64,14 +61,12 @@ class TenantForm(NetBoxModelForm): # Contacts # -class ContactGroupForm(NetBoxModelForm): +class ContactGroupForm(NestedGroupModelForm): parent = DynamicModelChoiceField( label=_('Parent'), queryset=ContactGroup.objects.all(), required=False ) - slug = SlugField() - comments = CommentField() fieldsets = ( FieldSet('parent', 'name', 'slug', 'description', 'tags', name=_('Contact Group')), @@ -82,9 +77,7 @@ class ContactGroupForm(NetBoxModelForm): fields = ('parent', 'name', 'slug', 'description', 'owner', 'comments', 'tags') -class ContactRoleForm(NetBoxModelForm): - slug = SlugField() - +class ContactRoleForm(OrganizationalModelForm): fieldsets = ( FieldSet('name', 'slug', 'description', 'tags', name=_('Contact Role')), ) @@ -94,7 +87,7 @@ class ContactRoleForm(NetBoxModelForm): fields = ('name', 'slug', 'description', 'owner', 'tags') -class ContactForm(NetBoxModelForm): +class ContactForm(PrimaryModelForm): groups = DynamicModelMultipleChoiceField( label=_('Groups'), queryset=ContactGroup.objects.all(), @@ -105,7 +98,6 @@ class ContactForm(NetBoxModelForm): assume_scheme='https', required=False, ) - comments = CommentField() fieldsets = ( FieldSet( diff --git a/netbox/virtualization/forms/model_forms.py b/netbox/virtualization/forms/model_forms.py index 2e47631a7..9b4c21783 100644 --- a/netbox/virtualization/forms/model_forms.py +++ b/netbox/virtualization/forms/model_forms.py @@ -10,12 +10,10 @@ from dcim.models import Device, DeviceRole, MACAddress, Platform, Rack, Region, from extras.models import ConfigTemplate from ipam.choices import VLANQinQRoleChoices from ipam.models import IPAddress, VLAN, VLANGroup, VLANTranslationPolicy, VRF -from netbox.forms import NetBoxModelForm +from netbox.forms import NetBoxModelForm, OrganizationalModelForm, PrimaryModelForm from tenancy.forms import TenancyForm from utilities.forms import ConfirmationForm -from utilities.forms.fields import ( - CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, JSONField, SlugField, -) +from utilities.forms.fields import DynamicModelChoiceField, DynamicModelMultipleChoiceField, JSONField from utilities.forms.rendering import FieldSet from utilities.forms.widgets import HTMXSelect from virtualization.models import * @@ -32,9 +30,7 @@ __all__ = ( ) -class ClusterTypeForm(NetBoxModelForm): - slug = SlugField() - +class ClusterTypeForm(OrganizationalModelForm): fieldsets = ( FieldSet('name', 'slug', 'description', 'tags', name=_('Cluster Type')), ) @@ -46,9 +42,7 @@ class ClusterTypeForm(NetBoxModelForm): ) -class ClusterGroupForm(NetBoxModelForm): - slug = SlugField() - +class ClusterGroupForm(OrganizationalModelForm): fieldsets = ( FieldSet('name', 'slug', 'description', 'tags', name=_('Cluster Group')), ) @@ -60,7 +54,7 @@ class ClusterGroupForm(NetBoxModelForm): ) -class ClusterForm(TenancyForm, ScopedForm, NetBoxModelForm): +class ClusterForm(TenancyForm, ScopedForm, PrimaryModelForm): type = DynamicModelChoiceField( label=_('Type'), queryset=ClusterType.objects.all(), @@ -72,7 +66,6 @@ class ClusterForm(TenancyForm, ScopedForm, NetBoxModelForm): required=False, quick_add=True ) - comments = CommentField() fieldsets = ( FieldSet('name', 'type', 'group', 'status', 'description', 'tags', name=_('Cluster')), @@ -173,7 +166,7 @@ class ClusterRemoveDevicesForm(ConfirmationForm): ) -class VirtualMachineForm(TenancyForm, NetBoxModelForm): +class VirtualMachineForm(TenancyForm, PrimaryModelForm): site = DynamicModelChoiceField( label=_('Site'), queryset=Site.objects.all(), @@ -221,7 +214,6 @@ class VirtualMachineForm(TenancyForm, NetBoxModelForm): required=False, label=_('Config template') ) - comments = CommentField() fieldsets = ( FieldSet('name', 'role', 'status', 'description', 'serial', 'tags', name=_('Virtual Machine')), diff --git a/netbox/vpn/forms/model_forms.py b/netbox/vpn/forms/model_forms.py index 241ac9c38..ad9d73901 100644 --- a/netbox/vpn/forms/model_forms.py +++ b/netbox/vpn/forms/model_forms.py @@ -4,9 +4,9 @@ from django.utils.translation import gettext_lazy as _ from dcim.models import Device, Interface from ipam.models import IPAddress, RouteTarget, VLAN -from netbox.forms import NetBoxModelForm +from netbox.forms import NetBoxModelForm, OrganizationalModelForm, PrimaryModelForm from tenancy.forms import TenancyForm -from utilities.forms.fields import CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, SlugField +from utilities.forms.fields import DynamicModelChoiceField, DynamicModelMultipleChoiceField, SlugField from utilities.forms.rendering import FieldSet, TabbedGroups from utilities.forms.utils import add_blank_choice, get_field_value from utilities.forms.widgets import HTMXSelect @@ -29,9 +29,7 @@ __all__ = ( ) -class TunnelGroupForm(NetBoxModelForm): - slug = SlugField() - +class TunnelGroupForm(OrganizationalModelForm): fieldsets = ( FieldSet('name', 'slug', 'description', 'tags', name=_('Tunnel Group')), ) @@ -43,7 +41,7 @@ class TunnelGroupForm(NetBoxModelForm): ] -class TunnelForm(TenancyForm, NetBoxModelForm): +class TunnelForm(TenancyForm, PrimaryModelForm): group = DynamicModelChoiceField( queryset=TunnelGroup.objects.all(), label=_('Tunnel Group'), @@ -55,7 +53,6 @@ class TunnelForm(TenancyForm, NetBoxModelForm): label=_('IPSec Profile'), required=False ) - comments = CommentField() fieldsets = ( FieldSet('name', 'status', 'group', 'encapsulation', 'description', 'tunnel_id', 'tags', name=_('Tunnel')), @@ -293,7 +290,7 @@ class TunnelTerminationForm(NetBoxModelForm): self.instance.termination = self.cleaned_data.get('termination') -class IKEProposalForm(NetBoxModelForm): +class IKEProposalForm(PrimaryModelForm): fieldsets = ( FieldSet('name', 'description', 'tags', name=_('Proposal')), @@ -311,7 +308,7 @@ class IKEProposalForm(NetBoxModelForm): ] -class IKEPolicyForm(NetBoxModelForm): +class IKEPolicyForm(PrimaryModelForm): proposals = DynamicModelMultipleChoiceField( queryset=IKEProposal.objects.all(), label=_('Proposals'), @@ -330,7 +327,7 @@ class IKEPolicyForm(NetBoxModelForm): ] -class IPSecProposalForm(NetBoxModelForm): +class IPSecProposalForm(PrimaryModelForm): fieldsets = ( FieldSet('name', 'description', 'tags', name=_('Proposal')), @@ -348,7 +345,7 @@ class IPSecProposalForm(NetBoxModelForm): ] -class IPSecPolicyForm(NetBoxModelForm): +class IPSecPolicyForm(PrimaryModelForm): proposals = DynamicModelMultipleChoiceField( queryset=IPSecProposal.objects.all(), label=_('Proposals'), @@ -367,7 +364,7 @@ class IPSecPolicyForm(NetBoxModelForm): ] -class IPSecProfileForm(NetBoxModelForm): +class IPSecProfileForm(PrimaryModelForm): ike_policy = DynamicModelChoiceField( queryset=IKEPolicy.objects.all(), label=_('IKE policy') @@ -376,7 +373,6 @@ class IPSecProfileForm(NetBoxModelForm): queryset=IPSecPolicy.objects.all(), label=_('IPSec policy') ) - comments = CommentField() fieldsets = ( FieldSet('name', 'description', 'tags', name=_('Profile')), @@ -394,7 +390,7 @@ class IPSecProfileForm(NetBoxModelForm): # L2VPN # -class L2VPNForm(TenancyForm, NetBoxModelForm): +class L2VPNForm(TenancyForm, PrimaryModelForm): slug = SlugField() import_targets = DynamicModelMultipleChoiceField( label=_('Import targets'), @@ -406,7 +402,6 @@ class L2VPNForm(TenancyForm, NetBoxModelForm): queryset=RouteTarget.objects.all(), required=False ) - comments = CommentField() fieldsets = ( FieldSet('name', 'slug', 'type', 'status', 'identifier', 'description', 'tags', name=_('L2VPN')), diff --git a/netbox/wireless/forms/model_forms.py b/netbox/wireless/forms/model_forms.py index b92874564..0cd107ba6 100644 --- a/netbox/wireless/forms/model_forms.py +++ b/netbox/wireless/forms/model_forms.py @@ -1,12 +1,12 @@ from django.forms import PasswordInput from django.utils.translation import gettext_lazy as _ -from dcim.models import Device, Interface, Location, Site from dcim.forms.mixins import ScopedForm +from dcim.models import Device, Interface, Location, Site from ipam.models import VLAN -from netbox.forms import NetBoxModelForm +from netbox.forms import NestedGroupModelForm, PrimaryModelForm from tenancy.forms import TenancyForm -from utilities.forms.fields import CommentField, DynamicModelChoiceField, SlugField +from utilities.forms.fields import DynamicModelChoiceField from utilities.forms.mixins import DistanceValidationMixin from utilities.forms.rendering import FieldSet, InlineFields from wireless.models import * @@ -18,14 +18,12 @@ __all__ = ( ) -class WirelessLANGroupForm(NetBoxModelForm): +class WirelessLANGroupForm(NestedGroupModelForm): parent = DynamicModelChoiceField( label=_('Parent'), queryset=WirelessLANGroup.objects.all(), required=False ) - slug = SlugField() - comments = CommentField() fieldsets = ( FieldSet('parent', 'name', 'slug', 'description', 'tags', name=_('Wireless LAN Group')), @@ -38,7 +36,7 @@ class WirelessLANGroupForm(NetBoxModelForm): ] -class WirelessLANForm(ScopedForm, TenancyForm, NetBoxModelForm): +class WirelessLANForm(ScopedForm, TenancyForm, PrimaryModelForm): group = DynamicModelChoiceField( label=_('Group'), queryset=WirelessLANGroup.objects.all(), @@ -51,7 +49,6 @@ class WirelessLANForm(ScopedForm, TenancyForm, NetBoxModelForm): selector=True, label=_('VLAN') ) - comments = CommentField() fieldsets = ( FieldSet('ssid', 'group', 'vlan', 'status', 'description', 'tags', name=_('Wireless LAN')), @@ -74,7 +71,7 @@ class WirelessLANForm(ScopedForm, TenancyForm, NetBoxModelForm): } -class WirelessLinkForm(DistanceValidationMixin, TenancyForm, NetBoxModelForm): +class WirelessLinkForm(DistanceValidationMixin, TenancyForm, PrimaryModelForm): site_a = DynamicModelChoiceField( queryset=Site.objects.all(), required=False, @@ -159,7 +156,6 @@ class WirelessLinkForm(DistanceValidationMixin, TenancyForm, NetBoxModelForm): }, label=_('Interface') ) - comments = CommentField() fieldsets = ( FieldSet('site_a', 'location_a', 'device_a', 'interface_a', name=_('Side A')),