Skip to content

Instantly share code, notes, and snippets.

@smithdc1
Created August 3, 2020 21:15
Show Gist options
  • Save smithdc1/689894b2d68ec333605d0bb021d614ac to your computer and use it in GitHub Desktop.
Save smithdc1/689894b2d68ec333605d0bb021d614ac to your computer and use it in GitHub Desktop.
#24782 -- Added TestCase.assertFormValid
from django.utils.translation import ngettext_lazy
from django.core.validators import validate_email
from django.forms import Form, CharField, IntegerField, PasswordInput
from django.conf import settings
from django.forms.utils import ErrorList, ErrorDict, ValidationError
import django
settings.configure()
django.setup()
class TestForm(Form):
name = CharField(label='Name', max_length=4)
age = IntegerField(label='Age')
class TestFormEmail(Form):
email = CharField(label='email', max_length=4, validators=[validate_email])
class UserRegistration(Form):
username = CharField(max_length=10)
password1 = CharField(widget=PasswordInput)
password2 = CharField(widget=PasswordInput)
def clean(self):
if (self.cleaned_data.get('password1') and self.cleaned_data.get('password2') and
self.cleaned_data['password1'] != self.cleaned_data['password2']):
raise ValidationError('Please make sure your passwords match.')
return self.cleaned_data
email_form = TestFormEmail(data={
'email': 'not_an_email_address'
})
email_form.is_valid()
test_form = TestForm(data={
'name': 'John',
'age': None,
})
test_form.is_valid()
password_form = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'bar'}, auto_id=False)
password_form.is_valid()
# manually build an error dict for test_form
required_error = ValidationError(message='This field is required.', code='required')
error_dict = ErrorDict(
{'age': ErrorList(required_error)}
)
assert error_dict == test_form.errors
# manually build error dict for email_form
email_error = ValidationError(message='Enter a valid email address.', code='invalid',
params={'value': 'not_an_email_address'})
max_length_error = ValidationError(
message=ngettext_lazy(
'Ensure this value has at most %(limit_value)d character (it has %(show_value)d).',
'Ensure this value has at most %(limit_value)d characters (it has %(show_value)d).',
'limit_value'),
code='max_length',
params={'limit_value': 4, 'show_value': 20, 'value': 'not_an_email_address'}
)
# ValidationErrors are equal to those generated by the form
assert email_form.errors['email'].data[0] == email_error
assert email_form.errors['email'].data[1] == max_length_error
# Manually built ErrorDict is equal to form.errors
email_error_dict = ErrorDict(
{'email': ErrorList([email_error, max_length_error])}
)
print('Can manually build ErrorDict')
print(email_form.errors == email_error_dict)
# But -- code and params are not important
print('\nCode and Params are not important')
print(email_form.errors == ErrorDict(
{'email': ErrorList(
[
ValidationError('Enter a valid email address.'),
ValidationError('Ensure this value has at most 4 characters (it has 20).')
])}
))
# and order is
print('\nErrorDict ordering is important')
print(email_form.errors == ErrorDict(
{'email': ErrorList(
[
ValidationError('Ensure this value has at most 4 characters (it has 20).'),
ValidationError('Enter a valid email address.')
])}
))
# NON_FIELD_ERRORS
# ValidationError was created without a code -- code will therefore be 'None'.
# Therefore testing against 'code' doesn't really work for non-field errors, unless users pass in a 'code'
# It _could_ be based upon ValidationError equality or message, but need to type it out in full.
print('\nCustom errors may not have a "code"')
print(password_form.errors['__all__'].data[0].code)
# Conclusions
# 1. Equality at the ErrorDict level is not a robust enough way to
# test for errors raised on a form. But could check for each ValidationError
# in turn
# 2. Building validation errors to pass the validation equality test is too
# hard. Max length error above is good, it's just too difficult to
# expect a user to be able to pass in message/params/code correctly.
# 3. Non field errors (e.g. password matching) is likely to not have a code
# passed to it, so testing on 'code' alone is not enough. Example here is from
# Django's own test suite.
# Options
# 1. My conclusion is wrong, it's ok to expect a user to pass in all of the
# requirements to build a ValidationError
# 2. Carry on with only testing for 'code'. This is simpler as in most cases
# the codes are fairly short. Downside it is not as robust as the equality error
# unlikely to be helpful for non field errors. Potential to pass 'code' check
# but still to be a different error.
# 3. Simplify function so new functions only test for valid/invalid, and not
# the reasons for a form being invalid.
# 4. Close ticket as won't fix. Is it worth adding an extra function when
# something like the only benefit would be you could write
# self.assertequal(form.is_valid(), True)
# as
# self.assertFormValid(form)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment