mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
* 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:
@ -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 login as auth_login, logout as auth_logout, update_session_auth_hash
|
||||||
from django.contrib.auth import 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)
|
||||||
|
@ -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(),
|
||||||
|
@ -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.
|
||||||
"""
|
"""
|
||||||
|
@ -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()
|
||||||
|
@ -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'),
|
||||||
|
@ -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):
|
class ConsolePortTemplateImportForm(forms.ModelForm):
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class ConsolePortTemplateImportForm(ComponentTemplateImportForm):
|
|
||||||
|
|
||||||
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(),
|
||||||
|
@ -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
|
||||||
)
|
)
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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(),
|
||||||
|
@ -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,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')
|
||||||
)
|
)
|
||||||
|
@ -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()
|
||||||
|
@ -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(
|
||||||
|
@ -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.
|
||||||
|
2
netbox/project-static/dist/netbox.css
vendored
2
netbox/project-static/dist/netbox.css
vendored
File diff suppressed because one or more lines are too long
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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
netbox/templates/django/forms/widgets/attrs.html
Normal file
2
netbox/templates/django/forms/widgets/attrs.html
Normal file
@ -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.
|
_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 %}">
|
||||||
|
@ -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
netbox/templates/django/forms/widgets/input.html
Normal file
1
netbox/templates/django/forms/widgets/input.html
Normal file
@ -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
netbox/templates/django/forms/widgets/select.html
Normal file
5
netbox/templates/django/forms/widgets/select.html
Normal file
@ -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
netbox/templates/django/forms/widgets/textarea.html
Normal file
2
netbox/templates/django/forms/widgets/textarea.html
Normal file
@ -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_edit import *
|
||||||
from .bulk_import import *
|
from .bulk_import import *
|
||||||
from .filtersets 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.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
|
||||||
|
@ -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(),
|
||||||
|
@ -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
|
||||||
|
@ -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.
|
||||||
"""
|
"""
|
||||||
|
@ -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.
|
||||||
|
@ -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()
|
||||||
|
@ -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(),
|
||||||
|
Reference in New Issue
Block a user