فهرست منبع

Closes #14740: Remove BootstrapMixin (#14841)

* Introduce custom form widget templates to apply CSS classes

* Apply both mandatory and optional CSS classes to form widgets

* Omit required & placeholder attrs

* Move annotation of field validation failures to CSS

* Remove BootstrapMixin class

* Remove obsolete ComponentTemplateImportForm class

* Remove obsolete custom forms for login & password change

* Clean up obsolete accommodations for 'required' widget attr
Jeremy Stretch 2 سال پیش
والد
کامیت
da085e60c2
33فایلهای تغییر یافته به همراه101 افزوده شده و 179 حذف شده
  1. 6 6
      netbox/account/views.py
  2. 1 2
      netbox/circuits/forms/bulk_import.py
  3. 2 2
      netbox/core/forms/model_forms.py
  4. 2 2
      netbox/dcim/forms/bulk_create.py
  5. 4 4
      netbox/dcim/forms/model_forms.py
  6. 10 15
      netbox/dcim/forms/object_import.py
  7. 2 2
      netbox/extras/dashboard/forms.py
  8. 1 2
      netbox/extras/dashboard/widgets.py
  9. 11 11
      netbox/extras/forms/model_forms.py
  10. 1 2
      netbox/extras/forms/reports.py
  11. 1 2
      netbox/extras/forms/scripts.py
  12. 1 2
      netbox/ipam/forms/bulk_create.py
  13. 3 4
      netbox/ipam/forms/model_forms.py
  14. 1 2
      netbox/netbox/forms/__init__.py
  15. 4 4
      netbox/netbox/forms/base.py
  16. 0 0
      netbox/project-static/dist/netbox.css
  17. 8 10
      netbox/project-static/styles/overrides/_slim-select.scss
  18. 9 0
      netbox/project-static/styles/transitional/_forms.scss
  19. 2 0
      netbox/templates/django/forms/widgets/attrs.html
  20. 1 1
      netbox/templates/django/forms/widgets/checkbox.html
  21. 5 0
      netbox/templates/django/forms/widgets/clearable_file_input.html
  22. 1 0
      netbox/templates/django/forms/widgets/input.html
  23. 5 0
      netbox/templates/django/forms/widgets/select.html
  24. 2 0
      netbox/templates/django/forms/widgets/textarea.html
  25. 0 1
      netbox/users/forms/__init__.py
  26. 0 25
      netbox/users/forms/authentication.py
  27. 3 3
      netbox/users/forms/bulk_edit.py
  28. 5 8
      netbox/users/forms/model_forms.py
  29. 1 2
      netbox/utilities/forms/bulk_import.py
  30. 5 6
      netbox/utilities/forms/forms.py
  31. 0 57
      netbox/utilities/forms/mixins.py
  32. 2 2
      netbox/virtualization/forms/bulk_create.py
  33. 2 2
      netbox/virtualization/forms/model_forms.py

+ 6 - 6
netbox/account/views.py

@@ -2,8 +2,8 @@ import logging
 
 
 from django.conf import settings
 from django.conf import settings
 from django.contrib import messages
 from django.contrib import messages
-from django.contrib.auth import login as auth_login, logout as auth_logout
-from django.contrib.auth import update_session_auth_hash
+from django.contrib.auth import login as auth_login, logout as auth_logout, update_session_auth_hash
+from django.contrib.auth.forms import AuthenticationForm, PasswordChangeForm
 from django.contrib.auth.mixins import LoginRequiredMixin
 from django.contrib.auth.mixins import LoginRequiredMixin
 from django.contrib.auth.models import update_last_login
 from django.contrib.auth.models import update_last_login
 from django.contrib.auth.signals import user_logged_in
 from django.contrib.auth.signals import user_logged_in
@@ -72,7 +72,7 @@ class LoginView(View):
         return auth_backends
         return auth_backends
 
 
     def get(self, request):
     def get(self, request):
-        form = forms.LoginForm(request)
+        form = AuthenticationForm(request)
 
 
         if request.user.is_authenticated:
         if request.user.is_authenticated:
             logger = logging.getLogger('netbox.auth.login')
             logger = logging.getLogger('netbox.auth.login')
@@ -85,7 +85,7 @@ class LoginView(View):
 
 
     def post(self, request):
     def post(self, request):
         logger = logging.getLogger('netbox.auth.login')
         logger = logging.getLogger('netbox.auth.login')
-        form = forms.LoginForm(request, data=request.POST)
+        form = AuthenticationForm(request, data=request.POST)
 
 
         if form.is_valid():
         if form.is_valid():
             logger.debug("Login form validation was successful")
             logger.debug("Login form validation was successful")
@@ -220,7 +220,7 @@ class ChangePasswordView(LoginRequiredMixin, View):
             messages.warning(request, "LDAP-authenticated user credentials cannot be changed within NetBox.")
             messages.warning(request, "LDAP-authenticated user credentials cannot be changed within NetBox.")
             return redirect('account:profile')
             return redirect('account:profile')
 
 
-        form = forms.PasswordChangeForm(user=request.user)
+        form = PasswordChangeForm(user=request.user)
 
 
         return render(request, self.template_name, {
         return render(request, self.template_name, {
             'form': form,
             'form': form,
@@ -228,7 +228,7 @@ class ChangePasswordView(LoginRequiredMixin, View):
         })
         })
 
 
     def post(self, request):
     def post(self, request):
-        form = forms.PasswordChangeForm(user=request.user, data=request.POST)
+        form = PasswordChangeForm(user=request.user, data=request.POST)
         if form.is_valid():
         if form.is_valid():
             form.save()
             form.save()
             update_session_auth_hash(request, form.user)
             update_session_auth_hash(request, form.user)

+ 1 - 2
netbox/circuits/forms/bulk_import.py

@@ -7,7 +7,6 @@ from django.utils.safestring import mark_safe
 from django.utils.translation import gettext_lazy as _
 from django.utils.translation import gettext_lazy as _
 from netbox.forms import NetBoxModelImportForm
 from netbox.forms import NetBoxModelImportForm
 from tenancy.models import Tenant
 from tenancy.models import Tenant
-from utilities.forms import BootstrapMixin
 from utilities.forms.fields import CSVChoiceField, CSVModelChoiceField, SlugField
 from utilities.forms.fields import CSVChoiceField, CSVModelChoiceField, SlugField
 
 
 __all__ = (
 __all__ = (
@@ -112,7 +111,7 @@ class CircuitImportForm(NetBoxModelImportForm):
         ]
         ]
 
 
 
 
-class CircuitTerminationImportForm(BootstrapMixin, forms.ModelForm):
+class CircuitTerminationImportForm(forms.ModelForm):
     site = CSVModelChoiceField(
     site = CSVModelChoiceField(
         label=_('Site'),
         label=_('Site'),
         queryset=Site.objects.all(),
         queryset=Site.objects.all(),

+ 2 - 2
netbox/core/forms/model_forms.py

@@ -11,7 +11,7 @@ from netbox.config import get_config, PARAMS
 from netbox.forms import NetBoxModelForm
 from netbox.forms import NetBoxModelForm
 from netbox.registry import registry
 from netbox.registry import registry
 from netbox.utils import get_data_backend_choices
 from netbox.utils import get_data_backend_choices
-from utilities.forms import BootstrapMixin, get_field_value
+from utilities.forms import get_field_value
 from utilities.forms.fields import CommentField
 from utilities.forms.fields import CommentField
 from utilities.forms.widgets import HTMXSelect
 from utilities.forms.widgets import HTMXSelect
 
 
@@ -138,7 +138,7 @@ class ConfigFormMetaclass(forms.models.ModelFormMetaclass):
         return super().__new__(mcs, name, bases, attrs)
         return super().__new__(mcs, name, bases, attrs)
 
 
 
 
-class ConfigRevisionForm(BootstrapMixin, forms.ModelForm, metaclass=ConfigFormMetaclass):
+class ConfigRevisionForm(forms.ModelForm, metaclass=ConfigFormMetaclass):
     """
     """
     Form for creating a new ConfigRevision.
     Form for creating a new ConfigRevision.
     """
     """

+ 2 - 2
netbox/dcim/forms/bulk_create.py

@@ -4,7 +4,7 @@ from django.utils.translation import gettext_lazy as _
 from dcim.models import *
 from dcim.models import *
 from extras.models import Tag
 from extras.models import Tag
 from netbox.forms.mixins import CustomFieldsMixin
 from netbox.forms.mixins import CustomFieldsMixin
-from utilities.forms import BootstrapMixin, form_from_model
+from utilities.forms import form_from_model
 from utilities.forms.fields import DynamicModelMultipleChoiceField, ExpandableNameField
 from utilities.forms.fields import DynamicModelMultipleChoiceField, ExpandableNameField
 from .object_create import ComponentCreateForm
 from .object_create import ComponentCreateForm
 
 
@@ -26,7 +26,7 @@ __all__ = (
 # Device components
 # Device components
 #
 #
 
 
-class DeviceBulkAddComponentForm(BootstrapMixin, CustomFieldsMixin, ComponentCreateForm):
+class DeviceBulkAddComponentForm(CustomFieldsMixin, ComponentCreateForm):
     pk = forms.ModelMultipleChoiceField(
     pk = forms.ModelMultipleChoiceField(
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
         widget=forms.MultipleHiddenInput()
         widget=forms.MultipleHiddenInput()

+ 4 - 4
netbox/dcim/forms/model_forms.py

@@ -11,7 +11,7 @@ from extras.models import ConfigTemplate
 from ipam.models import ASN, IPAddress, VLAN, VLANGroup, VRF
 from ipam.models import ASN, IPAddress, VLAN, VLANGroup, VRF
 from netbox.forms import NetBoxModelForm
 from netbox.forms import NetBoxModelForm
 from tenancy.forms import TenancyForm
 from tenancy.forms import TenancyForm
-from utilities.forms import BootstrapMixin, add_blank_choice
+from utilities.forms import add_blank_choice
 from utilities.forms.fields import (
 from utilities.forms.fields import (
     CommentField, ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, JSONField,
     CommentField, ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, JSONField,
     NumericArrayField, SlugField,
     NumericArrayField, SlugField,
@@ -748,7 +748,7 @@ class DeviceVCMembershipForm(forms.ModelForm):
         return vc_position
         return vc_position
 
 
 
 
-class VCMemberSelectForm(BootstrapMixin, forms.Form):
+class VCMemberSelectForm(forms.Form):
     device = DynamicModelChoiceField(
     device = DynamicModelChoiceField(
         label=_('Device'),
         label=_('Device'),
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
@@ -771,7 +771,7 @@ class VCMemberSelectForm(BootstrapMixin, forms.Form):
 # Device component templates
 # Device component templates
 #
 #
 
 
-class ComponentTemplateForm(BootstrapMixin, forms.ModelForm):
+class ComponentTemplateForm(forms.ModelForm):
     device_type = DynamicModelChoiceField(
     device_type = DynamicModelChoiceField(
         label=_('Device type'),
         label=_('Device type'),
         queryset=DeviceType.objects.all()
         queryset=DeviceType.objects.all()
@@ -1272,7 +1272,7 @@ class DeviceBayForm(DeviceComponentForm):
         ]
         ]
 
 
 
 
-class PopulateDeviceBayForm(BootstrapMixin, forms.Form):
+class PopulateDeviceBayForm(forms.Form):
     installed_device = forms.ModelChoiceField(
     installed_device = forms.ModelChoiceField(
         queryset=Device.objects.all(),
         queryset=Device.objects.all(),
         label=_('Child Device'),
         label=_('Child Device'),

+ 10 - 15
netbox/dcim/forms/object_import.py

@@ -3,7 +3,6 @@ from django.utils.translation import gettext_lazy as _
 
 
 from dcim.choices import InterfacePoEModeChoices, InterfacePoETypeChoices, InterfaceTypeChoices, PortTypeChoices
 from dcim.choices import InterfacePoEModeChoices, InterfacePoETypeChoices, InterfaceTypeChoices, PortTypeChoices
 from dcim.models import *
 from dcim.models import *
-from utilities.forms import BootstrapMixin
 from wireless.choices import WirelessRoleChoices
 from wireless.choices import WirelessRoleChoices
 
 
 __all__ = (
 __all__ = (
@@ -24,11 +23,7 @@ __all__ = (
 # Component template import forms
 # Component template import forms
 #
 #
 
 
-class ComponentTemplateImportForm(BootstrapMixin, forms.ModelForm):
-    pass
-
-
-class ConsolePortTemplateImportForm(ComponentTemplateImportForm):
+class ConsolePortTemplateImportForm(forms.ModelForm):
 
 
     class Meta:
     class Meta:
         model = ConsolePortTemplate
         model = ConsolePortTemplate
@@ -37,7 +32,7 @@ class ConsolePortTemplateImportForm(ComponentTemplateImportForm):
         ]
         ]
 
 
 
 
-class ConsoleServerPortTemplateImportForm(ComponentTemplateImportForm):
+class ConsoleServerPortTemplateImportForm(forms.ModelForm):
 
 
     class Meta:
     class Meta:
         model = ConsoleServerPortTemplate
         model = ConsoleServerPortTemplate
@@ -46,7 +41,7 @@ class ConsoleServerPortTemplateImportForm(ComponentTemplateImportForm):
         ]
         ]
 
 
 
 
-class PowerPortTemplateImportForm(ComponentTemplateImportForm):
+class PowerPortTemplateImportForm(forms.ModelForm):
 
 
     class Meta:
     class Meta:
         model = PowerPortTemplate
         model = PowerPortTemplate
@@ -55,7 +50,7 @@ class PowerPortTemplateImportForm(ComponentTemplateImportForm):
         ]
         ]
 
 
 
 
-class PowerOutletTemplateImportForm(ComponentTemplateImportForm):
+class PowerOutletTemplateImportForm(forms.ModelForm):
     power_port = forms.ModelChoiceField(
     power_port = forms.ModelChoiceField(
         label=_('Power port'),
         label=_('Power port'),
         queryset=PowerPortTemplate.objects.all(),
         queryset=PowerPortTemplate.objects.all(),
@@ -84,7 +79,7 @@ class PowerOutletTemplateImportForm(ComponentTemplateImportForm):
         return module_type
         return module_type
 
 
 
 
-class InterfaceTemplateImportForm(ComponentTemplateImportForm):
+class InterfaceTemplateImportForm(forms.ModelForm):
     type = forms.ChoiceField(
     type = forms.ChoiceField(
         label=_('Type'),
         label=_('Type'),
         choices=InterfaceTypeChoices.CHOICES
         choices=InterfaceTypeChoices.CHOICES
@@ -113,7 +108,7 @@ class InterfaceTemplateImportForm(ComponentTemplateImportForm):
         ]
         ]
 
 
 
 
-class FrontPortTemplateImportForm(ComponentTemplateImportForm):
+class FrontPortTemplateImportForm(forms.ModelForm):
     type = forms.ChoiceField(
     type = forms.ChoiceField(
         label=_('Type'),
         label=_('Type'),
         choices=PortTypeChoices.CHOICES
         choices=PortTypeChoices.CHOICES
@@ -145,7 +140,7 @@ class FrontPortTemplateImportForm(ComponentTemplateImportForm):
         ]
         ]
 
 
 
 
-class RearPortTemplateImportForm(ComponentTemplateImportForm):
+class RearPortTemplateImportForm(forms.ModelForm):
     type = forms.ChoiceField(
     type = forms.ChoiceField(
         label=_('Type'),
         label=_('Type'),
         choices=PortTypeChoices.CHOICES
         choices=PortTypeChoices.CHOICES
@@ -158,7 +153,7 @@ class RearPortTemplateImportForm(ComponentTemplateImportForm):
         ]
         ]
 
 
 
 
-class ModuleBayTemplateImportForm(ComponentTemplateImportForm):
+class ModuleBayTemplateImportForm(forms.ModelForm):
 
 
     class Meta:
     class Meta:
         model = ModuleBayTemplate
         model = ModuleBayTemplate
@@ -167,7 +162,7 @@ class ModuleBayTemplateImportForm(ComponentTemplateImportForm):
         ]
         ]
 
 
 
 
-class DeviceBayTemplateImportForm(ComponentTemplateImportForm):
+class DeviceBayTemplateImportForm(forms.ModelForm):
 
 
     class Meta:
     class Meta:
         model = DeviceBayTemplate
         model = DeviceBayTemplate
@@ -176,7 +171,7 @@ class DeviceBayTemplateImportForm(ComponentTemplateImportForm):
         ]
         ]
 
 
 
 
-class InventoryItemTemplateImportForm(ComponentTemplateImportForm):
+class InventoryItemTemplateImportForm(forms.ModelForm):
     parent = forms.ModelChoiceField(
     parent = forms.ModelChoiceField(
         label=_('Parent'),
         label=_('Parent'),
         queryset=InventoryItemTemplate.objects.all(),
         queryset=InventoryItemTemplate.objects.all(),

+ 2 - 2
netbox/extras/dashboard/forms.py

@@ -4,7 +4,7 @@ from django.utils.translation import gettext as _
 
 
 from extras.choices import DashboardWidgetColorChoices
 from extras.choices import DashboardWidgetColorChoices
 from netbox.registry import registry
 from netbox.registry import registry
-from utilities.forms import BootstrapMixin, add_blank_choice
+from utilities.forms import add_blank_choice
 
 
 __all__ = (
 __all__ = (
     'DashboardWidgetAddForm',
     'DashboardWidgetAddForm',
@@ -16,7 +16,7 @@ def get_widget_choices():
     return registry['widgets'].items()
     return registry['widgets'].items()
 
 
 
 
-class DashboardWidgetForm(BootstrapMixin, forms.Form):
+class DashboardWidgetForm(forms.Form):
     title = forms.CharField(
     title = forms.CharField(
         required=False
         required=False
     )
     )

+ 1 - 2
netbox/extras/dashboard/widgets.py

@@ -15,7 +15,6 @@ from django.utils.translation import gettext as _
 from core.models import ContentType
 from core.models import ContentType
 from extras.choices import BookmarkOrderingChoices
 from extras.choices import BookmarkOrderingChoices
 from utilities.choices import ButtonColorChoices
 from utilities.choices import ButtonColorChoices
-from utilities.forms import BootstrapMixin
 from utilities.permissions import get_permission_for_model
 from utilities.permissions import get_permission_for_model
 from utilities.templatetags.builtins.filters import render_markdown
 from utilities.templatetags.builtins.filters import render_markdown
 from utilities.utils import content_type_identifier, content_type_name, dict_to_querydict, get_viewname
 from utilities.utils import content_type_identifier, content_type_name, dict_to_querydict, get_viewname
@@ -58,7 +57,7 @@ def get_models_from_content_types(content_types):
     return models
     return models
 
 
 
 
-class WidgetConfigForm(BootstrapMixin, forms.Form):
+class WidgetConfigForm(forms.Form):
     pass
     pass
 
 
 
 

+ 11 - 11
netbox/extras/forms/model_forms.py

@@ -13,7 +13,7 @@ from extras.choices import *
 from extras.models import *
 from extras.models import *
 from netbox.forms import NetBoxModelForm
 from netbox.forms import NetBoxModelForm
 from tenancy.models import Tenant, TenantGroup
 from tenancy.models import Tenant, TenantGroup
-from utilities.forms import BootstrapMixin, add_blank_choice, get_field_value
+from utilities.forms import add_blank_choice, get_field_value
 from utilities.forms.fields import (
 from utilities.forms.fields import (
     CommentField, ContentTypeChoiceField, ContentTypeMultipleChoiceField, DynamicModelChoiceField,
     CommentField, ContentTypeChoiceField, ContentTypeMultipleChoiceField, DynamicModelChoiceField,
     DynamicModelMultipleChoiceField, JSONField, SlugField,
     DynamicModelMultipleChoiceField, JSONField, SlugField,
@@ -38,7 +38,7 @@ __all__ = (
 )
 )
 
 
 
 
-class CustomFieldForm(BootstrapMixin, forms.ModelForm):
+class CustomFieldForm(forms.ModelForm):
     content_types = ContentTypeMultipleChoiceField(
     content_types = ContentTypeMultipleChoiceField(
         label=_('Content types'),
         label=_('Content types'),
         queryset=ContentType.objects.with_feature('custom_fields')
         queryset=ContentType.objects.with_feature('custom_fields')
@@ -83,7 +83,7 @@ class CustomFieldForm(BootstrapMixin, forms.ModelForm):
             self.fields['type'].disabled = True
             self.fields['type'].disabled = True
 
 
 
 
-class CustomFieldChoiceSetForm(BootstrapMixin, forms.ModelForm):
+class CustomFieldChoiceSetForm(forms.ModelForm):
     extra_choices = forms.CharField(
     extra_choices = forms.CharField(
         widget=ChoicesWidget(),
         widget=ChoicesWidget(),
         required=False,
         required=False,
@@ -122,7 +122,7 @@ class CustomFieldChoiceSetForm(BootstrapMixin, forms.ModelForm):
         return data
         return data
 
 
 
 
-class CustomLinkForm(BootstrapMixin, forms.ModelForm):
+class CustomLinkForm(forms.ModelForm):
     content_types = ContentTypeMultipleChoiceField(
     content_types = ContentTypeMultipleChoiceField(
         label=_('Content types'),
         label=_('Content types'),
         queryset=ContentType.objects.with_feature('custom_links')
         queryset=ContentType.objects.with_feature('custom_links')
@@ -149,7 +149,7 @@ class CustomLinkForm(BootstrapMixin, forms.ModelForm):
         }
         }
 
 
 
 
-class ExportTemplateForm(BootstrapMixin, SyncedDataMixin, forms.ModelForm):
+class ExportTemplateForm(SyncedDataMixin, forms.ModelForm):
     content_types = ContentTypeMultipleChoiceField(
     content_types = ContentTypeMultipleChoiceField(
         label=_('Content types'),
         label=_('Content types'),
         queryset=ContentType.objects.with_feature('export_templates')
         queryset=ContentType.objects.with_feature('export_templates')
@@ -189,7 +189,7 @@ class ExportTemplateForm(BootstrapMixin, SyncedDataMixin, forms.ModelForm):
         return self.cleaned_data
         return self.cleaned_data
 
 
 
 
-class SavedFilterForm(BootstrapMixin, forms.ModelForm):
+class SavedFilterForm(forms.ModelForm):
     slug = SlugField()
     slug = SlugField()
     content_types = ContentTypeMultipleChoiceField(
     content_types = ContentTypeMultipleChoiceField(
         label=_('Content types'),
         label=_('Content types'),
@@ -216,7 +216,7 @@ class SavedFilterForm(BootstrapMixin, forms.ModelForm):
         super().__init__(*args, initial=initial, **kwargs)
         super().__init__(*args, initial=initial, **kwargs)
 
 
 
 
-class BookmarkForm(BootstrapMixin, forms.ModelForm):
+class BookmarkForm(forms.ModelForm):
     object_type = ContentTypeChoiceField(
     object_type = ContentTypeChoiceField(
         label=_('Object type'),
         label=_('Object type'),
         queryset=ContentType.objects.with_feature('bookmarks')
         queryset=ContentType.objects.with_feature('bookmarks')
@@ -367,7 +367,7 @@ class EventRuleForm(NetBoxModelForm):
         return super().save(*args, **kwargs)
         return super().save(*args, **kwargs)
 
 
 
 
-class TagForm(BootstrapMixin, forms.ModelForm):
+class TagForm(forms.ModelForm):
     slug = SlugField()
     slug = SlugField()
     object_types = ContentTypeMultipleChoiceField(
     object_types = ContentTypeMultipleChoiceField(
         label=_('Object types'),
         label=_('Object types'),
@@ -386,7 +386,7 @@ class TagForm(BootstrapMixin, forms.ModelForm):
         ]
         ]
 
 
 
 
-class ConfigContextForm(BootstrapMixin, SyncedDataMixin, forms.ModelForm):
+class ConfigContextForm(SyncedDataMixin, forms.ModelForm):
     regions = DynamicModelMultipleChoiceField(
     regions = DynamicModelMultipleChoiceField(
         label=_('Regions'),
         label=_('Regions'),
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),
@@ -497,7 +497,7 @@ class ConfigContextForm(BootstrapMixin, SyncedDataMixin, forms.ModelForm):
         return self.cleaned_data
         return self.cleaned_data
 
 
 
 
-class ConfigTemplateForm(BootstrapMixin, SyncedDataMixin, forms.ModelForm):
+class ConfigTemplateForm(SyncedDataMixin, forms.ModelForm):
     tags = DynamicModelMultipleChoiceField(
     tags = DynamicModelMultipleChoiceField(
         label=_('Tags'),
         label=_('Tags'),
         queryset=Tag.objects.all(),
         queryset=Tag.objects.all(),
@@ -541,7 +541,7 @@ class ConfigTemplateForm(BootstrapMixin, SyncedDataMixin, forms.ModelForm):
         return self.cleaned_data
         return self.cleaned_data
 
 
 
 
-class ImageAttachmentForm(BootstrapMixin, forms.ModelForm):
+class ImageAttachmentForm(forms.ModelForm):
 
 
     class Meta:
     class Meta:
         model = ImageAttachment
         model = ImageAttachment

+ 1 - 2
netbox/extras/forms/reports.py

@@ -2,7 +2,6 @@ from django import forms
 from django.utils.translation import gettext_lazy as _
 from django.utils.translation import gettext_lazy as _
 
 
 from extras.choices import DurationChoices
 from extras.choices import DurationChoices
-from utilities.forms import BootstrapMixin
 from utilities.forms.widgets import DateTimePicker, NumberWithOptions
 from utilities.forms.widgets import DateTimePicker, NumberWithOptions
 from utilities.utils import local_now
 from utilities.utils import local_now
 
 
@@ -11,7 +10,7 @@ __all__ = (
 )
 )
 
 
 
 
-class ReportForm(BootstrapMixin, forms.Form):
+class ReportForm(forms.Form):
     schedule_at = forms.DateTimeField(
     schedule_at = forms.DateTimeField(
         required=False,
         required=False,
         widget=DateTimePicker(),
         widget=DateTimePicker(),

+ 1 - 2
netbox/extras/forms/scripts.py

@@ -2,7 +2,6 @@ from django import forms
 from django.utils.translation import gettext_lazy as _
 from django.utils.translation import gettext_lazy as _
 
 
 from extras.choices import DurationChoices
 from extras.choices import DurationChoices
-from utilities.forms import BootstrapMixin
 from utilities.forms.widgets import DateTimePicker, NumberWithOptions
 from utilities.forms.widgets import DateTimePicker, NumberWithOptions
 from utilities.utils import local_now
 from utilities.utils import local_now
 
 
@@ -11,7 +10,7 @@ __all__ = (
 )
 )
 
 
 
 
-class ScriptForm(BootstrapMixin, forms.Form):
+class ScriptForm(forms.Form):
     _commit = forms.BooleanField(
     _commit = forms.BooleanField(
         required=False,
         required=False,
         initial=True,
         initial=True,

+ 1 - 2
netbox/ipam/forms/bulk_create.py

@@ -1,7 +1,6 @@
 from django import forms
 from django import forms
 from django.utils.translation import gettext_lazy as _
 from django.utils.translation import gettext_lazy as _
 
 
-from utilities.forms import BootstrapMixin
 from utilities.forms.fields import ExpandableIPAddressField
 from utilities.forms.fields import ExpandableIPAddressField
 
 
 __all__ = (
 __all__ = (
@@ -9,7 +8,7 @@ __all__ = (
 )
 )
 
 
 
 
-class IPAddressBulkCreateForm(BootstrapMixin, forms.Form):
+class IPAddressBulkCreateForm(forms.Form):
     pattern = ExpandableIPAddressField(
     pattern = ExpandableIPAddressField(
         label=_('Address pattern')
         label=_('Address pattern')
     )
     )

+ 3 - 4
netbox/ipam/forms/model_forms.py

@@ -11,7 +11,7 @@ from ipam.models import *
 from netbox.forms import NetBoxModelForm
 from netbox.forms import NetBoxModelForm
 from tenancy.forms import TenancyForm
 from tenancy.forms import TenancyForm
 from utilities.exceptions import PermissionsViolation
 from utilities.exceptions import PermissionsViolation
-from utilities.forms import BootstrapMixin, add_blank_choice
+from utilities.forms import add_blank_choice
 from utilities.forms.fields import (
 from utilities.forms.fields import (
     CommentField, ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, NumericArrayField,
     CommentField, ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, NumericArrayField,
     SlugField,
     SlugField,
@@ -419,7 +419,7 @@ class IPAddressBulkAddForm(TenancyForm, NetBoxModelForm):
         ]
         ]
 
 
 
 
-class IPAddressAssignForm(BootstrapMixin, forms.Form):
+class IPAddressAssignForm(forms.Form):
     vrf_id = DynamicModelChoiceField(
     vrf_id = DynamicModelChoiceField(
         queryset=VRF.objects.all(),
         queryset=VRF.objects.all(),
         required=False,
         required=False,
@@ -504,7 +504,7 @@ class FHRPGroupForm(NetBoxModelForm):
                 })
                 })
 
 
 
 
-class FHRPGroupAssignmentForm(BootstrapMixin, forms.ModelForm):
+class FHRPGroupAssignmentForm(forms.ModelForm):
     group = DynamicModelChoiceField(
     group = DynamicModelChoiceField(
         label=_('Group'),
         label=_('Group'),
         queryset=FHRPGroup.objects.all()
         queryset=FHRPGroup.objects.all()
@@ -738,7 +738,6 @@ class ServiceCreateForm(ServiceForm):
         # Fields which may be populated from a ServiceTemplate are not required
         # Fields which may be populated from a ServiceTemplate are not required
         for field in ('name', 'protocol', 'ports'):
         for field in ('name', 'protocol', 'ports'):
             self.fields[field].required = False
             self.fields[field].required = False
-            del self.fields[field].widget.attrs['required']
 
 
     def clean(self):
     def clean(self):
         super().clean()
         super().clean()

+ 1 - 2
netbox/netbox/forms/__init__.py

@@ -5,7 +5,6 @@ from django.utils.translation import gettext as _
 
 
 from netbox.search import LookupTypes
 from netbox.search import LookupTypes
 from netbox.search.backends import search_backend
 from netbox.search.backends import search_backend
-from utilities.forms import BootstrapMixin
 
 
 from .base import *
 from .base import *
 
 
@@ -18,7 +17,7 @@ LOOKUP_CHOICES = (
 )
 )
 
 
 
 
-class SearchForm(BootstrapMixin, forms.Form):
+class SearchForm(forms.Form):
     q = forms.CharField(
     q = forms.CharField(
         label=_('Search'),
         label=_('Search'),
         widget=forms.TextInput(
         widget=forms.TextInput(

+ 4 - 4
netbox/netbox/forms/base.py

@@ -7,7 +7,7 @@ from extras.choices import *
 from extras.models import CustomField, Tag
 from extras.models import CustomField, Tag
 from utilities.forms import CSVModelForm
 from utilities.forms import CSVModelForm
 from utilities.forms.fields import CSVModelMultipleChoiceField, DynamicModelMultipleChoiceField
 from utilities.forms.fields import CSVModelMultipleChoiceField, DynamicModelMultipleChoiceField
-from utilities.forms.mixins import BootstrapMixin, CheckLastUpdatedMixin
+from utilities.forms.mixins import CheckLastUpdatedMixin
 from .mixins import CustomFieldsMixin, SavedFiltersMixin, TagsMixin
 from .mixins import CustomFieldsMixin, SavedFiltersMixin, TagsMixin
 
 
 __all__ = (
 __all__ = (
@@ -18,7 +18,7 @@ __all__ = (
 )
 )
 
 
 
 
-class NetBoxModelForm(BootstrapMixin, CheckLastUpdatedMixin, CustomFieldsMixin, TagsMixin, forms.ModelForm):
+class NetBoxModelForm(CheckLastUpdatedMixin, CustomFieldsMixin, TagsMixin, forms.ModelForm):
     """
     """
     Base form for creating & editing NetBox models. Extends Django's ModelForm to add support for custom fields.
     Base form for creating & editing NetBox models. Extends Django's ModelForm to add support for custom fields.
 
 
@@ -96,7 +96,7 @@ class NetBoxModelImportForm(CSVModelForm, NetBoxModelForm):
         return customfield.to_form_field(for_csv_import=True)
         return customfield.to_form_field(for_csv_import=True)
 
 
 
 
-class NetBoxModelBulkEditForm(BootstrapMixin, CustomFieldsMixin, forms.Form):
+class NetBoxModelBulkEditForm(CustomFieldsMixin, forms.Form):
     """
     """
     Base form for modifying multiple NetBox objects (of the same type) in bulk via the UI. Adds support for custom
     Base form for modifying multiple NetBox objects (of the same type) in bulk via the UI. Adds support for custom
     fields and adding/removing tags.
     fields and adding/removing tags.
@@ -146,7 +146,7 @@ class NetBoxModelBulkEditForm(BootstrapMixin, CustomFieldsMixin, forms.Form):
         self.nullable_fields = (*self.nullable_fields, *nullable_custom_fields)
         self.nullable_fields = (*self.nullable_fields, *nullable_custom_fields)
 
 
 
 
-class NetBoxModelFilterSetForm(BootstrapMixin, CustomFieldsMixin, SavedFiltersMixin, forms.Form):
+class NetBoxModelFilterSetForm(CustomFieldsMixin, SavedFiltersMixin, forms.Form):
     """
     """
     Base form for FilerSet forms. These are used to filter object lists in the NetBox UI. Note that the
     Base form for FilerSet forms. These are used to filter object lists in the NetBox UI. Note that the
     corresponding FilterSet *must* provide a `q` filter.
     corresponding FilterSet *must* provide a `q` filter.

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
netbox/project-static/dist/netbox.css


+ 8 - 10
netbox/project-static/styles/overrides/_slim-select.scss

@@ -37,16 +37,6 @@ $spacing-s: $input-padding-x;
 .ss-main {
 .ss-main {
   color: $form-select-color;
   color: $form-select-color;
 
 
-  &.is-invalid .ss-single-selected,
-  &.is-invalid .ss-multi-selected {
-    border-color: $form-feedback-icon-invalid-color;
-  }
-
-  &.is-valid .ss-single-selected,
-  &.is-valid .ss-multi-selected {
-    border-color: $form-feedback-icon-valid-color;
-  }
-
   .ss-single-selected,
   .ss-single-selected,
   .ss-multi-selected {
   .ss-multi-selected {
     padding: $form-select-padding-y $input-padding-x $form-select-padding-y $form-select-padding-x;
     padding: $form-select-padding-y $input-padding-x $form-select-padding-y $form-select-padding-x;
@@ -195,3 +185,11 @@ $spacing-s: $input-padding-x;
 		}
 		}
 	}
 	}
 }
 }
+
+// Apply red border for fields inside a row with .has-errors
+.has-errors {
+  .ss-single-selected,
+  .ss-multi-selected {
+    border-color: $red;
+  }
+}

+ 9 - 0
netbox/project-static/styles/transitional/_forms.scss

@@ -16,3 +16,12 @@ form.object-edit {
     content: '\f06C4';
     content: '\f06C4';
   }
   }
 }
 }
+
+// Set red border on form fields inside a row with .has-errors
+.has-errors {
+  input,
+  select,
+  textarea {
+    border: 1px solid $red;
+  }
+}

+ 2 - 0
netbox/templates/django/forms/widgets/attrs.html

@@ -0,0 +1,2 @@
+{# Skip "class" attribute, which needs to be handled on the widget directly. #}
+{% for name, value in widget.attrs.items %}{% if name != 'class' %}{% if value is not False %} {{ name }}{% if value is not True %}="{{ value|stringformat:'s' }}"{% endif %}{% endif %}{% endif %}{% endfor %}

+ 1 - 1
netbox/templates/django/forms/widgets/checkbox.html

@@ -4,4 +4,4 @@
   _selected_action to avoid breaking the admin UI.
   _selected_action to avoid breaking the admin UI.
 {% endcomment %}
 {% endcomment %}
 {% if widget.name != '_selected_action' %}<input type="hidden" name="{{ widget.name }}" value="">{% endif %}
 {% if widget.name != '_selected_action' %}<input type="hidden" name="{{ widget.name }}" value="">{% endif %}
-{% include "django/forms/widgets/input.html" %}
+<input type="checkbox" name="{{ widget.name }}"{% if widget.value != None %} value="{{ widget.value|stringformat:'s' }}"{% endif %} {% include "django/forms/widgets/attrs.html" %} class="form-check-input{% if 'class' in widget.attrs %} {{ widget.attrs.class }}{% endif %}">

+ 5 - 0
netbox/templates/django/forms/widgets/clearable_file_input.html

@@ -0,0 +1,5 @@
+{% if widget.is_initial %}{{ widget.initial_text }}: <a href="{{ widget.value.url }}">{{ widget.value }}</a>{% if not widget.required %}
+<input type="checkbox" name="{{ widget.checkbox_name }}" id="{{ widget.checkbox_id }}"{% if widget.attrs.disabled %} disabled{% endif %}>
+<label for="{{ widget.checkbox_id }}">{{ widget.clear_checkbox_label }}</label>{% endif %}<br>
+{{ widget.input_text }}:{% endif %}
+<input type="{{ widget.type }}" name="{{ widget.name }}"{% include "django/forms/widgets/attrs.html" %} class="form-control{% if 'class' in widget.attrs %} {{ widget.attrs.class }}{% endif %}">

+ 1 - 0
netbox/templates/django/forms/widgets/input.html

@@ -0,0 +1 @@
+<input type="{{ widget.type }}" name="{{ widget.name }}"{% if widget.value != None %} value="{{ widget.value|stringformat:'s' }}"{% endif %}{% include "django/forms/widgets/attrs.html" %} class="form-control{% if 'class' in widget.attrs %} {{ widget.attrs.class }}{% endif %}">

+ 5 - 0
netbox/templates/django/forms/widgets/select.html

@@ -0,0 +1,5 @@
+<select name="{{ widget.name }}"{% include "django/forms/widgets/attrs.html" %} class="{% if 'size' in widget.attrs %}form-select form-select-sm{% else %}netbox-static-select{% endif %}{% if 'class' in widget.attrs %} {{ widget.attrs.class }}{% endif %}">{% for group_name, group_choices, group_index in widget.optgroups %}{% if group_name %}
+  <optgroup label="{{ group_name }}">{% endif %}{% for option in group_choices %}
+  {% include option.template_name with widget=option %}{% endfor %}{% if group_name %}
+  </optgroup>{% endif %}{% endfor %}
+</select>

+ 2 - 0
netbox/templates/django/forms/widgets/textarea.html

@@ -0,0 +1,2 @@
+<textarea name="{{ widget.name }}"{% include "django/forms/widgets/attrs.html" %} class="form-control{% if 'class' in widget.attrs %} {{ widget.attrs.class }}{% endif %}">
+{% if widget.value %}{{ widget.value }}{% endif %}</textarea>

+ 0 - 1
netbox/users/forms/__init__.py

@@ -1,4 +1,3 @@
-from .authentication import *
 from .bulk_edit import *
 from .bulk_edit import *
 from .bulk_import import *
 from .bulk_import import *
 from .filtersets import *
 from .filtersets import *

+ 0 - 25
netbox/users/forms/authentication.py

@@ -1,25 +0,0 @@
-from django.contrib.auth.forms import (
-    AuthenticationForm,
-    PasswordChangeForm as DjangoPasswordChangeForm,
-)
-
-from utilities.forms import BootstrapMixin
-
-__all__ = (
-    'LoginForm',
-    'PasswordChangeForm',
-)
-
-
-class LoginForm(BootstrapMixin, AuthenticationForm):
-    """
-    Used to authenticate a user by username and password.
-    """
-    pass
-
-
-class PasswordChangeForm(BootstrapMixin, DjangoPasswordChangeForm):
-    """
-    This form enables a user to change his or her own password.
-    """
-    pass

+ 3 - 3
netbox/users/forms/bulk_edit.py

@@ -5,7 +5,7 @@ from django.utils.translation import gettext_lazy as _
 from ipam.formfields import IPNetworkFormField
 from ipam.formfields import IPNetworkFormField
 from ipam.validators import prefix_validator
 from ipam.validators import prefix_validator
 from users.models import *
 from users.models import *
-from utilities.forms import BootstrapMixin, BulkEditForm
+from utilities.forms import BulkEditForm
 from utilities.forms.widgets import BulkEditNullBooleanSelect, DateTimePicker
 from utilities.forms.widgets import BulkEditNullBooleanSelect, DateTimePicker
 
 
 __all__ = (
 __all__ = (
@@ -15,7 +15,7 @@ __all__ = (
 )
 )
 
 
 
 
-class UserBulkEditForm(BootstrapMixin, forms.Form):
+class UserBulkEditForm(forms.Form):
     pk = forms.ModelMultipleChoiceField(
     pk = forms.ModelMultipleChoiceField(
         queryset=NetBoxUser.objects.all(),
         queryset=NetBoxUser.objects.all(),
         widget=forms.MultipleHiddenInput
         widget=forms.MultipleHiddenInput
@@ -53,7 +53,7 @@ class UserBulkEditForm(BootstrapMixin, forms.Form):
     nullable_fields = ('first_name', 'last_name')
     nullable_fields = ('first_name', 'last_name')
 
 
 
 
-class ObjectPermissionBulkEditForm(BootstrapMixin, forms.Form):
+class ObjectPermissionBulkEditForm(forms.Form):
     pk = forms.ModelMultipleChoiceField(
     pk = forms.ModelMultipleChoiceField(
         queryset=ObjectPermission.objects.all(),
         queryset=ObjectPermission.objects.all(),
         widget=forms.MultipleHiddenInput
         widget=forms.MultipleHiddenInput

+ 5 - 8
netbox/users/forms/model_forms.py

@@ -13,7 +13,6 @@ from ipam.validators import prefix_validator
 from netbox.preferences import PREFERENCES
 from netbox.preferences import PREFERENCES
 from users.constants import *
 from users.constants import *
 from users.models import *
 from users.models import *
-from utilities.forms import BootstrapMixin
 from utilities.forms.fields import ContentTypeMultipleChoiceField, DynamicModelMultipleChoiceField
 from utilities.forms.fields import ContentTypeMultipleChoiceField, DynamicModelMultipleChoiceField
 from utilities.forms.widgets import DateTimePicker
 from utilities.forms.widgets import DateTimePicker
 from utilities.permissions import qs_filter_from_constraints
 from utilities.permissions import qs_filter_from_constraints
@@ -53,7 +52,7 @@ class UserConfigFormMetaclass(forms.models.ModelFormMetaclass):
         return super().__new__(mcs, name, bases, attrs)
         return super().__new__(mcs, name, bases, attrs)
 
 
 
 
-class UserConfigForm(BootstrapMixin, forms.ModelForm, metaclass=UserConfigFormMetaclass):
+class UserConfigForm(forms.ModelForm, metaclass=UserConfigFormMetaclass):
     fieldsets = (
     fieldsets = (
         (_('User Interface'), (
         (_('User Interface'), (
             'locale.language',
             'locale.language',
@@ -109,7 +108,7 @@ class UserConfigForm(BootstrapMixin, forms.ModelForm, metaclass=UserConfigFormMe
         ]
         ]
 
 
 
 
-class UserTokenForm(BootstrapMixin, forms.ModelForm):
+class UserTokenForm(forms.ModelForm):
     key = forms.CharField(
     key = forms.CharField(
         label=_('Key'),
         label=_('Key'),
         help_text=_(
         help_text=_(
@@ -167,7 +166,7 @@ class TokenForm(UserTokenForm):
         }
         }
 
 
 
 
-class UserForm(BootstrapMixin, forms.ModelForm):
+class UserForm(forms.ModelForm):
     password = forms.CharField(
     password = forms.CharField(
         label=_('Password'),
         label=_('Password'),
         widget=forms.PasswordInput(),
         widget=forms.PasswordInput(),
@@ -214,9 +213,7 @@ class UserForm(BootstrapMixin, forms.ModelForm):
 
 
             # Password fields are optional for existing Users
             # Password fields are optional for existing Users
             self.fields['password'].required = False
             self.fields['password'].required = False
-            self.fields['password'].widget.attrs.pop('required')
             self.fields['confirm_password'].required = False
             self.fields['confirm_password'].required = False
-            self.fields['confirm_password'].widget.attrs.pop('required')
 
 
     def save(self, *args, **kwargs):
     def save(self, *args, **kwargs):
         instance = super().save(*args, **kwargs)
         instance = super().save(*args, **kwargs)
@@ -238,7 +235,7 @@ class UserForm(BootstrapMixin, forms.ModelForm):
             raise forms.ValidationError(_("Passwords do not match! Please check your input and try again."))
             raise forms.ValidationError(_("Passwords do not match! Please check your input and try again."))
 
 
 
 
-class GroupForm(BootstrapMixin, forms.ModelForm):
+class GroupForm(forms.ModelForm):
     users = DynamicModelMultipleChoiceField(
     users = DynamicModelMultipleChoiceField(
         label=_('Users'),
         label=_('Users'),
         required=False,
         required=False,
@@ -281,7 +278,7 @@ class GroupForm(BootstrapMixin, forms.ModelForm):
         return instance
         return instance
 
 
 
 
-class ObjectPermissionForm(BootstrapMixin, forms.ModelForm):
+class ObjectPermissionForm(forms.ModelForm):
     object_types = ContentTypeMultipleChoiceField(
     object_types = ContentTypeMultipleChoiceField(
         label=_('Object types'),
         label=_('Object types'),
         queryset=ContentType.objects.all(),
         queryset=ContentType.objects.all(),

+ 1 - 2
netbox/utilities/forms/bulk_import.py

@@ -10,10 +10,9 @@ from core.forms.mixins import SyncedDataMixin
 from utilities.choices import CSVDelimiterChoices, ImportFormatChoices, ImportMethodChoices
 from utilities.choices import CSVDelimiterChoices, ImportFormatChoices, ImportMethodChoices
 from utilities.constants import CSV_DELIMITERS
 from utilities.constants import CSV_DELIMITERS
 from utilities.forms.utils import parse_csv
 from utilities.forms.utils import parse_csv
-from .mixins import BootstrapMixin
 
 
 
 
-class BulkImportForm(BootstrapMixin, SyncedDataMixin, forms.Form):
+class BulkImportForm(SyncedDataMixin, forms.Form):
     import_method = forms.ChoiceField(
     import_method = forms.ChoiceField(
         choices=ImportMethodChoices,
         choices=ImportMethodChoices,
         required=False
         required=False

+ 5 - 6
netbox/utilities/forms/forms.py

@@ -2,7 +2,6 @@ import re
 
 
 from django import forms
 from django import forms
 from django.utils.translation import gettext as _
 from django.utils.translation import gettext as _
-from .mixins import BootstrapMixin
 
 
 __all__ = (
 __all__ = (
     'BulkEditForm',
     'BulkEditForm',
@@ -14,7 +13,7 @@ __all__ = (
 )
 )
 
 
 
 
-class ConfirmationForm(BootstrapMixin, forms.Form):
+class ConfirmationForm(forms.Form):
     """
     """
     A generic confirmation form. The form is not valid unless the `confirm` field is checked.
     A generic confirmation form. The form is not valid unless the `confirm` field is checked.
     """
     """
@@ -29,14 +28,14 @@ class ConfirmationForm(BootstrapMixin, forms.Form):
     )
     )
 
 
 
 
-class BulkEditForm(BootstrapMixin, forms.Form):
+class BulkEditForm(forms.Form):
     """
     """
     Provides bulk edit support for objects.
     Provides bulk edit support for objects.
     """
     """
     nullable_fields = ()
     nullable_fields = ()
 
 
 
 
-class BulkRenameForm(BootstrapMixin, forms.Form):
+class BulkRenameForm(forms.Form):
     """
     """
     An extendable form to be used for renaming objects in bulk.
     An extendable form to be used for renaming objects in bulk.
     """
     """
@@ -90,7 +89,7 @@ class CSVModelForm(forms.ModelForm):
         return super().clean()
         return super().clean()
 
 
 
 
-class FilterForm(BootstrapMixin, forms.Form):
+class FilterForm(forms.Form):
     """
     """
     Base Form class for FilterSet forms.
     Base Form class for FilterSet forms.
     """
     """
@@ -100,7 +99,7 @@ class FilterForm(BootstrapMixin, forms.Form):
     )
     )
 
 
 
 
-class TableConfigForm(BootstrapMixin, forms.Form):
+class TableConfigForm(forms.Form):
     """
     """
     Form for configuring user's table preferences.
     Form for configuring user's table preferences.
     """
     """

+ 0 - 57
netbox/utilities/forms/mixins.py

@@ -3,68 +3,11 @@ import time
 from django import forms
 from django import forms
 from django.utils.translation import gettext_lazy as _
 from django.utils.translation import gettext_lazy as _
 
 
-from .widgets import APISelect, APISelectMultiple, ClearableFileInput
-
 __all__ = (
 __all__ = (
-    'BootstrapMixin',
     'CheckLastUpdatedMixin',
     'CheckLastUpdatedMixin',
 )
 )
 
 
 
 
-class BootstrapMixin:
-    """
-    Add the base Bootstrap CSS classes to form elements.
-    """
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args, **kwargs)
-
-        exempt_widgets = [
-            forms.FileInput,
-            forms.RadioSelect,
-            APISelect,
-            APISelectMultiple,
-            ClearableFileInput,
-        ]
-
-        for field_name, field in self.fields.items():
-            css = field.widget.attrs.get('class', '')
-
-            if field.widget.__class__ in exempt_widgets:
-                continue
-
-            elif isinstance(field.widget, forms.CheckboxInput):
-                field.widget.attrs['class'] = f'{css} form-check-input'
-
-            elif isinstance(field.widget, forms.SelectMultiple) and 'size' in field.widget.attrs:
-                # Use native Bootstrap class for multi-line <select> widgets
-                field.widget.attrs['class'] = f'{css} form-select form-select-sm'
-
-            elif isinstance(field.widget, (forms.Select, forms.SelectMultiple)):
-                field.widget.attrs['class'] = f'{css} netbox-static-select'
-
-            else:
-                field.widget.attrs['class'] = f'{css} form-control'
-
-            if field.required and not isinstance(field.widget, forms.FileInput):
-                field.widget.attrs['required'] = 'required'
-
-            if 'placeholder' not in field.widget.attrs and field.label is not None:
-                field.widget.attrs['placeholder'] = field.label
-
-    def is_valid(self):
-        is_valid = super().is_valid()
-
-        # Apply is-invalid CSS class to fields with errors
-        if not is_valid:
-            for field_name in self.errors:
-                # Ignore e.g. __all__
-                if field := self.fields.get(field_name):
-                    css = field.widget.attrs.get('class', '')
-                    field.widget.attrs['class'] = f'{css} is-invalid'
-
-        return is_valid
-
-
 class CheckLastUpdatedMixin(forms.Form):
 class CheckLastUpdatedMixin(forms.Form):
     """
     """
     Checks whether the object being saved has been updated since the form was initialized. If so, validation fails.
     Checks whether the object being saved has been updated since the form was initialized. If so, validation fails.

+ 2 - 2
netbox/virtualization/forms/bulk_create.py

@@ -1,7 +1,7 @@
 from django import forms
 from django import forms
 from django.utils.translation import gettext_lazy as _
 from django.utils.translation import gettext_lazy as _
 
 
-from utilities.forms import BootstrapMixin, form_from_model
+from utilities.forms import form_from_model
 from utilities.forms.fields import ExpandableNameField
 from utilities.forms.fields import ExpandableNameField
 from virtualization.models import VirtualDisk, VMInterface, VirtualMachine
 from virtualization.models import VirtualDisk, VMInterface, VirtualMachine
 
 
@@ -11,7 +11,7 @@ __all__ = (
 )
 )
 
 
 
 
-class VirtualMachineBulkAddComponentForm(BootstrapMixin, forms.Form):
+class VirtualMachineBulkAddComponentForm(forms.Form):
     pk = forms.ModelMultipleChoiceField(
     pk = forms.ModelMultipleChoiceField(
         queryset=VirtualMachine.objects.all(),
         queryset=VirtualMachine.objects.all(),
         widget=forms.MultipleHiddenInput()
         widget=forms.MultipleHiddenInput()

+ 2 - 2
netbox/virtualization/forms/model_forms.py

@@ -9,7 +9,7 @@ from extras.models import ConfigTemplate
 from ipam.models import IPAddress, VLAN, VLANGroup, VRF
 from ipam.models import IPAddress, VLAN, VLANGroup, VRF
 from netbox.forms import NetBoxModelForm
 from netbox.forms import NetBoxModelForm
 from tenancy.forms import TenancyForm
 from tenancy.forms import TenancyForm
-from utilities.forms import BootstrapMixin, ConfirmationForm
+from utilities.forms import ConfirmationForm
 from utilities.forms.fields import (
 from utilities.forms.fields import (
     CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, JSONField, SlugField,
     CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, JSONField, SlugField,
 )
 )
@@ -90,7 +90,7 @@ class ClusterForm(TenancyForm, NetBoxModelForm):
         )
         )
 
 
 
 
-class ClusterAddDevicesForm(BootstrapMixin, forms.Form):
+class ClusterAddDevicesForm(forms.Form):
     region = DynamicModelChoiceField(
     region = DynamicModelChoiceField(
         label=_('Region'),
         label=_('Region'),
         queryset=Region.objects.all(),
         queryset=Region.objects.all(),

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است