diff --git a/netbox/users/forms/model_forms.py b/netbox/users/forms/model_forms.py index 6a2921b60..fef10cfe2 100644 --- a/netbox/users/forms/model_forms.py +++ b/netbox/users/forms/model_forms.py @@ -201,6 +201,11 @@ class UserForm(BootstrapMixin, forms.ModelForm): del self.fields['date_joined'] del self.fields['last_login'] + # def is_valid(self): + # ret = super().is_valid() + # breakpoint() + # return ret + def save(self, *args, **kwargs): instance = super().save(*args, **kwargs) instance.object_permissions.set(self.cleaned_data['object_permissions']) @@ -215,9 +220,27 @@ class UserForm(BootstrapMixin, forms.ModelForm): if password != confirm_password: raise forms.ValidationError( - "password and confirm_password does not match" + _("password and confirm_password does not match") ) + def clean_username(self): + """Reject usernames that differ only in case.""" + instance = getattr(self, 'instance', None) + if instance: + qs = self._meta.model.objects.exclude(pk=instance.pk) + else: + qs = self._meta.model.objects.all() + + username = self.cleaned_data.get("username") + if ( + username and qs.filter(username__iexact=username).exists() + ): + raise forms.ValidationError( + _("user with this username already exists") + ) + + return username + class GroupForm(BootstrapMixin, forms.ModelForm): users = DynamicModelMultipleChoiceField( diff --git a/netbox/utilities/testing/views.py b/netbox/utilities/testing/views.py index dc17548a2..d073b8db6 100644 --- a/netbox/utilities/testing/views.py +++ b/netbox/utilities/testing/views.py @@ -170,13 +170,14 @@ class ViewTestCases: instance = self._get_queryset().order_by('pk').last() self.assertInstanceEqual(instance, self.form_data) - # Verify ObjectChange creation - objectchanges = ObjectChange.objects.filter( - changed_object_type=ContentType.objects.get_for_model(instance), - changed_object_id=instance.pk - ) - self.assertEqual(len(objectchanges), 1) - self.assertEqual(objectchanges[0].action, ObjectChangeActionChoices.ACTION_CREATE) + if hasattr(self.model, "to_objectchange"): + # Verify ObjectChange creation + objectchanges = ObjectChange.objects.filter( + changed_object_type=ContentType.objects.get_for_model(instance), + changed_object_id=instance.pk + ) + self.assertEqual(len(objectchanges), 1) + self.assertEqual(objectchanges[0].action, ObjectChangeActionChoices.ACTION_CREATE) @override_settings(EXEMPT_VIEW_PERMISSIONS=['*']) def test_create_object_with_constrained_permission(self): @@ -263,13 +264,14 @@ class ViewTestCases: self.assertHttpStatus(self.client.post(**request), 302) self.assertInstanceEqual(self._get_queryset().get(pk=instance.pk), self.form_data) - # Verify ObjectChange creation - objectchanges = ObjectChange.objects.filter( - changed_object_type=ContentType.objects.get_for_model(instance), - changed_object_id=instance.pk - ) - self.assertEqual(len(objectchanges), 1) - self.assertEqual(objectchanges[0].action, ObjectChangeActionChoices.ACTION_UPDATE) + if hasattr(self.model, "to_objectchange"): + # Verify ObjectChange creation + objectchanges = ObjectChange.objects.filter( + changed_object_type=ContentType.objects.get_for_model(instance), + changed_object_id=instance.pk + ) + self.assertEqual(len(objectchanges), 1) + self.assertEqual(objectchanges[0].action, ObjectChangeActionChoices.ACTION_UPDATE) @override_settings(EXEMPT_VIEW_PERMISSIONS=['*']) def test_edit_object_with_constrained_permission(self):