Clean up model forms

This commit is contained in:
Jeremy Stretch 2023-07-20 11:49:06 -04:00
parent d24330f412
commit b5362f059c
3 changed files with 81 additions and 66 deletions

View File

@ -1,3 +1,4 @@
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 *

View File

@ -0,0 +1,25 @@
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

View File

@ -1,50 +1,33 @@
from django import forms from django import forms
from django.conf import settings from django.conf import settings
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.auth.forms import AuthenticationForm, PasswordChangeForm as DjangoPasswordChangeForm, SetPasswordForm as DjangoPasswordSetForm
from django.contrib.auth.models import Group from django.contrib.auth.models import Group
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.contrib.postgres.forms import SimpleArrayField from django.contrib.postgres.forms import SimpleArrayField
from django.core.exceptions import FieldError from django.core.exceptions import FieldError
from django.urls import reverse
from django.utils.html import mark_safe from django.utils.html import mark_safe
from django.utils.translation import gettext_lazy as _ 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 netbox.preferences import PREFERENCES from netbox.preferences import PREFERENCES
from utilities.forms import BootstrapMixin
from utilities.forms.fields import ContentTypeMultipleChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField
from utilities.forms.widgets import DateTimePicker
from utilities.utils import flatten_dict
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.widgets import DateTimePicker
from utilities.permissions import qs_filter_from_constraints from utilities.permissions import qs_filter_from_constraints
from utilities.utils import flatten_dict
__all__ = ( __all__ = (
'GroupForm', 'GroupForm',
'LoginForm',
'ObjectPermissionForm', 'ObjectPermissionForm',
'PasswordChangeForm',
'PasswordSetForm',
'TokenForm', 'TokenForm',
'UserConfigForm', 'UserConfigForm',
'UserForm', 'UserForm',
) )
class LoginForm(BootstrapMixin, AuthenticationForm):
pass
class PasswordChangeForm(BootstrapMixin, DjangoPasswordChangeForm):
pass
class PasswordSetForm(BootstrapMixin, DjangoPasswordSetForm):
pass
class UserConfigFormMetaclass(forms.models.ModelFormMetaclass): class UserConfigFormMetaclass(forms.models.ModelFormMetaclass):
def __new__(mcs, name, bases, attrs): def __new__(mcs, name, bases, attrs):
@ -180,10 +163,10 @@ class UserForm(BootstrapMixin, forms.ModelForm):
) )
fieldsets = ( fieldsets = (
(_('User'), ('username', 'password', 'confirm_password', 'first_name', 'last_name', 'email', )), (_('User'), ('username', 'password', 'confirm_password', 'first_name', 'last_name', 'email')),
(_('Groups'), ('groups', )), (_('Groups'), ('groups', )),
(_('Status'), ('is_active', 'is_staff', 'is_superuser', )), (_('Status'), ('is_active', 'is_staff', 'is_superuser')),
(_('Permissions'), ('object_permissions', )), (_('Permissions'), ('object_permissions',)),
) )
class Meta: class Meta:
@ -196,41 +179,36 @@ class UserForm(BootstrapMixin, forms.ModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
# Adjust form fields depending if Add or Edit
if self.instance.pk: if self.instance.pk:
self.fields['object_permissions'].initial = self.instance.object_permissions.all().values_list('id', flat=True) # Populate assigned permissions
pw_field = self.fields['password'] self.fields['object_permissions'].initial = self.instance.object_permissions.values_list('id', flat=True)
pwc_field = self.fields['confirm_password']
pw_field.required = False # Password fields are optional for existing Users
pw_field.widget.attrs.pop('required') self.fields['password'].required = False
pw_field.help_text = _("Leave empty to keep the old password.") self.fields['password'].widget.attrs.pop('required')
pwc_field.required = False self.fields['confirm_password'].required = False
pwc_field.widget.attrs.pop('required') self.fields['confirm_password'].widget.attrs.pop('required')
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
edited = getattr(self, 'instance', None)
instance = super().save(*args, **kwargs) instance = super().save(*args, **kwargs)
# Update assigned permissions
instance.object_permissions.set(self.cleaned_data['object_permissions']) instance.object_permissions.set(self.cleaned_data['object_permissions'])
# On edit, check if we have to save the password # On edit, check if we have to save the password
if edited and self.cleaned_data.get("password"): if self.cleaned_data.get('password'):
instance.set_password(self.cleaned_data.get("password")) instance.set_password(self.cleaned_data.get('password'))
instance.save() instance.save()
return instance return instance
def clean(self): def clean(self):
cleaned_data = super().clean()
instance = getattr(self, 'instance', None)
if not instance or cleaned_data.get("password"):
password = cleaned_data.get("password")
confirm_password = cleaned_data.get("confirm_password")
if password != confirm_password: # Check that password confirmation matches if password is set
raise forms.ValidationError( if self.cleaned_data['password'] and self.cleaned_data['password'] != self.cleaned_data['confirm_password']:
_("password and confirm_password does not match") raise forms.ValidationError(_("Passwords do not match! Please check your input and try again."))
)
# TODO: Move this logic to the NetBoxUser class
def clean_username(self): def clean_username(self):
"""Reject usernames that differ only in case.""" """Reject usernames that differ only in case."""
instance = getattr(self, 'instance', None) instance = getattr(self, 'instance', None)
@ -277,22 +255,46 @@ class GroupForm(BootstrapMixin, forms.ModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
# Populate assigned users and permissions
if self.instance.pk: if self.instance.pk:
self.fields['users'].initial = self.instance.user_set.all().values_list('id', flat=True) self.fields['users'].initial = self.instance.user_set.values_list('id', flat=True)
self.fields['object_permissions'].initial = self.instance.object_permissions.all().values_list('id', flat=True) self.fields['object_permissions'].initial = self.instance.object_permissions.values_list('id', flat=True)
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
instance = super().save(*args, **kwargs) instance = super().save(*args, **kwargs)
# Update assigned users and permissions
instance.user_set.set(self.cleaned_data['users']) instance.user_set.set(self.cleaned_data['users'])
instance.object_permissions.set(self.cleaned_data['object_permissions']) instance.object_permissions.set(self.cleaned_data['object_permissions'])
return instance return instance
class ObjectPermissionForm(BootstrapMixin, forms.ModelForm): class ObjectPermissionForm(BootstrapMixin, forms.ModelForm):
object_types = ContentTypeMultipleChoiceField(
label=_('Object types'),
queryset=ContentType.objects.all(),
limit_choices_to=OBJECTPERMISSION_OBJECT_TYPES,
widget=forms.SelectMultiple(attrs={'size': 6})
)
can_view = forms.BooleanField(
required=False
)
can_add = forms.BooleanField(
required=False
)
can_change = forms.BooleanField(
required=False
)
can_delete = forms.BooleanField(
required=False
)
actions = SimpleArrayField( actions = SimpleArrayField(
label=_('Actions'), label=_('Additional actions'),
base_field=forms.CharField(), base_field=forms.CharField(),
required=False, required=False,
help_text=_('Actions granted in addition to those listed above')
) )
users = DynamicModelMultipleChoiceField( users = DynamicModelMultipleChoiceField(
label=_('Users'), label=_('Users'),
@ -304,17 +306,6 @@ class ObjectPermissionForm(BootstrapMixin, forms.ModelForm):
required=False, required=False,
queryset=Group.objects.all() queryset=Group.objects.all()
) )
object_types = ContentTypeMultipleChoiceField(
label=_('Object types'),
queryset=ContentType.objects.all(),
limit_choices_to=OBJECTPERMISSION_OBJECT_TYPES,
widget=forms.SelectMultiple(attrs={'size': 6})
)
can_view = forms.BooleanField(required=False)
can_add = forms.BooleanField(required=False)
can_change = forms.BooleanField(required=False)
can_delete = forms.BooleanField(required=False)
fieldsets = ( fieldsets = (
(None, ('name', 'description', 'enabled',)), (None, ('name', 'description', 'enabled',)),
@ -330,13 +321,11 @@ class ObjectPermissionForm(BootstrapMixin, forms.ModelForm):
'name', 'description', 'enabled', 'object_types', 'users', 'groups', 'constraints', 'actions', 'name', 'description', 'enabled', 'object_types', 'users', 'groups', 'constraints', 'actions',
] ]
help_texts = { help_texts = {
'actions': _('Actions granted in addition to those listed above'), 'constraints': _(
'constraints': _('JSON expression of a queryset filter that will return only permitted objects. Leave null ' 'JSON expression of a queryset filter that will return only permitted objects. Leave null '
'to match all objects of this type. A list of multiple objects will result in a logical OR ' 'to match all objects of this type. A list of multiple objects will result in a logical OR '
'operation.') 'operation.'
} )
labels = {
'actions': 'Additional actions'
} }
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):