Django model data types

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 enforce model data fit certain rules (e.g. text, number, email).

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 that reflect a Django model's fields (e.g. a Django IntegerField model field 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 parameters like choices enforce a field's values conform to a set of choices, this type of rule is enforced when you attempt to create, save or update Django model records 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 not have to create a new migration to alter the underlying database table -- in this case, changing the Python code is enough to change validation behavior.

Always keep this subtle in mind 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 7-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 7-1 Django model data types and generated DDL by database

Data type Django model type Database DDL Description - Validation - Notes
SQLite MySQL PostgreSQL Oracle
Binary models.BinaryField() BLOB NOT NULL longblob NOT NULL bytea NOT NULL BLOB NULL Creates a blob field to store binary data (e.g. images, audio or other multimedia objects)
Boolean models.BooleanField() bool NOT NULL bool NOT NULL boolean NOT NULL NUMBER(1) NOT NULL CHECK ("VAR" IN (0,1)) Creates a boolean field to store True/False (or 0/1) values
Boolean models.NullBooleanField() bool NULL bool NULL boolean NULL NUMBER(1) NULL CHECK (("VAR" IN (0,1)) OR ("VAR" IS NULL)) Works just like BooleanField but also allows NULL values
Date/time models.DateField() date NOT NULL date NOT NULL date NOT NULL DATE NOT NULL Creates a date field to store dates
Date/time models.TimeField() time NOT NULL time NOT NULL time NOT NULL TIMESTAMP NOT NULL Creates a time field to store times.
Date/time models.DateTimeField() datetime NOT NULL datetime NOT NULL timestamp with time zone NOT NULL TIMESTAMP NOT NULL Creates a datetime field to store dates with times
Date/time models.DurationField() bigint NOT NULL bigint NOT NULL interval NOT NULL INTERVAL DAY(9) TO SECOND(6) NOT NULL Creates a field to store periods of time.
Number models.AutoField() integer NOT NULL AUTOINCREMENT integer AUTO_INCREMENT NOT NULL serial NOT NULL NUMBER(11) NOT NULL & also creates a SEQUENCE and TRIGGER to increase the field Creates an integer that autoincrements, primarly used for custom primary keys
Number models.BigIntegerField() bigint NOT NULL bigint NOT NULL bigint NOT NULL NUMBER(19) NOT NULL Create a big integer to fit numbers between -9223372036854775808 to 9223372036854775807. This range may vary depending on the DB brand
Number models.DecimalField(decimal_places=X,max_digits=Y) decimal NOT NULL numeric(X, Y) NOT NULL numeric(X, Y) NOT NULL NUMBER(10, 3) NOT NULL Enforces a number have a maximum X digits and Y decimal points Creates 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.
Number models.FloatField() real NOT NULL double precision NOT NULL double precision NOT NULL DOUBLE PRECISION NOT NULL Creates a column to store floating-point numbers.
Number models.IntegerField() integer NOT NULL integer NOT NULL integer NOT NULL NUMBER(11) NOT NULL Creates a column to store integer numbers.
Number models.PositiveIntegerField() integer unsigned NOT NULL integer UNSIGNED NOT NULL integer NOT NULL CHECK ("VAR" >= 0) NUMBER(11) NOT NULL CHECK ("VAR" >= 0) Enforces values from 0 to 2147483647 Works just like IntegerField but limits values to positive numbers
Number models.PositiveSmallIntegerField() smallint unsigned NOT NULL smallint UNSIGNED NOT NULL smallint NOT NULL CHECK ("VAR" >= 0) NUMBER(11) NOT NULL CHECK ("VAR" >= 0) Enforces values from 0 to 32767 Works just like IntegerField and the specialized PositiveIntegerField but limits numbers to a smaller positive range.
Number options.SmallIntegerField() smallint NOT NULL smallint NOT NULL smallint NOT NULL NUMBER(11) NOT NULL Enforces a number is in the range from -32768 to 32767 Works just like IntegerField but in a smaller integer range.
Text models.CharField(max_length=N) varchar(N) NOT NULL varchar(50) NOT NULL varchar(50) NOT NULL NVARCHAR2(50) NULL Creates a text column, where the max_length argument is required to specify the maximum length in characters.
Text models.TextField() text NOT NULL longtext NOT NULL text NOT NULL NCLOB NULL Creates a text field to store text.
Text (Specialized) models.CommaSeparatedIntegerField(max_length=50) varchar(N) NOT NULL varchar(N) NOT NULL varchar(N) NOT NULL NVARCHAR2(N) NULL Enforces the string a CSV of integers.Works 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 NULL varchar(254) NOT NULL varchar(254) NOT NULL NVARCHAR2(254) NULL Enforces 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 NULL varchar(100) NOT NULL varchar(100) NOT NULL NVARCHAR2(100) NULL Enforces 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 NULL varchar(100) NOT NULL varchar(100) NOT NULL NVARCHAR2(100) NULL Enforces 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 NULL varchar(100) NOT NULL varchar(100) NOT NULL NVARCHAR2(100) NULL Enforces 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.GenericIPAddressField() char(39) NOT NULL char(39) NOT NULL inet NOT NULL VARCHAR2(39) NULL Enforces 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 NULL varchar(50) NOT NULL varchar(50) NOT NULL NVARCHAR2(50) NULL Enforces 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 NULL varchar(200) NOT NULL varchar(200) NOT NULL NVARCHAR2(200) NULL Enforces the provided text value is a valid URL Works 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 NULL char(32) NOT NULL uuid NOT NULL VARCHAR2(32) NOT NULL Enforces 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.
Oracle NOT NULL considered harmful

If you look closely at some of the DDL generated for the different Django model fields in table 7-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 brands do, Oracle comes with the side-effect that it also treats empty strings '' as NOT NULL values. This means that if Django attempts 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's 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 7-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 and 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. In table 7-1 you can see 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 with an explict max_length value.

For fields that use the IntegerField data type, Django offers the min_value and max_value options to restrict a value to lower/upper bounds (e.g. IntegerField(min_value=0,max_value=1000) limits values to a 0 to 1000 range). Similarly, for fields with 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.

Empty, null & not null 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 7-1 again you can confirm this by the generated DDL -- the only exception is for certain fields on Oracle databases, which is explained in the sidebar below table 7-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 also defaults to blank=False on all fields and 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 a form, otherwise (i.e. if blank=True isn't explicitly declared in a model field) 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 7-2 presents a matrix with different model definitions and operations to better illustrate this behavior.

Table 7-2. Django model validation for blank and null field options

Model definition

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)
Blank and null field combinations Default (blank=False,null=False)
middle_name =
    models.CharField(max_length=30)
null=True (with default blank=False)
middle_name =
    models.CharField(max_length=30,null=True)
blank=True(with default null=True)
middle_name=
   models.CharField(max_length=30,blank=True)
null=True and blank=True
middle_name =
    models.CharField(max_length=30,null=True,blank=True)
Person.objects.create(
          first_name='Johann',
	  middle_name=None,
	  last_name='Bach')
None is treated as NULL and so middle_name can't be NULL middle_name can be NULL due to null=True None is treated as NULL and so middle_name can't be NULL, blank=True is for forms 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 Unspecified middle_name defaults to an empty string '' and operation succeeds as '' is treated as not null Unspecified middle_name defaults to an empty string '' and operation succeeds as '' is treated as not null 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. Explicit empty string '' is treated as not null and operation succeeds. Explicit empty string '' is treated as not null and operation succeeds. Explicit empty string '' is treated as not null and operation succeeds.
Form validation with empty middle_name in Django admin or regular Django form. Validation error due to empty middle_name Validation error due to empty middle_name Record creation valid even with empty middle_name due to blank=True Record creation valid even with empty middle_name due to blank=True
Tip The next chapter covers Django model forms and their validation.

Predetermined values: default, auto_now, auto_now_add and choices

Sometimes it's helpful to assign predetermined values to Django model fields. For example, to avoid empty strings when no value is provided -- as illustrated in some of the cases in table 7-2 -- you can use the default option on a Django model field (e.g. provide a default id, city or date). In most circumstances you assign a default option as a hard-coded value or a method reference. Listing 7-6 illustrates an example of the default option using both approaches.

Listing 7-6. 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 7-6 we have two Django model fields that use the default option. First the city field relies on the default=default_city value -- note the lack of parenthesis in the syntax which makes it a reference to the function -- that tells Django to call the default_city method to populate the field every time a new record is created.

It's worth mentioning the 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[1]. Next in listing 7-6 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 when 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 7-6 Django/Python intervenes to provide default values. For example, if you create a Django model instance as Person.objects.create(name='Downtown',address='Main Street #5'), 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 7-6, 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 let's do that and while on the topic of dates also learn about the auto_now and auto_now_add options related to date and time model fields.

Listing 7-7. 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 7-7 illustrates a modified version of the Store model from listing 7-6 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 -- the sidebar contains more details on why use the django.utils.timezone module to create a date time value.

In this case, if you create a Store record based on the model in listing 7-7, 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 7-7, the date and datetime form fields are pre-filled with the default values for the current date and date with time, respectively.

Don't add () PARENTHESIS 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 assigns a reference to a method and evaluates the expression until runtime, but if you use the () syntax (e.g. default=timezone.now()) Python evaluates the expression at compile time. For functions that return fixed values this is a non-issue, but for functions that return 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).
Don't use the basic datetime.now for DateTimeField fields
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 7-7 the models.DateTimeField(default=timezone.now) statement uses Django's django.utils.timezone module which generates time-zone aware dates.

In listing 7-7 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 a slightly different behavior. The first important difference is that while the default option can be used with several types of fields, the auto_now and auto_now_add options are designed for DateField and DateTimeField fields.

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 a 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().

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 an auto_now or 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. 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 works as a general purpose modifiable date on a form field.

Another scenario for predetermined values is to restrict a Django model field to a list of values with the choices option to limit open-ended values and reduce disparate data and errors (e.g. a list of states ["CA","AR"], instead of letting users introduce Ca, Cali, ar or Arizona). Listing 7-8 illustrates an example of a Django model field that uses the choices option.

Listing 7-8 Django model choices option

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

class Menu(models.Model):
    name = models.CharField(max_length=30)

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 7-8. The ITEMS_SIZES tuple 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 7-8 you can see the size field is assigned the choices=ITEM_SIZES value which tells Django to use the keys from ITEM_SIZES 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.

Tip The next chapter covers Django form models with the choices option.

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. For example, in listing 7-7 if you change name=models.CharField(max_length=30) to name=models.CharField(max_length=30, unique=True) it tells Django to ensure all Store records have a unique name value.

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 7-7 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 allow only one record with the same name and date_lastupdated (e.g. you can't have two records with name="Downtown" and date_lastupdated="2018-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 and 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.

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.

Form values: editable, help_text, verbose_name and error_messages

When Django models are used in the context of forms, form fields backed by Django model fields can be influenced through various model options. The following options are identical to the form options you learned in the previous chapter on Django forms, except these options are assigned as part of model fields to influence forms fields.

By default, all Django model fields presented in forms 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 7-9 illustrates the use of the help_text option.

Listing 7-9 Django model help_text option

ITEM_SIZES = (
            ('S','Small'),
            ('M','Medium'),
            ('L','Large'),
            ('P','Portion'),
            )
	    
class Menu(models.Model):
    name = models.CharField(max_length=30)

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 
                                             <b>size</b> of the item""")

In listing 7-9 you can see there are two fields with the help_text option. When a Django form based on this Django model is rendered in a template, the help_text defined in the model goes on to form part of the final form layout.

By default, a Django model field name is used to generate the header of a Django form field using a capitalized version of the model field name. (e.g. Name, Description). You can configure more verbose form field headers with the verbose_name option (e.g. models.CharField (max_length=30,verbose_name="ITEM NAME"), outputs the ITEM NAME header for the form field.

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, so 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 codes and values error messages. See the previous chapter on Django forms for detailed examples on the structure of the error_messages dictionary.

Database Definition Language (DDL) values: 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 database 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) generates a database index for the size field). Be aware that under the following circumstances Django automatically creates a database index, so db_index=True for the following scenarios is redundant:

Tip The meta indexes option can also define indexes for a single or multiple fields. See the following section on 'Meta class options' for more details.

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 value in settings.py to determine a tablespace, but if a field uses the db_tablespace this value takes precedence. If a database brand doesn't support tablespaces (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 data enforcement options for Django models described in previous sections (e.g. unique, default) Django models also offers enforcement of values through the validators option, which allow more advanced validation logic through built-in or custom methods.

Django model offers a series of built-in validator methods in the django.core.validators package which are used by model data types. 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.

In addition to Django built-in validator methods, you can also create custom validator methods. The only requirements to create custom validator methods, are for a method to accept a model field's input and to throw a django.core.exceptions.ValidatorError in case the value doesn't comply with the expected rules. Listing 7-10 illustrates a model that uses both a built-in validator and a custom validator.

Listing 7-10. 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

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 Menu(models.Model):
    name = models.CharField(max_length=30)

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 7-10 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 7-10 uses the custom calorie_watcher validator method to enforce the values for the calories field fit a certain range and uses custom messages in case this range is not met. It's worth mentioning that it's possible to use multiple validator methods on a single field with a list syntax (e.g.validators=[MinLengthValidators(5),MaxLengthValidators(100)]).

  1. https://docs.djangoproject.com/en/1.11/topics/migrations/#serializing-values