Django model data types: Options and validations

Problem

You want to create Django models and understand what are the different data types including their options and validations.

Solution

First ensure you understand the default database DDL (Data definition language) and logical behavior Django creates for each Django model data type. If the default DDL and logical behavior for a data type is not suitable, you can use one of the various Django model data type options.

To limit the values of a Django model field you can use the max_length, min_value, max_value, max_digits and decimal_places options. To control how Django handles NULL, NOT NULL and empty values you can use the blank and null options. To use predetermined values for a Django model field you can use the default, auto_now, auto_now_add and choices options.

To enforce unique values for a Django model field you can use the unique, unique_for_date, unique_for_month and unique_for_year options. To influence how forms treat a Django model field you can use the editable, help_text, verbose_name and error_messages options. To alter the database DDL logic for a Django model field you can use the db_column, db_index, db_tablespace, primary_key options. And finally, to enforce more advanced validation logic you can use the validators options.

How it works

Data needs to conform to certain rules for it to be useful in any application. If it weren't for rules, then you can easily end up with ZIP code numbers where you expect address information or extensive text where you expect a maximum 10-character input. Django models can use several data types to define their fields and enforce data fit certain rules (e.g. text, number, email, url).

It's important to understand Django model data types operate on two layers, at the database layer and the Django/Python layer. When you define a Django model with its data types and create its initial migration, Django generates and executes a model's DDL (Data definition language) to create a database table, this DDL contains rules reflective of the Django model (e.g. a Django IntegerField gets translated into an INTEGER DB column to enforce integer values). This means that if you change this type of Django model field (e.g. changing an integer field to a text field) it requires generating a new migration in order for the database table to also reflect any new rule.

In addition to this rule enforcement created at the outset by a Django model at the database layer, Django model fields also enforce data rules at the Django/Python layer (i.e. prior to inserting/updating data in the database). For example, Django model field options like choices enforce that a field's values conform to a certain set of choices, this type of rule is enforced when you attempt to create, save or update Django model records via a form and is done irrespective of the database table rules. This means you can simply change a Django model field associated with this type of rule and you don't need to create a new migration to alter the underlying database table -- just changing the Python code is sufficient to change validation behavior.

Always keep in mind this subtle behavioral difference for Django model field data types: some Django model changes require creating a migration, while others can simply take effect changing the Django model field code. Table 1 illustrates the various Django model fields and the DDL they produce for all of the four main relational database supported by Django: SQLite, MySQL, PostgreSQL and Oracle.

Table 1 - Django model data types and generated DDL by database
Data typeDjango model typeDatabase DDLDjango validationDescription/Notes
SQLiteMySQLPostgreSQLOracle
Binarymodels.BinaryField()BLOB NOT NULLlongblob NOT NULLbytea NOT NULLBLOB NULL---Creates a blob field to store binary data (e.g. images, audio or other multimedia objects)
Booleanmodels.BooleanField()bool NOT NULLbool NOT NULLboolean NOT NULLNUMBER(1) NOT NULL CHECK ("VAR" IN (0,1))---Creates a boolean field to store True/False (or 0/1) values
Booleanmodels.Null
BooleanField()
bool NULLbool NULLboolean NULLNUMBER(1) NULL CHECK (("VAR" IN (0,1)) OR ("VAR" IS NULL))---Works just like BooleanField but also allows NULL values.
Date/timemodels.DateField()date NOT NULLdate NOT NULLdate NOT NULLDATE NOT NULL---Creates a date field to store dates
Date/timemodels.TimeField()time NOT NULLtime NOT NULLtime NOT NULLTIMESTAMP NOT NULLCreates a time field to store times.
Date/timemodels.DateTimeField()datetime NOT NULLdatetime NOT NULLtimestamp with time zone NOT NULLTIMESTAMP NOT NULL---Creates a datetime field to store dates with times
Date/timemodels.DurationField()bigint NOT NULLbigint NOT NULLinterval NOT NULLINTERVAL DAY(9) TO SECOND(6) NOT NULL---Creates a field to store periods of time.
Numbermodels.AutoField()integer NOT NULL AUTOINCREMENTinteger AUTO_INCREMENT NOT NULLserial NOT NULLNUMBER(11) NOT NULL & also creates a SEQUENCE and TRIGGER to increase the field---Creates an integer that autoincrements, primarly used for custom primary keys
Numbermodels.BigInteger
Field()
bigint NOT NULLbigint NOT NULLbigint NOT NULLNUMBER(19) NOT NULL---Create a big integer to fit numbers between -9223372036854775808 to 9223372036854775807. This range may vary depending on the DB brand
Numbermodels.DecimalField(
decimal_places=X,
max_digits=Y)
decimal NOT NULLnumeric(X, Y) NOT NULLnumeric(X, Y) NOT NULLNUMBER(10, 3) NOT NULLEnforces a number have a maximum X digits and Y decimal pointsCreates a decimal field to store decimal numbers. Note both X and Y arguments are required, where the X argument represents the maximum number of digits to store and the Y argument represents the number of decimal places to store.
Numbermodels.FloatField()real NOT NULLdouble precision NOT NULLdouble precision NOT NULLDOUBLE PRECISION NOT NULL---Creates a column to store floating-point numbers.
Numbermodels.IntegerField()integer NOT NULLinteger NOT NULLinteger NOT NULLNUMBER(11) NOT NULL---Creates a column to store integer numbers.
Numbermodels.Positive
IntegerField()
integer unsigned NOT NULLinteger UNSIGNED NOT NULLinteger NOT NULL CHECK ("VAR" >= 0)NUMBER(11) NOT NULL CHECK ("VAR" >= 0)Enforces values from 0 to 2147483647Works just like IntegerField but limits values to positive numbers
Numbermodels.Positive
Small
IntegerField()
smallint unsigned NOT NULLsmallint UNSIGNED NOT NULLsmallint NOT NULL CHECK ("VAR" >= 0)NUMBER(11) NOT NULL CHECK ("VAR" >= 0)Enforces values from 0 to 32767Works just like IntegerField and the specialized PositiveIntegerField but limits numbers to a smaller positive range.
Numberoptions.Small
IntegerField()
smallint NOT NULLsmallint NOT NULLsmallint NOT NULLNUMBER(11) NOT NULLEnforces a number is in the range from -32768 to 32767Works just like IntegerField but in a smaller integer range.
Textmodels.CharField(
max_length=N)
varchar(N) NOT NULLvarchar(50) NOT NULLvarchar(50) NOT NULLNVARCHAR2(50) NULL---Creates a text column, where the max_length argument is required to specify the maximum length in characters.
Textmodels.TextField()text NOT NULLlongtext NOT NULLtext NOT NULLNCLOB NULL---Creates a text field to store text.
Text (Specialized)models.
CommaSeparated
IntegerField(
max_length=X)
varchar(N) NOT NULLvarchar(50) NOT NULLvarchar(50) NOT NULLNVARCHAR2(50) NULLEnforces the string a CSV of integersWorks just like CharField except Django enforces the string be a comma separated value of integers prior to interacting with the database (e.g. 3,54,54,664,65)
Text (Specialized)models.EmailField()varchar(254) NOT NULLvarchar(254) NOT NULLvarchar(254) NOT NULLNVARCHAR2(254) NULLEnforces the text is a valid email with the internal Django EmailValidator to determine what is and isn't a valid.Works just like CharField defaulting to a max_length of 254 characters and also enforces the string is a valid email.
Text (Specialized)models.FileField()varchar(100) NOT NULLvarchar(100) NOT NULLvarchar(100) NOT NULLNVARCHAR2(100) NULLEnforces and provides various utilities to handle files (e.g. opening/closing file, upload location,etc)Works just like CharField defaulting to a max_length of 100 characters and also enforces the string is a valid file.
Text (Specialized)models.FilePathField()varchar(100) NOT NULLvarchar(100) NOT NULLvarchar(100) NOT NULLNVARCHAR2(100) NULLEnforces and provides various utilities to limit choices of filenames in certain filesystem directories.Works just like CharField defaulting to a max_length of 100 characters and also enforces the string is a valid file in a filesystem directory.
Text (Specialized)models.ImageField()varchar(100) NOT NULLvarchar(100) NOT NULLvarchar(100) NOT NULLNVARCHAR2(100) NULLEnforces and provides various utilities to handle image files (e.g. getting the height & width)Works just like CharField and the specialized FileField defaulting to a max_length of 100 characters and also enforces the string is a valid image. Note this field requires the presence of the Pillow Python library (e.g. pip install Pillow).
Text (Specialized)models.GenericIP
AddressField()
char(39) NOT NULLchar(39) NOT NULLinet NOT NULLVARCHAR2(39) NULLEnforces and provides various utilities to only accept valid IPv4 or IPv6 addresses (e.g. 198.10.22.64 and FE80::0202:B3FF
:FE1E:8329, as well as utilities like unpack_ipv4 and protocol)
Works just like CharField defaulting to a max_length of 39 characters and enforces the string is a valid IP address.
Text (Specialized)models.SlugField()varchar(50) NOT NULLvarchar(50) NOT NULLvarchar(50) NOT NULLNVARCHAR2(50) NULLEnforces a string is a slug string, which is a string that only contains letters, numbers, underscores or hyphens.Works just like CharField defaulting to a max_length of 50 characters and ensure the provided string is a slug -- a concept that's typically used to cleanse URL strings that contains spaces and other potentially invalid character like letter with accents.
Text (Specialized)models.URLField()varchar(200) NOT NULLvarchar(200) NOT NULLvarchar(200) NOT NULLNVARCHAR2(200) NULLEnforces the provided text value is a valid URLWorks just like CharField defaulting to a max_length of 200 characters and enforces the string is a valid URL
Text (Specialized)models.UUIDField()char(32) NOT NULLchar(32) NOT NULLuuid NOT NULLVARCHAR2(32) NOT NULLEnforces the provided text is a Universally unique identifiers (UUID)Works just like CharField defaulting to a max_length of 32 characters and enforces the value is a UUID.
Note Oracle NOT NULL considered harmful

If you look closely at some of the DDL generated for the different Django model fields in table 1 (e.g. models.CharField(), models.FileField()), you'll notice Oracle generates DB columns with the NULL constraint, where as the other three database brands generate DB columns with the NOT NULL constraint, this is neither a typo or a bug, it's by design due to the way Oracle works.

Even though Oracle does support the NOT NULL constraint as the other database do, Oracle comes with the side-effect that it also treats empty strings '' as NOT NULL values too. This means that if Django attempted to store an empty string (e.g. on a CharField() or FieldField()) in an Oracle database the operation would be rejected if the NOT NULL constraint were present -- an operation that is perfectly valid in other databases even with the NOT NULL constraint. So to maintain uniformity across databases and to avoid this Oracle specific edge-case, Django opts to not use the NOT NULL constraint for certain scenarios.

Note this is a well-known Oracle 'feature' or 'quirk' -- depending on your perspective -- that's been known for years: How to insert an empty string (zero-length) in a non-nullable column ? A: You can't. Oracle states this fact in their docs and also mentions this behavior may change in the future to become standard compliant (ANSI), but as of the latest version Oracle 12c this behavior remains the same. I'll provide some Django specific details related to working with NULL, NOT NULL and empty string '' issues in the next section.

As you can see in table 1, Django model fields produce slightly different DDL depending on the database brand, albeit the end behavior for all backends maps as close as possible to one another with Django/Python rule enforcement filling in the gaps. For example, for a Django model field like models.DurationField(), SQLite and MySQL use the bigint data type, where as PostgreSQL and Oracle use the more specialized interval data type. Similarly for Django model fields like models.CommaSeparatedIntegerField() and models.EmailField(), at the database level these are represented as basic character varchar data types -- or NVARCHAR2 in Oracle -- and it's Django/Python that enforces the text representation is a valid CSV of integers or email, respectively.

With knowledge of the initial DDL generated by each Django model field, in the next sections I'll present the various Django model field options and their behavior associated with the original DDL and Django/Python validation.

Limiting values: max_length, min_value, max_value, max_digits, decimal_places

Limiting values to a certain range is one of the most basic options in Django model fields. For text based data types the max_length option enforces values don't surpass a certain number of characters. As you can see in table 1, the CharField data type requires you specify the max_length option and for more specialized text based data types (e.g. EmailField) Django assigns a default max_length option value, which you can override if you specify the max_length option explicitly.

For fields that use the IntegerField data type, Django offers the min_value and max_value options to restrict values to a lower and upper bound (e.g. IntegerField(min_value=0,max_value=1000) limits values to the 0 to 1000 range). Similarly, for fields that use the DecimalField data type, Django requires you specify the max_digits and decimal_places options to enforce a value's maximum number of digits and decimal points, respectively.

NULL, NOT NULL and empty values : blank and null

By default, all Django model fields are assigned a NOT NULL restriction at the database level, if you look at table 1 again you can confirm this by the generated DDL -- the only exception to this behavior is for certain fields on Oracle databases, which is explained in the sidebar below table 1. This means that when you create/update a record with a NOT NULL field on it, a value must be provided for the field or otherwise the database rejects the operation. On certain occasions though, it can be necessary to allow empty field values or as they're known in the database world NULL values. To allow Django model fields to support NULL values at the database level, you must declare the null=True field option (e.g. IntegerField(null=True) allows an integer field to be left empty and generates DDL with NULL instead of the default NOT NULL).

In addition to the null option that's enforced at the database level and which defaults to null=False, Django also supports the blank option. The blank option which also defaults to blank=False on all fields is used to enforce Django/Python validation through forms that work on Django models (e.g. creating a Django model record through a form in the Django admin or a custom Django form). If a Django model field is declared with blank=True, Django permits the field to be left blank in the form, otherwise with no explicit blank option Django rejects the form and forces an end-user to provide a value for the field.

Since the use of null and blank options can be one of the most confusing topics in Django model field options, table 2 presents a matrix with different model definitions and operations to better illustrate this behavior.

Table 2 - Django model validation for blank and null field options
Default (blank=False,null=False)null=True (w/default blank=False)blank=True (w/default null=True)null=True and blank=True

class Person(models.Model):
 first_name = models.
  CharField(max_length=30)
 middle_name = models.
  CharField(max_length=30)
 last_name = models.
  CharField(max_length=30)

class Person(models.Model):
 first_name = models.
  CharField(max_length=30)
 middle_name = models.
  CharField(max_length=30,null=True)
 last_name = models.
  CharField(max_length=30)

class Person(models.Model):
 first_name = models.
  CharField(max_length=30)
 middle_name = models.
  CharField(max_length=30,blank=True)
 last_name = models.
  CharField(max_length=30)

class Person(models.Model):
 first_name = models.
  CharField(max_length=30)
 middle_name = models.
  CharField(max_length=30,null=True,blank=True)
 last_name = models.
  CharField(max_length=30)
Person.objects.create
(first_name='Johann',
middle_name=None,
last_name='Bach')

Noneis treated as NULL and so middle_name can't be NULL
Person.objects.create
(first_name='Johann',
middle_name=None,
last_name='Bach')

middle_name can be NULL due to null=True
Person.objects.create
(first_name='Johann',
middle_name=None,
last_name='Bach')

Noneis treated as NULL and so middle_name can't be NULL,blank=True is for forms
Person.objects.create
(first_name='Johann',
middle_name=None,
last_name='Bach')

middle_name can be NULL due to null=True
Person.objects.create
(first_name='Johann',
last_name='Bach')

Unspecified middle_name defaults to an empty string '' and operation succeeds as '' is treated as not null
Person.objects.create
(first_name='Johann',
last_name='Bach')

Unspecified middle_name defaults to an empty string '' and operation succeeds as '' is treated as not null
Person.objects.create
(first_name='Johann',
last_name='Bach')

Unspecified middle_name defaults to an empty string '' and operation succeeds as '' is treated as not null
Person.objects.create
(first_name='Johann',
last_name='Bach')

Unspecified middle_name defaults to an empty string '' and operation succeeds as '' is treated as not null
Person.objects.create
(first_name='Johann',
middle_name='',
last_name='Bach')

Explicit empty string '' is treated as not null and operation succeeds.
Person.objects.create
(first_name='Johann',
middle_name='',
last_name='Bach')

Explicit empty string '' is treated as not null and operation succeeds.
Person.objects.create
(first_name='Johann',
middle_name='',
last_name='Bach')

Explicit empty string '' is treated as not null and operation succeeds.
Person.objects.create
(first_name='Johann',
middle_name='',
last_name='Bach')

Explicit empty string '' is treated as not null and operation succeeds.
Form validation with empty middle_name in Django admin or regular Django form
Django form validation rejection
Form validation with empty middle_name in Django admin or regular Django form
Django form validation rejection
Record creation valid even with empty middle_name due to blank=True
Django form validation rejection
Record creation valid even with empty middle_name due to blank=True
Django form validation rejection

Predetermined values: default, auto_now, auto_now_add and choices

Sometimes it's helpful to assign predetermined values to a Django model field. For example, to avoid empty strings when no value is provided -- as illustrated in some of the cases in table 2 -- you can assign a default value to a Django model field (e.g. provide a default id, city or date). Another scenario for predetermined values is to restrict a Django model field to a series of choices instead of allowing users to introduce open-ended values, a mechanism that limits disparate data and errors (e.g. provide a list of state values such as CA, AR, instead of letting users introduce Ca, Cali, ar or Arizona).

To declare a default value for a Django model field you use the default option. For most circumstances you assign the default option a hard-coded value or a callable (i.e. a function). Listing 1 illustrates an example of the default option using both approaches.

Listing 1 - Django model default option use


def default_city():
    return "San Diego"

class Store(models.Model):
    name = models.CharField(max_length=30)    
    address = models.CharField(max_length=30)
    city = models.CharField(max_length=30,default=default_city)
    state = models.CharField(max_length=2,default='CA')

As you can see in listing 1 we have two Django model fields that use the default option. First the city field relies on the default=default_city value which tells Django to call the default_city method to populate the field every time a new record is created. It's worth mentioning the callable method is placed outside the Django model class vs. declaring it in the same class body, this is to allow serialization and support of Django model migrations in Python 2 projects, more extensive information on this limitation is provided at https://docs.djangoproject.com/en/1.9/topics/migrations/#serializing-values. Next in listing 1 you can see the state field uses the default='CA' value which tells Django to use the hard-coded CA string to populate the field every time a new record is created.

The enforcement of default options is done entirely by Django/Python (i.e. the database DDL is unaware of any default values). This means that when you work with a model like the one in listing 1 Django/Python intervenes to provide default values. For example, if you create a Django model instance directly with the Person.objects.create(name='Downtown',address='Main Street #5') statement the city and state values are filled by Django/Python to create the record. Similarly, if you go to the Django admin and attempt to create an instance of the model in listing 1, the city and state form fields are pre-filled with the default values.

Even though the default option works just as described for text fields, as well as number and boolean fields, it has particular behaviors for date fields -- specifically models.DateField() and models.DateTimeField() -- that are important to explore, so as to also understand the use of the auto_now and auto_now_add options.

Listing 2 - Django model default options for dates and times, as well as auto_now and auto_now_add use


from datetime import date
from django.utils import timezone

class Store(models.Model):
    name = models.CharField(max_length=30)    
    address = models.CharField(max_length=30)
    date = models.DateField(default=date.today)
    datetime = models.DateTimeField(default=timezone.now)
    date_lastupdated = models.DateField(auto_now=True)
    date_added = models.DateField(auto_now_add=True)
    timestamp_lastupdated = models.DateTimeField(auto_now=True)
    timestamp_added = models.DateTimeField(auto_now_add=True)

Listing 2 illustrates a modified version of the Store model from listing 1 with a series of date field variations. The first two additional fields date and datetime use a DateField with a default value using Python's standard datetime library and a DateTimeField with a default value using Django's django.utils.timezone module, respectively. In this case, if you create a Store record based on the model in listing 2, Django/Python fills in the values for the date and datetime fields based on the backing library's functionality. Similarly, if you go to the Django admin and attempt to create an instance of the model in listing 2, the date and datetime form fields are pre-filled with the default values for the current date and date with time, respectively.

Note Don't add () to methods in default values

If you look closely at the default values from previous listings, notice they lack () in their syntax, which creates an important behavior. By omitting the () syntax Python evaluates the expression until runtime, but if you use the () syntax (e.g. default=timezone.now()) Python evaluates the expression at compile time. For callables that use hard-coded values this is a non-issue, but for callables that use dynamically calculated values (e.g. dates) this is critical, otherwise you get a single value calculated at compile time (e.g. the date or datetime field would contain the same value calculated at compile time for all records).

Note Don't use the basic datetime.now for DateTimeField fields to avoid RuntimeWarning: DateTimeField .... received a naive datetime

Django is time-zone aware by default -- due to the USE_TZ = True in settings.py. This means that when you handle dates with times (e.g.in DateTimeField model fields) you must provide time-zone aware dates. This is why in listing 2 we use the models.DateTimeField(default=timezone.now) statement using Django's django.utils.timezone module which generates time-zone aware dates based on your Django project's configuration.

If you use Python's standard datetime library to assign a default date with time value (e.g. from datetime import datetime.... models.DateTimeField(default=datetime.now)) Django generates a warning like RuntimeWarning: DateTimeField Store.datetime received a naive datetime (......) while time zone support is active. indicating you are not providing a time-zone aware date. To solve this issue, you must either disable the use of time-zones (e.g. set USE_TZ = False), use Django's django.utils.timezone module as illustrated in listing 2 or generate a time-zone aware date yourself (e.g. import datetime...import pytz...tz_award_date = datetime.datetime.now(pytz.timezone('US/Pacific'))) noting this last option is not as flexible because you must hard-code a timezone vs. using Django's utility module which uses the timezone set in your project's TIME_ZONE variable.

In listing 2 there's an additional pair of DateField and DateTimeField fields that use the auto_now=True and auto_now_add=True options. Both of these options work like the default option -- in the sense they add a default date or date & time -- but have different behaviors than the default option. The first important difference is that while the default option can be used with several types of fields, both the auto_now and auto_now_add options are designed for either DateField and DateTimeField fields.

Unlike fields with the default option where you can provide a value for the field, fields with the auto_now and auto_now_add options can't be overridden. This means that even if you create or update a record and provide a value for a field with a auto_now and auto_now_add option the value is ignored. Similarly, in the Django admin by default fields that use the auto_now and auto_now_add option are not displayed because their value can't be modified.

Values for fields that use either the auto_now and auto_now_add options are generated when a record is created. Values for fields that use the auto_now option are updated every time the record is changed, while values for fields that use the auto_now_add option remain frozen for the lifetime of the record. By default, DateField fields that use either the auto_now or auto_now_add options generate their value from datetime.date.today(), where as DateTimeField fields that use either auto_now or auto_now_add options generate their value from django.utils.timezone.now().

Finally, it's worth mentioning the auto_now, auto_now_add and default options are mutually exclusive (i.e. you can't use more than one option in a field or it will result in an error). As you can see, the auto_now option is ideal to track a record's last-modified date, the auto_now_add is ideal to track a record's creation date and the default option for date fields works to set a general purpose modifiable date.

Another option that supports predetermined values in Django model fields is choices. Unlike the default option where you define a single value, choices lets you create a list of values to choose from. Listing 3 illustrates an example of a Django model field that uses the choices option.

Listing 3 - Django model choices option


ITEM_SIZES = (
            ('S','Small'),
            ('M','Medium'),
            ('L','Large'),
            ('P','Portion'),
            )


class Item(models.Model):
    menu = models.ForeignKey(Menu, on_delete=models.CASCADE)
    name = models.CharField(max_length=30)
    description = models.CharField(max_length=100)
    size = models.CharField(choices=ITEM_SIZES,max_length=1)

The first thing you need to use the choices option is create a list of values as a tuple of tuples as illustrated in listing 3. The ITEMS_SIZES tuple in listing 3 has four tuples, where the first tuple element represents a key to use in the database (e.g.S,M) and the second tuple element represents a human-friendly representation of the first tuple element (e.g.Small,Medium).

Next, in listing 3 you can see the size field is assigned the models.CharField(choices=ITEM_SIZES,max_length=1) value which tells Django to use the keys from the ITEM_SIZES variable as potential values. Note that in this case the keys to use in the database are all one-character text values (e.g.S,M) that correspond to the CharField(max_length=1) data type, but you can also use numbers or boolean values as keys so long as they match the target field data type.

The human-friendly representation of choices values plays a role when use the Django model field in the context of Django forms. For example, if you create or edit a record from a model like the one in listing 3, Django uses the human-friendly representation of choices to generate a selection list as illustrated in figure 1 taken from the Django admin.

Django human-friendly choices values in form
Figure 1.- Django human-friendly choices values in form

Another important tidbit of the choices option is that in addition to accessing key values in a standard way (e.g. item.size to output S or M), it's possible to access the human-friendly friendly values with the special method get_<field_name>_display() (e.g. item.get_size_display() to output Small or Medium). If you use this method in the context of templates, you must use the get_<field_name>_display syntax, note the missing () that makes it a callable.

Unique values: unique, unique_for_date, unique_for_month and unique_for_year

It's possible to enforce a field value be unique across all records of the same type. For example, in listing 1 if you change the name = models.CharField(max_length=30) statement to name = models.CharField(max_length=30, unique=True) it tells Django to ensure all Store records have a unique name value (i.e. it enforces no duplicate Store.name values).

The unique option is enforced by Django at the database layer (i.e. by adding a DDL UNIQUE SQL constraint), as well as the Django/Python layer. In addition, the unique option is valid on all field types except ManyToManyField, OneToOneField, and FileField.

To enforce uniqueness of a field along with a DateField or DateTimeField value, Django offers the unique_for_date, unique_for_month and unique_for_year options. For example, in listing 2 to enforce the name field value be unique with a date of the date_lastupdated field you can use the name = models.CharField(max_length=30, unique_for_date="date_lastupdated") statement which tells Django to only allow one record with the same name and date_lastupdated (e.g. you can't have two records with name="Downtown" and date_lastupdated="2016-01-01", but two records with name="Downtown" and different date_lastupdated values are allowed).

The unique_for_month and unique_for_year options provide wider ranges to enforce validation. For example, the name = models.CharField(max_length=30, unique_for_month="date_lastupdated") statement tells Django to only allow one record with the same name and date_lastupdated value for the same month (e.g. you can't have two records with name="Downtown" that have date_lastupdated values with the same month). For example, the name = models.CharField(max_length=30, unique_for_year="date_lastupdated") statement tells Django to only allow one record with the same name and date_lastupdated value for the same year (e.g. you can't have two records with name="Downtown" that have date_lastupdated values with the same year).

Due to the more complex requirements of the unique_for_date, unique_for_month and unique_for_year options, the enforcement of these options is done at the Django/Python layer. In addition, due to the nature of the validation process these options are only valid for DateField and DateTimeField fields, noting that for DateTimeField cases only the date portion of the value is used.

Forms: editable, help_text, verbose_name and error_messages

When a Django model is used in the context of forms, form fields -- which are backed by Django model fields -- also receive behaviors that can be influenced through various options.

By default, all Django model fields presented in a form are editable, but you can change this behavior by using the editable=False option. Setting a model field with editable=False tells Django to omit it completely in a form (e.g. in the Django admin) and as a consequence any validation associated with the form field is also bypassed.

The help_text option allows the inclusion of additional text alongside a form field. Listing 4 illustrates the use of the help_text option and figure 2 the rendered interface.

Listing 4 - Django model help_text option

ITEM_SIZES = (
            ('S','Small'),
            ('M','Medium'),
            ('L','Large'),
            ('P','Portion'),
            )

class Item(models.Model):
    menu = models.ForeignKey(Menu, on_delete=models.CASCADE)
    name = models.CharField(max_length=30)
    description = models.CharField(max_length=100,help_text="Ensure you provide some description of the ingredients")
    size = models.CharField(choices=ITEM_SIZES,max_length=1)
    calories = models.IntegerField(help_text="Calorie count should reflect size of the item")
Django model help_text option
Figure 2.- Django model help_text option

In listing 4 you can see there are two fields with the help_text option, one with a simple string value and the other a string with the HTML <b> tag. In figure 2 you can see the fields from listing 4 have the help_text value placed below the field form. Note how the HTML <b> tag is rendered, which means Django doesn't escape the HTML and assumes help_text values are safe. If you wanted to escape the HTML in a help_text value you would need to use Django's escape method (e.g. from django.utils.html import escape.....models.IntegerField(help_text=escape("Calorie count should reflect size of the item")) to output Calorie count should reflect <b>size</b> of the item literally).

By default, a Django field name is used to generate the header of a Django form field. For example, in figure 2 you can see each form field is preceded by a capitalized version of the field name in listing 4 (e.g. Name, Description, Size, Calories). You can configure more verbose form field headers with the verbose_name option. For example, if you use the statement name = models.CharField(max_length=30,verbose_name="ITEM NAME"), Django outputs the ITEM NAME header for the name form field instead of the default Name header.

If you attempt to save or update a Django model in a form and any of the values don't comply with the underlying field types, Django generates default error messages for the non-compliant values. These error messages while helpful are generic in nature, it's possible to customize these error messages for each field with the error_messages option. The error_messages option accepts a dictionary of key-values, where keys represents error types and values error messages, some valid keys include: null, blank, invalid, invalid_choice, unique, unique_for_date and other depending on the data type -- note that valid keys depend on the underlying data type (e.g. only date fields support the unique_for_date key, where as required is supported by all data types).

For the name = models.CharField(max_length=30, error_messages={'blank': 'You need to provide your name!'}) statement, if no value is provided for the name field in a form, Django generates the custom error message You need to provide your name!.

Database Definition Language (DDL): db_column, db_index, db_tablespace, primary_key

By default, Django generates a database table's column names based on a Django model's field names. For example, if a Django model field is named menu Django generates a DB column named menu. It's possible to override this behavior using the db_column option (e.g. name = models.CharField(max_length=30,db_column="my_custom_name") Django generates the DDL with the my_custom_name column name)

Another DB related option for Django models is the db_index option that tells Django to generate a database index for the field (e.g. size = models.CharField(choices=ITEM_SIZES,max_length=1,db_index=True) generate an DB index for the size field). Be aware that under the following circumstances Django automatically creates an index, so db_index=True for the following scenarios:

If you use a PostgreSQL or Oracle database, it's possible to specify a DB tablespace for a field's index through the db_tablespace name. By default Django uses a project's DEFAULT_INDEX_TABLESPACE setting to determine a tablespace, but if a given field uses the db_tablespace Django uses this value instead. If the database brand doesn't support tablespaces for indexes (e.g. MySQL or SQLite) this option is ignored.

Finally, the primary_key option allows you to define a primary key for a model. By default, if no Django model field is set with the primary_key=True statement, Django automatically creates an AutoField data type named id to hold the primary key (e.g. id = models.AutoField(primary_key=True). The typical use case for the primary_key option is for fields that are references to other fields (e.g. OneToOneField) or if you want a custom primary key due to design constraints.

Built-in and custom validators: validators

In addition to the enforcement of limiting, predetermined and unique values in Django models -- previously described in their own sections in this recipe -- Django also offers enforcement of values through the validators option. Validators offer a way to integrate more advanced logic into Django model fields. Django has a series of built-in validators in the django.core.validators package which are used internally by data types to enforce their behavior. For example, the models.EmailField data type relies on django.core.validators.EmailValidator to validate email values, just as the models.IntegerField data type uses the MinValueValidator and MaxValueValidator validators to enforce the min_value and max_value options, respectively.

Built-in validators can be used on any Django model field -- even though they are reasonably used by different Django model data types -- and in addition it's possible to create custom validators with more advanced logic. Both of these last validators options are illustrated in listing 5.

Listing 5 - Django model field validators option with built-in and custom validator



ITEM_SIZES = (
            ('S','Small'),
            ('M','Medium'),
            ('L','Large'),
            ('P','Portion'),
            )

# Import built-in validator
from  django.core.validators import MinLengthValidator

# Create custom validator
from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _

def calorie_watcher(value):
    if value > 5000:
        raise ValidationError(
            _('Whoa! calories are %(value)s ? We try to serve healthy food, try something less than 5000!'),
            params={'value': value},
        )
    if value < 0:
        raise ValidationError(
            _('Strange calories are %(value)s ? This can\'t be, value must be greater than 0'),
            params={'value': value},
        )

class Item(models.Model):
    menu = models.ForeignKey(Menu, on_delete=models.CASCADE)
    name = models.CharField(max_length=30,validators=[MinLengthValidator(5)])
    description = models.CharField(max_length=100)
    size = models.CharField(choices=ITEM_SIZES,max_length=1)
    calories = models.IntegerField(validators=[calorie_watcher])

The first validators option in listing 5 uses the built-in MinLengthValidator validator class to enforce the values for the name field contain at least 5 characters. The second validators option in listing 5 uses the custom calorie_watcher validator method to enforce the values for the calories field fit a certain range and use custom messages in case this range is not met. It's worth mentioning that it's possible to use multiple validators on a single field by using a list syntax (e.g.validators=[MinLengthValidators(5),MaxLengthValidators(100)]).