* 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
This commit is contained in:
parent
874685fd6f
commit
da085e60c2
|
@ -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)
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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.
|
||||
"""
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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')
|
||||
)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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.
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -37,16 +37,6 @@ $spacing-s: $input-padding-x;
|
|||
.ss-main {
|
||||
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-multi-selected {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,3 +16,12 @@ form.object-edit {
|
|||
content: '\f06C4';
|
||||
}
|
||||
}
|
||||
|
||||
// Set red border on form fields inside a row with .has-errors
|
||||
.has-errors {
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
border: 1px solid $red;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 %}
|
|
@ -4,4 +4,4 @@
|
|||
_selected_action to avoid breaking the admin UI.
|
||||
{% endcomment %}
|
||||
{% 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 %}">
|
||||
|
|
|
@ -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 %}">
|
|
@ -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 %}">
|
|
@ -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>
|
|
@ -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>
|
|
@ -1,4 +1,3 @@
|
|||
from .authentication import *
|
||||
from .bulk_edit import *
|
||||
from .bulk_import import *
|
||||
from .filtersets import *
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
"""
|
||||
|
|
|
@ -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 <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):
|
||||
"""
|
||||
Checks whether the object being saved has been updated since the form was initialized. If so, validation fails.
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from django import forms
|
||||
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 virtualization.models import VirtualDisk, VMInterface, VirtualMachine
|
||||
|
||||
|
@ -11,7 +11,7 @@ __all__ = (
|
|||
)
|
||||
|
||||
|
||||
class VirtualMachineBulkAddComponentForm(BootstrapMixin, forms.Form):
|
||||
class VirtualMachineBulkAddComponentForm(forms.Form):
|
||||
pk = forms.ModelMultipleChoiceField(
|
||||
queryset=VirtualMachine.objects.all(),
|
||||
widget=forms.MultipleHiddenInput()
|
||||
|
|
|
@ -9,7 +9,7 @@ from extras.models import ConfigTemplate
|
|||
from ipam.models import IPAddress, VLAN, VLANGroup, VRF
|
||||
from netbox.forms import NetBoxModelForm
|
||||
from tenancy.forms import TenancyForm
|
||||
from utilities.forms import BootstrapMixin, ConfirmationForm
|
||||
from utilities.forms import ConfirmationForm
|
||||
from utilities.forms.fields import (
|
||||
CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, JSONField, SlugField,
|
||||
)
|
||||
|
@ -90,7 +90,7 @@ class ClusterForm(TenancyForm, NetBoxModelForm):
|
|||
)
|
||||
|
||||
|
||||
class ClusterAddDevicesForm(BootstrapMixin, forms.Form):
|
||||
class ClusterAddDevicesForm(forms.Form):
|
||||
region = DynamicModelChoiceField(
|
||||
label=_('Region'),
|
||||
queryset=Region.objects.all(),
|
||||
|
|
Loading…
Reference in New Issue