215

I am storing a phone number in model like this:

phone_number = models.CharField(max_length=12)

The user would enter a phone number and I would use the phone number for SMS authentication. This application would be used globally. So I would also need a country code. Is CharField a good way to store a phone number? And, how do I validate the phone number?

Super Kai - Kazuya Ito
  • 22,221
  • 10
  • 124
  • 129
pynovice
  • 7,424
  • 25
  • 69
  • 109

11 Answers11

410

You might actually look into the internationally standardized format E.164, recommended by Twilio for example (who have a service and an API for sending SMS or phone-calls via REST requests).

This is likely to be the most universal way to store phone numbers, in particular if you have international numbers work with.

  1. Phone by PhoneNumberField

    You can use the phonenumber_field library. It is a port of Google's libphonenumber library, which powers Android's phone number handling. See django-phonenumber-field.

    In the model:

    from phonenumber_field.modelfields import PhoneNumberField
    
    class Client(models.Model, Importable):
        phone = PhoneNumberField(null=False, blank=False, unique=True)
    

    In the form:

    from phonenumber_field.formfields import PhoneNumberField
    class ClientForm(forms.Form):
        phone = PhoneNumberField()
    

    Get the phone as a string from an object field:

    client.phone.as_e164
    

    Normalize the phone string (for tests and other staff):

    from phonenumber_field.phonenumber import PhoneNumber
    phone = PhoneNumber.from_string(phone_number=raw_phone, region='RU').as_e164
    
  2. Phone by regexp

    One note for your model: E.164 numbers have a maximum character length of 15.

    To validate, you can employ some combination of formatting and then attempting to contact the number immediately to verify.

    I believe I used something like the following in my django project:

     class ReceiverForm(forms.ModelForm):
         phone_number = forms.RegexField(regex=r'^\+?1?\d{9,15}$',
                                         error_message = ("Phone number must be entered in the format: '+999999999'. Up to 15 digits is allowed."))
    

As per jpotter6, you can do something like the following in your models as well:

File models.py:

from django.core.validators import RegexValidator

class PhoneModel(models.Model):
    ...
    phone_regex = RegexValidator(regex=r'^\+?1?\d{9,15}$', message="Phone number must be entered in the format: '+999999999'. Up to 15 digits allowed.")
    phone_number = models.CharField(validators=[phone_regex], max_length=17, blank=True) # Validators should be a list
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
erewok
  • 7,555
  • 3
  • 33
  • 45
  • 27
    It might be valuable to show how to do this on a model as well. phone_regex = RegexValidator(regex=r'^\+?1?\d{9,15}$', message="Phone number must be entered in the format: '+999999999'. Up to 15 digits allowed.") phone_number = models.CharField(validators=phone_regex, blank=True) – jpotts18 Apr 23 '14 at 16:23
  • @jpotter6 Your comment would be a great supplementary answer to this question. – par Sep 06 '14 at 01:57
  • 14
    Don't forget to add 'max_length=15' to the models.CharField – Esteban Jan 23 '15 at 02:41
  • 4
    @Esteban - max_length=16 (there is an optional + at the start) – dhackner Feb 03 '15 at 00:55
  • 3
    @erewok - I don't believe you want the '1' in your regex. E.164 allows up to 15 digits, inclusive of the country code. – dhackner Feb 03 '15 at 00:56
  • 1
    Indeed, E.164 does not include any international call prefixes, only the country code followed by the actual number, also the [minimum length of an E.164 number](http://stackoverflow.com/q/14894899/343834) seems to be 8 so the regex should be `'^\+\d{8,15}$'` and then `max_length=16` for the `CharField`. – Maxime R. Jun 24 '16 at 15:10
  • When using thte phonenumber_field library remember to first pip install django-phonenumber-field[phonenumbers] – Darkhorse Oct 12 '21 at 00:01
  • can you help me to handle user when search using national format but in database phone formated e164 ? if you can plese help me solve my problem (https://stackoverflow.com/questions/75274216/store-phone-field-use-django-phonenumber-field-override-search-method-to-e164-fo) thanks before. – Reynaldo Jan 30 '23 at 01:42
65

Use django-phonenumber-field:

pip install django-phonenumber-field
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
rgenito
  • 1,751
  • 13
  • 8
  • 4
    Don't forget to add the region. Without `PHONENUMBER_DEFAULT_REGION = 'US'` in the settings it won't allow you to input anything users from the United States would recognize as a phone number. Even then, this package won't output as something that looks like a phone number . . . I'm sure there's a setting I just haven't found yet! – Drigan Mar 16 '18 at 22:52
  • 15
    Note: this package is very big. Over 40MB. 33MB of it is geodata. – Forethinker Sep 18 '18 at 01:21
  • 5
    Noting @Forethinker 's comment, a much more lightweight alternative is https://github.com/VeryApt/django-phone-field , via `pip install django-phone-field` – SMX Oct 15 '18 at 22:19
  • 9
    @Forethinker then we can use the "lite" version `pip install django-phonenumber-field[phonenumberslite]` which does not include the geocoding, carrier and timezone metadata – heyxh Apr 26 '20 at 14:37
9

Use a CharField for the phone field in the model and the localflavor app for form validation:

https://docs.djangoproject.com/en/1.7/topics/localflavor/

As of 2021-12-07, it looks like LocalFlavor is no longer part of Django.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
nicorellius
  • 3,715
  • 4
  • 48
  • 79
8

Validation is easy. Text them a little code to type in.

A CharField is a great way to store it. I wouldn't worry too much about canonicalizing phone numbers.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
U2EF1
  • 12,907
  • 3
  • 35
  • 37
8

This solution worked for me:

First install django-phone-field. Command:

pip install django-phone-field

Then in file models.py:

from phone_field import PhoneField
...

class Client(models.Model):
    ...
    phone_number = PhoneField(blank=True, help_text='Contact phone number')

And in file settings.py:

INSTALLED_APPS = [...,
                  'phone_field'
]

It looks like this in the end:

Phone in form

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
VMMF
  • 906
  • 1
  • 17
  • 28
8

First, install "django-phonenumber-field" package with the command below:

pip install django-phonenumber-field[phonenumbers]

Then, set "phonenumber_field" to INSTALLED_APPS in "settings.py":

# "settings.py"

INSTALLED_APPS = [
    ...
    "phonenumber_field",
    ...
]

Then, set a field with "PhoneNumberField()" in "models.py":

# "models.py"

from django.db import models
from phonenumber_field.modelfields import PhoneNumberField

class Contact(models.Model):
    phone = PhoneNumberField()

Then, register "Contact" in "admin.py":

# "admin.py"

from django.contrib import admin
from .models import Contact

@admin.register(Contact)
class ContactAdmin(admin.ModelAdmin):
    pass

Then, run the command below:

python manage.py makemigrations && python manage.py migrate

Now, the field for a phone number is created as shown below:

enter image description here

In addition, assign the widget "PhoneNumberPrefixWidget()" to the field in a custom form and assign the custom form to the admin as shown below:

# "admin.py"

from django.contrib import admin
from .models import Contact
from django import forms
from phonenumber_field.widgets import PhoneNumberPrefixWidget

class ContactForm(forms.ModelForm):
    class Meta:
        widgets = {
            'phone': PhoneNumberPrefixWidget(),
        }

@admin.register(Contact)
class ContactAdmin(admin.ModelAdmin):
    form = ContactForm

Now, with country codes, the field for a phone number is created

enter image description here

And, you can set an initial country code like initial='US' to "PhoneNumberPrefixWidget()" as shown below. *Initial country code must be uppercase:

# "admin.py"

from django.contrib import admin
from .models import Contact
from django import forms
from phonenumber_field.widgets import PhoneNumberPrefixWidget

class ContactForm(forms.ModelForm):
    class Meta:
        widgets = {                          # Here
            'phone': PhoneNumberPrefixWidget(initial='US'),
        }

@admin.register(Contact)
class ContactAdmin(admin.ModelAdmin):
    form = ContactForm

Now, with the initial country code "US" selected, the field for a phone number is created:

enter image description here

You can also set an initial country code with "PHONENUMBER_DEFAULT_REGION" in "settings.py" as shown below but I recommand to set an initial country code with initial='US' to "PhoneNumberPrefixWidget()" as I've done above because using "PHONENUMBER_DEFAULT_REGION" sometimes doesn't display saved phone numbers in Django Admin:

# "settings.py"

PHONENUMBER_DEFAULT_REGION = "US"
Henry Ecker
  • 34,399
  • 18
  • 41
  • 57
Super Kai - Kazuya Ito
  • 22,221
  • 10
  • 124
  • 129
3

Others mentioned django-phonenumber-field. To get the display format how you want you need to set PHONENUMBER_DEFAULT_FORMAT setting to "E164", "INTERNATIONAL", "NATIONAL", or "RFC3966", however you want it displayed. See the GitHub source.

GoPackGo90
  • 41
  • 4
1

It all depends on what you understand as a phone number. Phone numbers are country-specific. The localflavors packages for several countries contains their own "phone number field". So if you are OK being country-specific you should take a look at localflavor package (class us.models.PhoneNumberField for the US case, etc.)

Otherwise you could inspect the localflavors to get the maximum length for all countries. Localflavor also has forms fields you could use in conjunction with the country code to validate the phone number.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
esauro
  • 1,276
  • 10
  • 17
1

I will describe what I use:

Validation: The string contains more than 5 digits.

Cleaning: removing all non-digit symbols and writing only numbers to the database. I'm lucky, because in my country (Russia) everybody has phone numbers with 10 digits. So I store only 10 digits in the database. If you are writing a multi-country application, then you should make a comprehensive validation.

Rendering: I write a custom template tag to render nicely it in the template. Or even render it like a picture - it is safer to prevent SMS spam.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Павел Тявин
  • 2,529
  • 4
  • 25
  • 32
  • Stumbling across this a few years later, but depending on platform (Web, in this case, I assume), I argue against rendering as a picture, even if it helps prevent spam. It's very inaccessible. It can't be copy'pasted, it can't be zoomed-in and maintain clarity, and it can't be ready by screen readers. https://www.boia.org/blog/why-is-it-important-for-accessibility-to-use-actual-text-instead-of-images-of-text – Caleb Jay May 02 '22 at 10:19
1

First of all, you need to install the phone number Django package.

pip install django-phonenumber-field[phonenumbers]

Then next is to add it to the installed applications:

INSTALLED_APPS = [
    ...
    'phonenumber_field',
    ...
]

This is how to use it in your model:

from phonenumber_field.modelfields import PhoneNumberField

class Artist(models.Model):

    phone_number = PhoneNumberField()
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
1
  1. Installs phonenumbers minimal metadata
pip install "django-phonenumber-field[phonenumberslite]"
  1. Installs phonenumbers extended features (e.g. geocoding)
pip install "django-phonenumber-field[phonenumbers]

I recommend using that pip install "django-phonenumber-field[phonenumbers]"

  1. models.py
from django.contrib.auth.models import AbstractUser
from phonenumber_field.modelfields import PhoneNumberField

class CustomUser(AbstractUser):
    phone = PhoneNumberField(unique=True, region='IR')
  1. Settings.py
AUTH_USER_MODEL = 'accounts.CustomUser'
  1. forms.py
from django.contrib.auth.forms import UserCreationForm, UserChangeForm
from django.contrib.auth import get_user_model
from phonenumber_field.widgets import PhoneNumberPrefixWidget

# from .models import CustomUser

class CustomUserCreationForm(UserCreationForm):
    class Meta:
        model = get_user_model()
        widgets = {
            'phone': PhoneNumberPrefixWidget(),
        }

        # fields = UserCreationForm.Meta.fields +('phone',) 
        fields = ['phone']
        
    
class CustomUserChangeForm(UserChangeForm):
    class Meta:
        model = get_user_model()
        widgets = {
            'phone': PhoneNumberPrefixWidget(),
        }
        # fields = UserChangeForm.Meta.fields +('phone',) 
        fields = ['phone']

Important : Requires the Babel package be installed.

  1. admin.py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth import get_user_model

from .forms import CustomUserCreationForm, CustomUserChangeForm


@admin.register(get_user_model())
class CustomUserAdmin(UserAdmin):
    model =get_user_model()
    add_form = CustomUserCreationForm
    form = CustomUserChangeForm
    list_display = [
        'username',
        'email',
        'phone'
    ]
    
    add_fieldsets = UserAdmin.add_fieldsets +(
        (None,{'fields':('phone',),}),
    )
    
    fieldsets = UserAdmin.fieldsets +(
        (None,{'fields':('phone',),}),
    )
    
# admin.site.register(get_user_model(), CustomUserAdmin)
Diego Borba
  • 1,282
  • 8
  • 22