This seems strange
I have the following model in Django:
class CustomUser(AbstractUser):
first_name = models.CharField(_("first name"), max_length=20)
last_name = models.CharField(_("last name"), max_length=20)
email = models.EmailField(_('email address'), blank=False, unique=True)
validated_account = models.BooleanField(_('validated account'), blank=False, default=False)
phone = PhoneNumberField(null=False, blank=False, unique=True)
def __str__(self):
return f"{self.first_name} {self.last_name}"
I have the following form to edit the model:
class CustomUserChangeForm(UserChangeForm):
password = None
class Meta(UserChangeForm.Meta):
model = CustomUser
fields = ["first_name", "last_name", "email", "phone", "username"]
This is my view to edit the model:
@user_passes_test(lambda u: u.is_authenticated, "users:login")
def edit_profile(request):
if request.method == "POST":
print(request.POST)
form = CustomUserChangeForm(request.POST, instance=request.user)
print(form)
if form.is_valid():
user = form.save()
auth_login(request, user)
messages.success(request, "Edition successful.")
return redirect("dashboard:cases")
messages.error(
request, "Unsuccessful Edition. Invalid information.")
else:
form = CustomUserChangeForm(instance=request.user)
return render(request=request, template_name="dashboard/edit_profile.html", context={"edit_profile_form": form})
This is the printing result from the view call:
<QueryDict: {'csrfmiddlewaretoken': ['DbmuCTIOAXdP96Ozrcsf3YXSCxQ1lusywgLz4OLh5FAHrjas25inxqIt0kS9syaj'], 'first_name': ['Super'], 'last_name': ['User'], 'email': ['admin@example.com'], 'phone': ['+0043328107a'], 'username': ['admin']}>
<tr>
<th><label for="id_first_name">First name:</label></th>
<td>
<input type="text" name="first_name" value="Super" maxlength="20" required id="id_first_name">
</td>
</tr>
<tr>
<th><label for="id_last_name">Last name:</label></th>
<td>
<input type="text" name="last_name" value="User" maxlength="20" required id="id_last_name">
</td>
</tr>
<tr>
<th><label for="id_email">Email address:</label></th>
<td>
<input type="email" name="email" value="admin@example.com" maxlength="254" required id="id_email">
</td>
</tr>
<tr>
<th><label for="id_phone">Phone:</label></th>
<td>
<input type="tel" name="phone" value="+0043328107" maxlength="128" required id="id_phone">
</td>
</tr>
<tr>
<th><label for="id_username">Username:</label></th>
<td>
<input type="text" name="username" value="admin" maxlength="150" autocapitalize="none" autocomplete="username" required id="id_username">
<br>
<span class="helptext">Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.</span>
</td>
</tr>
Why doesn't the resulting form contain the last letter in the phone number? In the POST request, a phone number with a letter at the end clearly appears. I would expect it to be not valid, and instead the form is valid.
What am I doing wrong? Thanks for the help
I'm assuming the PhoneNumberField
is from django-phonenumber-field
. If so,
Short answer: That's the library doing it.
Long answer:
The specified behaviour of html input of type tel
is to allow almost all characters since phone numbers have varied syntax.
Unlike the URL and Email types, the Telephone type does not enforce a particular syntax. This is intentional; in practice, telephone number fields tend to be free-form fields, because there are a wide variety of valid phone numbers.
Except for a carriage return /r
or line feed /n
, the input will let you type in anything, including a mixture of letters and numbers as is your case. That's why the POSTed data contains the 'invalid' character.
But you're using PhoneNumberField
for that field, that's what's responsible for the 'unexpected' behaviour. Django forms implement to_python
method which is responsible for converting the user html inputs into Python objects. The returned value is what's used in the form/model's clean
method. And this is where the 'unexpected' behaviour originates.
PhoneNumberField
overrides this to_python
method, and when supplied a string (which is always the case with html forms), it parses the input using phonenumbers
's parse
function. This is what's removing the invalid character in your bound form field's value.
You can see the national_number
attribute has stripped off the trailing invalid characters. It does the parsing for invalid characters in between the phone number as well. The exact implementation of how it does this is explained in the parse
function's doc string:
"""The method is quite lenient and looks for a number in the input text (raw input) and does not check whether the string is definitely only a phone number. To do this, it ignores punctuation and white-space, as well as any text before the number (e.g. a leading "Tel: ") and trims the non-number bits. It will accept a number in any format (E164, national, international etc), assuming it can be interpreted with the defaultRegion supplied. It also attempts to convert any alpha characters into digits if it thinks this is a vanity number of the type "1800 MICROSOFT".
Again, this assumes you're using django-phonenumber-field
, if not, well, I am dammed.