From 5272e9e2dbf2f80b2d626bf3335e7b00531b1a57 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 19 Jan 2024 14:02:33 -0500 Subject: [PATCH] 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 --- netbox/account/views.py | 12 ++-- netbox/circuits/forms/bulk_import.py | 3 +- netbox/core/forms/model_forms.py | 4 +- netbox/dcim/forms/bulk_create.py | 4 +- netbox/dcim/forms/model_forms.py | 8 +-- netbox/dcim/forms/object_import.py | 25 +++----- netbox/extras/dashboard/forms.py | 4 +- netbox/extras/dashboard/widgets.py | 3 +- netbox/extras/forms/model_forms.py | 22 +++---- netbox/extras/forms/reports.py | 3 +- netbox/extras/forms/scripts.py | 3 +- netbox/ipam/forms/bulk_create.py | 3 +- netbox/ipam/forms/model_forms.py | 7 +-- netbox/netbox/forms/__init__.py | 3 +- netbox/netbox/forms/base.py | 8 +-- netbox/project-static/dist/netbox.css | Bin 546330 -> 546301 bytes .../styles/overrides/_slim-select.scss | 18 +++--- .../styles/transitional/_forms.scss | 9 +++ .../templates/django/forms/widgets/attrs.html | 2 + .../django/forms/widgets/checkbox.html | 2 +- .../forms/widgets/clearable_file_input.html | 5 ++ .../templates/django/forms/widgets/input.html | 1 + .../django/forms/widgets/select.html | 5 ++ .../django/forms/widgets/textarea.html | 2 + netbox/users/forms/__init__.py | 1 - netbox/users/forms/authentication.py | 25 -------- netbox/users/forms/bulk_edit.py | 6 +- netbox/users/forms/model_forms.py | 13 ++-- netbox/utilities/forms/bulk_import.py | 3 +- netbox/utilities/forms/forms.py | 11 ++-- netbox/utilities/forms/mixins.py | 57 ------------------ netbox/virtualization/forms/bulk_create.py | 4 +- netbox/virtualization/forms/model_forms.py | 4 +- 33 files changed, 101 insertions(+), 179 deletions(-) create mode 100644 netbox/templates/django/forms/widgets/attrs.html create mode 100644 netbox/templates/django/forms/widgets/clearable_file_input.html create mode 100644 netbox/templates/django/forms/widgets/input.html create mode 100644 netbox/templates/django/forms/widgets/select.html create mode 100644 netbox/templates/django/forms/widgets/textarea.html delete mode 100644 netbox/users/forms/authentication.py diff --git a/netbox/account/views.py b/netbox/account/views.py index 3dbba9b29..40ce78039 100644 --- a/netbox/account/views.py +++ b/netbox/account/views.py @@ -2,8 +2,8 @@ import logging from django.conf import settings 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.models import update_last_login from django.contrib.auth.signals import user_logged_in @@ -72,7 +72,7 @@ class LoginView(View): return auth_backends def get(self, request): - form = forms.LoginForm(request) + form = AuthenticationForm(request) if request.user.is_authenticated: logger = logging.getLogger('netbox.auth.login') @@ -85,7 +85,7 @@ class LoginView(View): def post(self, request): logger = logging.getLogger('netbox.auth.login') - form = forms.LoginForm(request, data=request.POST) + form = AuthenticationForm(request, data=request.POST) if form.is_valid(): 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.") return redirect('account:profile') - form = forms.PasswordChangeForm(user=request.user) + form = PasswordChangeForm(user=request.user) return render(request, self.template_name, { 'form': form, @@ -228,7 +228,7 @@ class ChangePasswordView(LoginRequiredMixin, View): }) 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(): form.save() update_session_auth_hash(request, form.user) diff --git a/netbox/circuits/forms/bulk_import.py b/netbox/circuits/forms/bulk_import.py index 0c30e3cda..8127d5bcb 100644 --- a/netbox/circuits/forms/bulk_import.py +++ b/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 netbox.forms import NetBoxModelImportForm from tenancy.models import Tenant -from utilities.forms import BootstrapMixin from utilities.forms.fields import CSVChoiceField, CSVModelChoiceField, SlugField __all__ = ( @@ -112,7 +111,7 @@ class CircuitImportForm(NetBoxModelImportForm): ] -class CircuitTerminationImportForm(BootstrapMixin, forms.ModelForm): +class CircuitTerminationImportForm(forms.ModelForm): site = CSVModelChoiceField( label=_('Site'), queryset=Site.objects.all(), diff --git a/netbox/core/forms/model_forms.py b/netbox/core/forms/model_forms.py index 652728734..52fa3a608 100644 --- a/netbox/core/forms/model_forms.py +++ b/netbox/core/forms/model_forms.py @@ -11,7 +11,7 @@ from netbox.config import get_config, PARAMS from netbox.forms import NetBoxModelForm from netbox.registry import registry 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.widgets import HTMXSelect @@ -138,7 +138,7 @@ class ConfigFormMetaclass(forms.models.ModelFormMetaclass): 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. """ diff --git a/netbox/dcim/forms/bulk_create.py b/netbox/dcim/forms/bulk_create.py index 2a84a9a51..2939b986e 100644 --- a/netbox/dcim/forms/bulk_create.py +++ b/netbox/dcim/forms/bulk_create.py @@ -4,7 +4,7 @@ from django.utils.translation import gettext_lazy as _ from dcim.models import * from extras.models import Tag 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 .object_create import ComponentCreateForm @@ -26,7 +26,7 @@ __all__ = ( # Device components # -class DeviceBulkAddComponentForm(BootstrapMixin, CustomFieldsMixin, ComponentCreateForm): +class DeviceBulkAddComponentForm(CustomFieldsMixin, ComponentCreateForm): pk = forms.ModelMultipleChoiceField( queryset=Device.objects.all(), widget=forms.MultipleHiddenInput() diff --git a/netbox/dcim/forms/model_forms.py b/netbox/dcim/forms/model_forms.py index da3a2bea4..fdb5f1d5a 100644 --- a/netbox/dcim/forms/model_forms.py +++ b/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 netbox.forms import NetBoxModelForm 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 ( CommentField, ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, JSONField, NumericArrayField, SlugField, @@ -748,7 +748,7 @@ class DeviceVCMembershipForm(forms.ModelForm): return vc_position -class VCMemberSelectForm(BootstrapMixin, forms.Form): +class VCMemberSelectForm(forms.Form): device = DynamicModelChoiceField( label=_('Device'), queryset=Device.objects.all(), @@ -771,7 +771,7 @@ class VCMemberSelectForm(BootstrapMixin, forms.Form): # Device component templates # -class ComponentTemplateForm(BootstrapMixin, forms.ModelForm): +class ComponentTemplateForm(forms.ModelForm): device_type = DynamicModelChoiceField( label=_('Device type'), queryset=DeviceType.objects.all() @@ -1272,7 +1272,7 @@ class DeviceBayForm(DeviceComponentForm): ] -class PopulateDeviceBayForm(BootstrapMixin, forms.Form): +class PopulateDeviceBayForm(forms.Form): installed_device = forms.ModelChoiceField( queryset=Device.objects.all(), label=_('Child Device'), diff --git a/netbox/dcim/forms/object_import.py b/netbox/dcim/forms/object_import.py index bab8876da..d46ef83ad 100644 --- a/netbox/dcim/forms/object_import.py +++ b/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.models import * -from utilities.forms import BootstrapMixin from wireless.choices import WirelessRoleChoices __all__ = ( @@ -24,11 +23,7 @@ __all__ = ( # Component template import forms # -class ComponentTemplateImportForm(BootstrapMixin, forms.ModelForm): - pass - - -class ConsolePortTemplateImportForm(ComponentTemplateImportForm): +class ConsolePortTemplateImportForm(forms.ModelForm): class Meta: model = ConsolePortTemplate @@ -37,7 +32,7 @@ class ConsolePortTemplateImportForm(ComponentTemplateImportForm): ] -class ConsoleServerPortTemplateImportForm(ComponentTemplateImportForm): +class ConsoleServerPortTemplateImportForm(forms.ModelForm): class Meta: model = ConsoleServerPortTemplate @@ -46,7 +41,7 @@ class ConsoleServerPortTemplateImportForm(ComponentTemplateImportForm): ] -class PowerPortTemplateImportForm(ComponentTemplateImportForm): +class PowerPortTemplateImportForm(forms.ModelForm): class Meta: model = PowerPortTemplate @@ -55,7 +50,7 @@ class PowerPortTemplateImportForm(ComponentTemplateImportForm): ] -class PowerOutletTemplateImportForm(ComponentTemplateImportForm): +class PowerOutletTemplateImportForm(forms.ModelForm): power_port = forms.ModelChoiceField( label=_('Power port'), queryset=PowerPortTemplate.objects.all(), @@ -84,7 +79,7 @@ class PowerOutletTemplateImportForm(ComponentTemplateImportForm): return module_type -class InterfaceTemplateImportForm(ComponentTemplateImportForm): +class InterfaceTemplateImportForm(forms.ModelForm): type = forms.ChoiceField( label=_('Type'), choices=InterfaceTypeChoices.CHOICES @@ -113,7 +108,7 @@ class InterfaceTemplateImportForm(ComponentTemplateImportForm): ] -class FrontPortTemplateImportForm(ComponentTemplateImportForm): +class FrontPortTemplateImportForm(forms.ModelForm): type = forms.ChoiceField( label=_('Type'), choices=PortTypeChoices.CHOICES @@ -145,7 +140,7 @@ class FrontPortTemplateImportForm(ComponentTemplateImportForm): ] -class RearPortTemplateImportForm(ComponentTemplateImportForm): +class RearPortTemplateImportForm(forms.ModelForm): type = forms.ChoiceField( label=_('Type'), choices=PortTypeChoices.CHOICES @@ -158,7 +153,7 @@ class RearPortTemplateImportForm(ComponentTemplateImportForm): ] -class ModuleBayTemplateImportForm(ComponentTemplateImportForm): +class ModuleBayTemplateImportForm(forms.ModelForm): class Meta: model = ModuleBayTemplate @@ -167,7 +162,7 @@ class ModuleBayTemplateImportForm(ComponentTemplateImportForm): ] -class DeviceBayTemplateImportForm(ComponentTemplateImportForm): +class DeviceBayTemplateImportForm(forms.ModelForm): class Meta: model = DeviceBayTemplate @@ -176,7 +171,7 @@ class DeviceBayTemplateImportForm(ComponentTemplateImportForm): ] -class InventoryItemTemplateImportForm(ComponentTemplateImportForm): +class InventoryItemTemplateImportForm(forms.ModelForm): parent = forms.ModelChoiceField( label=_('Parent'), queryset=InventoryItemTemplate.objects.all(), diff --git a/netbox/extras/dashboard/forms.py b/netbox/extras/dashboard/forms.py index ab708228c..fd3492726 100644 --- a/netbox/extras/dashboard/forms.py +++ b/netbox/extras/dashboard/forms.py @@ -4,7 +4,7 @@ from django.utils.translation import gettext as _ from extras.choices import DashboardWidgetColorChoices from netbox.registry import registry -from utilities.forms import BootstrapMixin, add_blank_choice +from utilities.forms import add_blank_choice __all__ = ( 'DashboardWidgetAddForm', @@ -16,7 +16,7 @@ def get_widget_choices(): return registry['widgets'].items() -class DashboardWidgetForm(BootstrapMixin, forms.Form): +class DashboardWidgetForm(forms.Form): title = forms.CharField( required=False ) diff --git a/netbox/extras/dashboard/widgets.py b/netbox/extras/dashboard/widgets.py index 8cfbb4c61..7503b7077 100644 --- a/netbox/extras/dashboard/widgets.py +++ b/netbox/extras/dashboard/widgets.py @@ -15,7 +15,6 @@ from django.utils.translation import gettext as _ from core.models import ContentType from extras.choices import BookmarkOrderingChoices from utilities.choices import ButtonColorChoices -from utilities.forms import BootstrapMixin from utilities.permissions import get_permission_for_model from utilities.templatetags.builtins.filters import render_markdown 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 -class WidgetConfigForm(BootstrapMixin, forms.Form): +class WidgetConfigForm(forms.Form): pass diff --git a/netbox/extras/forms/model_forms.py b/netbox/extras/forms/model_forms.py index 346225c8a..5495bc87e 100644 --- a/netbox/extras/forms/model_forms.py +++ b/netbox/extras/forms/model_forms.py @@ -13,7 +13,7 @@ from extras.choices import * from extras.models import * from netbox.forms import NetBoxModelForm 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 ( CommentField, ContentTypeChoiceField, ContentTypeMultipleChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, JSONField, SlugField, @@ -38,7 +38,7 @@ __all__ = ( ) -class CustomFieldForm(BootstrapMixin, forms.ModelForm): +class CustomFieldForm(forms.ModelForm): content_types = ContentTypeMultipleChoiceField( label=_('Content types'), queryset=ContentType.objects.with_feature('custom_fields') @@ -83,7 +83,7 @@ class CustomFieldForm(BootstrapMixin, forms.ModelForm): self.fields['type'].disabled = True -class CustomFieldChoiceSetForm(BootstrapMixin, forms.ModelForm): +class CustomFieldChoiceSetForm(forms.ModelForm): extra_choices = forms.CharField( widget=ChoicesWidget(), required=False, @@ -122,7 +122,7 @@ class CustomFieldChoiceSetForm(BootstrapMixin, forms.ModelForm): return data -class CustomLinkForm(BootstrapMixin, forms.ModelForm): +class CustomLinkForm(forms.ModelForm): content_types = ContentTypeMultipleChoiceField( label=_('Content types'), 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( label=_('Content types'), queryset=ContentType.objects.with_feature('export_templates') @@ -189,7 +189,7 @@ class ExportTemplateForm(BootstrapMixin, SyncedDataMixin, forms.ModelForm): return self.cleaned_data -class SavedFilterForm(BootstrapMixin, forms.ModelForm): +class SavedFilterForm(forms.ModelForm): slug = SlugField() content_types = ContentTypeMultipleChoiceField( label=_('Content types'), @@ -216,7 +216,7 @@ class SavedFilterForm(BootstrapMixin, forms.ModelForm): super().__init__(*args, initial=initial, **kwargs) -class BookmarkForm(BootstrapMixin, forms.ModelForm): +class BookmarkForm(forms.ModelForm): object_type = ContentTypeChoiceField( label=_('Object type'), queryset=ContentType.objects.with_feature('bookmarks') @@ -367,7 +367,7 @@ class EventRuleForm(NetBoxModelForm): return super().save(*args, **kwargs) -class TagForm(BootstrapMixin, forms.ModelForm): +class TagForm(forms.ModelForm): slug = SlugField() object_types = ContentTypeMultipleChoiceField( 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( label=_('Regions'), queryset=Region.objects.all(), @@ -497,7 +497,7 @@ class ConfigContextForm(BootstrapMixin, SyncedDataMixin, forms.ModelForm): return self.cleaned_data -class ConfigTemplateForm(BootstrapMixin, SyncedDataMixin, forms.ModelForm): +class ConfigTemplateForm(SyncedDataMixin, forms.ModelForm): tags = DynamicModelMultipleChoiceField( label=_('Tags'), queryset=Tag.objects.all(), @@ -541,7 +541,7 @@ class ConfigTemplateForm(BootstrapMixin, SyncedDataMixin, forms.ModelForm): return self.cleaned_data -class ImageAttachmentForm(BootstrapMixin, forms.ModelForm): +class ImageAttachmentForm(forms.ModelForm): class Meta: model = ImageAttachment diff --git a/netbox/extras/forms/reports.py b/netbox/extras/forms/reports.py index 0ff7d1509..ad37eb744 100644 --- a/netbox/extras/forms/reports.py +++ b/netbox/extras/forms/reports.py @@ -2,7 +2,6 @@ from django import forms from django.utils.translation import gettext_lazy as _ from extras.choices import DurationChoices -from utilities.forms import BootstrapMixin from utilities.forms.widgets import DateTimePicker, NumberWithOptions from utilities.utils import local_now @@ -11,7 +10,7 @@ __all__ = ( ) -class ReportForm(BootstrapMixin, forms.Form): +class ReportForm(forms.Form): schedule_at = forms.DateTimeField( required=False, widget=DateTimePicker(), diff --git a/netbox/extras/forms/scripts.py b/netbox/extras/forms/scripts.py index c555747ae..f67ad3e75 100644 --- a/netbox/extras/forms/scripts.py +++ b/netbox/extras/forms/scripts.py @@ -2,7 +2,6 @@ from django import forms from django.utils.translation import gettext_lazy as _ from extras.choices import DurationChoices -from utilities.forms import BootstrapMixin from utilities.forms.widgets import DateTimePicker, NumberWithOptions from utilities.utils import local_now @@ -11,7 +10,7 @@ __all__ = ( ) -class ScriptForm(BootstrapMixin, forms.Form): +class ScriptForm(forms.Form): _commit = forms.BooleanField( required=False, initial=True, diff --git a/netbox/ipam/forms/bulk_create.py b/netbox/ipam/forms/bulk_create.py index ea553c655..856476786 100644 --- a/netbox/ipam/forms/bulk_create.py +++ b/netbox/ipam/forms/bulk_create.py @@ -1,7 +1,6 @@ from django import forms from django.utils.translation import gettext_lazy as _ -from utilities.forms import BootstrapMixin from utilities.forms.fields import ExpandableIPAddressField __all__ = ( @@ -9,7 +8,7 @@ __all__ = ( ) -class IPAddressBulkCreateForm(BootstrapMixin, forms.Form): +class IPAddressBulkCreateForm(forms.Form): pattern = ExpandableIPAddressField( label=_('Address pattern') ) diff --git a/netbox/ipam/forms/model_forms.py b/netbox/ipam/forms/model_forms.py index 6c445ef27..0e2250d2d 100644 --- a/netbox/ipam/forms/model_forms.py +++ b/netbox/ipam/forms/model_forms.py @@ -11,7 +11,7 @@ from ipam.models import * from netbox.forms import NetBoxModelForm from tenancy.forms import TenancyForm 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 ( CommentField, ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, NumericArrayField, SlugField, @@ -419,7 +419,7 @@ class IPAddressBulkAddForm(TenancyForm, NetBoxModelForm): ] -class IPAddressAssignForm(BootstrapMixin, forms.Form): +class IPAddressAssignForm(forms.Form): vrf_id = DynamicModelChoiceField( queryset=VRF.objects.all(), required=False, @@ -504,7 +504,7 @@ class FHRPGroupForm(NetBoxModelForm): }) -class FHRPGroupAssignmentForm(BootstrapMixin, forms.ModelForm): +class FHRPGroupAssignmentForm(forms.ModelForm): group = DynamicModelChoiceField( label=_('Group'), queryset=FHRPGroup.objects.all() @@ -738,7 +738,6 @@ class ServiceCreateForm(ServiceForm): # Fields which may be populated from a ServiceTemplate are not required for field in ('name', 'protocol', 'ports'): self.fields[field].required = False - del self.fields[field].widget.attrs['required'] def clean(self): super().clean() diff --git a/netbox/netbox/forms/__init__.py b/netbox/netbox/forms/__init__.py index 65460ebf1..fa82689a5 100644 --- a/netbox/netbox/forms/__init__.py +++ b/netbox/netbox/forms/__init__.py @@ -5,7 +5,6 @@ from django.utils.translation import gettext as _ from netbox.search import LookupTypes from netbox.search.backends import search_backend -from utilities.forms import BootstrapMixin from .base import * @@ -18,7 +17,7 @@ LOOKUP_CHOICES = ( ) -class SearchForm(BootstrapMixin, forms.Form): +class SearchForm(forms.Form): q = forms.CharField( label=_('Search'), widget=forms.TextInput( diff --git a/netbox/netbox/forms/base.py b/netbox/netbox/forms/base.py index 0b0e2036e..7e1eaa80c 100644 --- a/netbox/netbox/forms/base.py +++ b/netbox/netbox/forms/base.py @@ -7,7 +7,7 @@ from extras.choices import * from extras.models import CustomField, Tag from utilities.forms import CSVModelForm 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 __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. @@ -96,7 +96,7 @@ class NetBoxModelImportForm(CSVModelForm, NetBoxModelForm): 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 fields and adding/removing tags. @@ -146,7 +146,7 @@ class NetBoxModelBulkEditForm(BootstrapMixin, CustomFieldsMixin, forms.Form): 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 corresponding FilterSet *must* provide a `q` filter. diff --git a/netbox/project-static/dist/netbox.css b/netbox/project-static/dist/netbox.css index 2cf01fdfa5a34f3c82f4fd1a07c6f1be70e35a98..5fc874a091b256936aca5e453fa95671c8a3d8b3 100644 GIT binary patch delta 197 zcmbQWM)B`z#fBEf7N!>F7M2#)7Pc1lEgUbbrZ?Dfh-^P#&B4pft(TElteaX?lwVXl z-9d>%ghvNi#L?CCbK}^~6o$~GkeOFdT7s-ep*S@sH5pZ? zB(u_!gMIw`*>CAG-Pu%JSrI6o&dML{{m%-GV{vR1DkF&$)s@bm;%4!+3_vxK(a J_T;eV0RWP>MJxaS delta 173 zcmeynT5;AI#fBEf7N!>F7M2#)7Pc1lEgUbbxb-rNbu;tI5_2+BraLHch)n-u#leXp zs%Qrk^>E`571S#(*3C`K%me8`)4_=lgQ@3ZGfGP`HZhsL(3V4XJJ48OZl;2w=?|x~ i%5Sf8{% endif %} -{% include "django/forms/widgets/input.html" %} + diff --git a/netbox/templates/django/forms/widgets/clearable_file_input.html b/netbox/templates/django/forms/widgets/clearable_file_input.html new file mode 100644 index 000000000..a84b941fa --- /dev/null +++ b/netbox/templates/django/forms/widgets/clearable_file_input.html @@ -0,0 +1,5 @@ +{% if widget.is_initial %}{{ widget.initial_text }}: {{ widget.value }}{% if not widget.required %} + +{% endif %}
+{{ widget.input_text }}:{% endif %} + \ No newline at end of file diff --git a/netbox/templates/django/forms/widgets/input.html b/netbox/templates/django/forms/widgets/input.html new file mode 100644 index 000000000..dd6058328 --- /dev/null +++ b/netbox/templates/django/forms/widgets/input.html @@ -0,0 +1 @@ + diff --git a/netbox/templates/django/forms/widgets/select.html b/netbox/templates/django/forms/widgets/select.html new file mode 100644 index 000000000..0e87394e3 --- /dev/null +++ b/netbox/templates/django/forms/widgets/select.html @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/netbox/templates/django/forms/widgets/textarea.html b/netbox/templates/django/forms/widgets/textarea.html new file mode 100644 index 000000000..aed4dd3b0 --- /dev/null +++ b/netbox/templates/django/forms/widgets/textarea.html @@ -0,0 +1,2 @@ + \ No newline at end of file diff --git a/netbox/users/forms/__init__.py b/netbox/users/forms/__init__.py index a545c3add..1499f98b2 100644 --- a/netbox/users/forms/__init__.py +++ b/netbox/users/forms/__init__.py @@ -1,4 +1,3 @@ -from .authentication import * from .bulk_edit import * from .bulk_import import * from .filtersets import * diff --git a/netbox/users/forms/authentication.py b/netbox/users/forms/authentication.py deleted file mode 100644 index 2b540b752..000000000 --- a/netbox/users/forms/authentication.py +++ /dev/null @@ -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 diff --git a/netbox/users/forms/bulk_edit.py b/netbox/users/forms/bulk_edit.py index 0e29109a4..f88f71bf0 100644 --- a/netbox/users/forms/bulk_edit.py +++ b/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.validators import prefix_validator from users.models import * -from utilities.forms import BootstrapMixin, BulkEditForm +from utilities.forms import BulkEditForm from utilities.forms.widgets import BulkEditNullBooleanSelect, DateTimePicker __all__ = ( @@ -15,7 +15,7 @@ __all__ = ( ) -class UserBulkEditForm(BootstrapMixin, forms.Form): +class UserBulkEditForm(forms.Form): pk = forms.ModelMultipleChoiceField( queryset=NetBoxUser.objects.all(), widget=forms.MultipleHiddenInput @@ -53,7 +53,7 @@ class UserBulkEditForm(BootstrapMixin, forms.Form): nullable_fields = ('first_name', 'last_name') -class ObjectPermissionBulkEditForm(BootstrapMixin, forms.Form): +class ObjectPermissionBulkEditForm(forms.Form): pk = forms.ModelMultipleChoiceField( queryset=ObjectPermission.objects.all(), widget=forms.MultipleHiddenInput diff --git a/netbox/users/forms/model_forms.py b/netbox/users/forms/model_forms.py index 99320fa25..d5742587f 100644 --- a/netbox/users/forms/model_forms.py +++ b/netbox/users/forms/model_forms.py @@ -13,7 +13,6 @@ from ipam.validators import prefix_validator from netbox.preferences import PREFERENCES from users.constants import * from users.models import * -from utilities.forms import BootstrapMixin from utilities.forms.fields import ContentTypeMultipleChoiceField, DynamicModelMultipleChoiceField from utilities.forms.widgets import DateTimePicker from utilities.permissions import qs_filter_from_constraints @@ -53,7 +52,7 @@ class UserConfigFormMetaclass(forms.models.ModelFormMetaclass): return super().__new__(mcs, name, bases, attrs) -class UserConfigForm(BootstrapMixin, forms.ModelForm, metaclass=UserConfigFormMetaclass): +class UserConfigForm(forms.ModelForm, metaclass=UserConfigFormMetaclass): fieldsets = ( (_('User Interface'), ( '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( label=_('Key'), help_text=_( @@ -167,7 +166,7 @@ class TokenForm(UserTokenForm): } -class UserForm(BootstrapMixin, forms.ModelForm): +class UserForm(forms.ModelForm): password = forms.CharField( label=_('Password'), widget=forms.PasswordInput(), @@ -214,9 +213,7 @@ class UserForm(BootstrapMixin, forms.ModelForm): # Password fields are optional for existing Users self.fields['password'].required = False - self.fields['password'].widget.attrs.pop('required') self.fields['confirm_password'].required = False - self.fields['confirm_password'].widget.attrs.pop('required') def save(self, *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.")) -class GroupForm(BootstrapMixin, forms.ModelForm): +class GroupForm(forms.ModelForm): users = DynamicModelMultipleChoiceField( label=_('Users'), required=False, @@ -281,7 +278,7 @@ class GroupForm(BootstrapMixin, forms.ModelForm): return instance -class ObjectPermissionForm(BootstrapMixin, forms.ModelForm): +class ObjectPermissionForm(forms.ModelForm): object_types = ContentTypeMultipleChoiceField( label=_('Object types'), queryset=ContentType.objects.all(), diff --git a/netbox/utilities/forms/bulk_import.py b/netbox/utilities/forms/bulk_import.py index 57362d3dd..8ef1f6123 100644 --- a/netbox/utilities/forms/bulk_import.py +++ b/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.constants import CSV_DELIMITERS 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( choices=ImportMethodChoices, required=False diff --git a/netbox/utilities/forms/forms.py b/netbox/utilities/forms/forms.py index 54c9e41cb..9747de001 100644 --- a/netbox/utilities/forms/forms.py +++ b/netbox/utilities/forms/forms.py @@ -2,7 +2,6 @@ import re from django import forms from django.utils.translation import gettext as _ -from .mixins import BootstrapMixin __all__ = ( '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. """ @@ -29,14 +28,14 @@ class ConfirmationForm(BootstrapMixin, forms.Form): ) -class BulkEditForm(BootstrapMixin, forms.Form): +class BulkEditForm(forms.Form): """ Provides bulk edit support for objects. """ nullable_fields = () -class BulkRenameForm(BootstrapMixin, forms.Form): +class BulkRenameForm(forms.Form): """ An extendable form to be used for renaming objects in bulk. """ @@ -90,7 +89,7 @@ class CSVModelForm(forms.ModelForm): return super().clean() -class FilterForm(BootstrapMixin, forms.Form): +class FilterForm(forms.Form): """ 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. """ diff --git a/netbox/utilities/forms/mixins.py b/netbox/utilities/forms/mixins.py index 2d6c20fcc..e89fbb520 100644 --- a/netbox/utilities/forms/mixins.py +++ b/netbox/utilities/forms/mixins.py @@ -3,68 +3,11 @@ import time from django import forms from django.utils.translation import gettext_lazy as _ -from .widgets import APISelect, APISelectMultiple, ClearableFileInput - __all__ = ( - 'BootstrapMixin', '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