Django web applications access and manage data through Python objects referred to as models.

Models define the structure of stored data, including the field types and possibly also their maximum size, default values, selection list options, help text for documentation, label text for forms, etc.

Models are usually defined in an application’s models.py file. They are implemented as subclasses of django.db.models.Model, and can include fields, methods and metadata.

Designing the Models

Before you jump in and start coding the models, it’s worth taking a few minutes to think about what data we need to store and the relationships between the different objects.

Here, for example, we need to store information about movies.

Let us take the Aquaman for example,

1
2
3
4
5
6
7
8
9
10
11
12
13
Title: Aquaman	[海王]
Poster: https://img1.doubanio.com/view/photo/s_ratio_poster/public/p2541280047.webp
Release Date: 2018 7 December(China) [2018-12-07(中国大陆)/2018-12-21(美国)]
Director: James Wan [温子仁]
Summary: Arthur Curry learns that he is the heir to the underwater kingdom of Atlantis, and must step forward to lead his people and be a hero to the world.
[许多年前,亚特兰蒂斯女王...宿命推动着亚瑟,去寻找失落已久的三叉戟,建立一个更加开明的海底王国……]
Genre: Action, Adventure, Fantasy [动作/奇幻/冒险]
Language: English(mainly) [英语/...]
Time: 2h 23min(143 min) [143分钟]
Rating_IMDB: 7.6(IMDB)
Rating_Douban: 7.8(Douban)
URL_IMDB: https://www.imdb.com/title/tt1477834/
URL_Douban: https://movie.douban.com/subject/3878007/

At the first time, we won’t include them all, coz we need to make the concepts more concise and the code more clear, we will take these attributes:

Title,poster,release date,director,summarygenre,language,rating_IMDB,url_IMDB

And Django allows you to define relationships that are one to one (OneToOneField), one to many (ForeignKey) and many to many (ManyToManyField).

The field name is used to refer to it in queries and templates.

Fields also have a label, which is either specified as an argument (verbose_name) or inferred by capitalising the first letter of the field’s variable name and replacing any underscores with a space (for example my_field_name would have a default label of My field name).

Edit the apps/models.py file so it looks like this:

1
2
3
4
5
6
7
8
9
10
11
class Genre(models.Model):
"""Model representing a movie genre."""

## Fields
## CharField will contain strings of alphanumeric characters,which is used to describe the genre (this is limited to 50 characters here).
## help_text provides a text label to display to help users know what value to provide when this value is to be entered by a user via an HTML form.
genre_name = models.CharField(max_length=50, help_text='Enter a movie genre (e.g. Adventure)')

def __str__(self):
"""String for representing the Question object (in Admin site etc.)."""
return self.name

This model is used to store information about the movie genres — for example whether it is Action or Adventure, Crime, Fantasy, Historical, Horror, Romance,Science fiction or Animation, etc.

At the end of the model we declare a __str__() method, which simply returns the name of the genre defined by a particular record. No verbose name has been defined, so the field will be called Name in forms.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
from django.db import models
from django.urls import reverse ## Used to generate URLs by reversing the URL patterns
from django.utils import timezone

class Movie(models.Model):
"""Model representing a movie."""

movie_title = models.CharField(max_length = 200, help_text = 'Plz enter your movie\'s name.')

## We will explain this later
movie_poster = models.ImageField(upload_to = 'mirages/', default = "https://img1.doubanio.com/view/photo/s_ratio_poster/public/p2541280047.jpg")

movie_summary = models.TextField(max_length = 2000, blank = True, help_text='Enter a brief description of the movie.')

movie_rating_imdb = models.DecimalField('IMDB Rating', max_digits = 3, decimal_places = 1, help_text = 'Rating from 1 to 10 <a href="https://www.imdb.com/">IMDB</a>')

movie_url_imdb = models.URLField('IMDB Movie URL', blank = True, help_text = 'The IMDB movie url <a href="https://www.imdb.com/">IMDB</a>')
## ManyToManyField used because genre can contain many movies. Movies can cover many genres.
genre = models.ManyToManyField(Genre, help_text = 'Select a genre for this movie.')

director = models.ForeignKey(
'Director', on_delete = models.SET_NULL, null = True)

language = models.ForeignKey(
'Language', on_delete = models.SET_NULL, null = True)

movie_release_date = models.DateField(
'Release Date', default = timezone.now)

## Metadata
class Meta:
## character fields are sorted alphabetically, while date fields are sorted in chronological order
ordering = ['-release_date','movie_title']
## a verbose name for the class in singular and plural form, will show in the admin site
verbose_name = 'movie'
verbose_name_plural = 'movies'

## Minimally, in every model you should define the standard Python class method __str__() to return a human-readable string for each object. To represent individual records in the administration site (and anywhere else you need to refer to a model instance).
def __str__(self):
return self.movie_title

## This method returns a URL for displaying individual model records on the website (if you define this method then Django will automatically add a "View on Site" button to the model's record editing screens in the Admin site).
def get_absolute_url(self):
"""Returns the url to access a particular instance of this movie."""
return reverse('movie-detail', args = [str(self.id)])

The movie model represents all information about an available movie in a general sense.

The genre is a ManyToManyField, so that a movie can have multiple genres and a genre can have many books. The director and language are declared as ForeignKey, so each movie will only have one director and one language (in practice a movie might have many directors and many languages, but not in this implementation.)

The final method, get_absolute_url() returns a URL that can be used to access a detail record for this model

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import uuid # Required for unique movie watching status instances    

class Rating(models.Model):
"""Model representing a specific watching status of a movie."""
id = models.UUIDField(primary_key=True, default=uuid.uuid4, help_text='Unique ID for this particular movie watching status')

movie = models.ForeignKey('Movie', on_delete=models.CASCADE,verbose_name="the related movie")

rating_movie = models.IntegerField(
default = 7,
validators=[
MaxValueValidator(10),
MinValueValidator(1)
])

rating_time = models.DateTimeField(auto_now_add=True, editable=False)

watching_status = (
('n','Not interested'),
('a', 'Add to Watchlist'),
('h', 'Have Watched'),
('w', 'Waiting / Watching'),
)

rating_status = models.CharField(
max_length=1,
choices=watching_status,
default='n',
)

rating_comment = models.CharField(max_length=250, blank=True)

class Meta:
ordering = ['-rating_time']

def __str__(self):
"""String for representing the Model object."""
"""string interpolation syntax (also known as f-strings)"""
"""older expression=='{0} ({1})'.format(self.id,self.movies.title)).
'{0} ({1})'.format(self.id,self.book.title))."""
return f'{self.id} ({self.movies.title})'

  • UUIDField is used for the id field to set it as the primary_key for this model. This type of field allocates a globally unique value for each instance (one for every user movie watching status you can find in the library).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Director(models.Model):

"""Model representing an author."""
director_name = models.CharField(max_length=150)
director_last_name = models.CharField(max_length=150)

class Meta:
ordering = ['last_name', 'first_name']

def get_absolute_url(self):
"""Returns the url to access a particular author instance."""
return reverse('director-detail', args=[str(self.id)])

def __str__(self):
"""String for representing the Model object."""
return f'{self.last_name}, {self.first_name}'

class Language(models.Model):
"""Model representing a language."""
language_name = models.CharField(
max_length=100, help_text="Plz enter the main language of this movie.")

class Meta:
ordering = ['language']

def get_absolute_url(self):
"""Returns the url to access a particular language instance."""
return reverse('language-detail', args=[str(self.id)])

def __str__(self):
"""String for representing the Model object."""
return f'{self.language}'

Common field arguments

The following common arguments can be used when declaring many/most of the different field types:

  • help_text: Provides a text label for HTML forms (e.g. in the admin site), as described above.

    Note that this value is not HTML-escaped in automatically-generated forms. This lets you include HTML if you so desire. For example:

    1
    help_text="Please use the following format: <em>YYYY-MM-DD</em>."
  • verbose_name: A human-readable name for the field. If the verbose name isn’t given, Django will automatically create it using the field’s attribute name, converting underscores to spaces.

    In this example, the verbose name is "person's first name":

    1
    first_name = models.CharField("person's first name", max_length=30)
  • validators: A list of validators to run for this field.

    A validator is a callable that takes a value and raises a ValidationError if it doesn’t meet some criteria. Validators can be useful for re-using validation logic between different types of fields.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    from django.core.exceptions import ValidationError
    from django.utils.translation import gettext_lazy as _

    def validate_even(value):
    if value % 2 != 0:
    raise ValidationError(
    _('%(value)s is not an even number'),
    params={'value': value},
    )
    class MyModel(models.Model):
    even_field = models.IntegerField(validators=[validate_even])

    See the validators documentation for more information.

  • default: The default value for the field. This can be a value or a callable object, in which case the object will be called every time a new record is created.

    The default can’t be a mutable object (model instance, list, set, etc.), as a reference to the same instance of that object would be used as the default value in all new model instances. Instead, wrap the desired default in a callable. For example, if you want to specify a default dict for JSONField, use a function:

    1
    2
    3
    4
    def contact_default():
    return {"email": "to1@example.com"}

    contact_info = JSONField("ContactInfo", default=contact_default)
  • null: If True, Django will store blank values as NULL in the database for fields where this is appropriate (a CharField or TextField will instead store an empty string). The default is False.

    When a CharField has both unique=True and blank=True set. In this situation, null=True is required to avoid unique constraint violations when saving multiple objects with blank values.

  • blank: For both string-based and non-string-based fields, you will need to set blank=True if you wish to permit empty values in forms, as the null parameter only affects database storage .

    This is often used with null=True , because if you’re going to allow blank values, you also want the database to be able to represent them appropriately.

    The default is False, which means that Django’s form validation will force you to enter a value, and the field value will be required.

  • choices: An iterable (e.g., a list or tuple) consisting itself of iterables of exactly two items (e.g. [(A, B),(A, B) ...]) to use as choices for this field.

    The first element in each tuple is the actual value to be set on the model, and the second element is the human-readable name.

    For example:

    1
    2
    3
    4
    5
    6
    YEAR_IN_SCHOOL_CHOICES = (
    ('FR', 'Freshman'),
    ('SO', 'Sophomore'),
    ('JR', 'Junior'),
    ('SR', 'Senior'),
    )

    Unless blank=False is set on the field along with a defaultthen a label containing "---------" will be rendered with the select box. To override this behavior, add a tuple to choices containing None; e.g. (None, 'Your String For Display'). Alternatively, you can use an empty string instead of None where this makes sense.

    Generally, it’s best to define choices inside a model class, and to define a suitably-named constant for each value:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    from django.db import models

    class Student(models.Model):
    FRESHMAN = 'FR'
    SOPHOMORE = 'SO'
    JUNIOR = 'JR'
    SENIOR = 'SR'
    GRADUATE = 'GR'
    YEAR_IN_SCHOOL_CHOICES = [
    (FRESHMAN, 'Freshman'),
    (SOPHOMORE, 'Sophomore'),
    (JUNIOR, 'Junior'),
    (SENIOR, 'Senior'),
    (GRADUATE, 'Graduate'),
    ]
    year_in_school = models.CharField(
    max_length=2,
    choices=YEAR_IN_SCHOOL_CHOICES,
    default=FRESHMAN,
    )

    def is_upperclass(self):
    return self.year_in_school in {self.JUNIOR, self.SENIOR}

    In addition, Django provides enumeration types that you can subclass to define choices in a concise way:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    from django.utils.translation import gettext_lazy as _

    class Student(models.Model):

    class YearInSchool(models.TextChoices):
    FRESHMAN = 'FR', _('Freshman')
    SOPHOMORE = 'SO', _('Sophomore')
    JUNIOR = 'JR', _('Junior')
    SENIOR = 'SR', _('Senior')
    GRADUATE = 'GR', _('Graduate')

    year_in_school = models.CharField(
    max_length=2,
    choices=YearInSchool.choices,
    default=YearInSchool.FRESHMAN,
    )

    def is_upperclass(self):
    return self.year_in_school in {
    self.YearInSchool.JUNIOR,
    self.YearInSchool.SENIOR,
    }
  • primary_key: If True, this field is the primary key for the model.

    If you don’t specify primary_key=True for any field in your model, Django will automatically add an AutoField to hold the primary key, so you don’t need to set primary_key=True on any of your fields unless you want to override the default primary-key behavior.

    primary_key=True implies null=False and unique=True. Only one primary key is allowed on an object.

  • unique: If True, this field must be unique throughout the table.

    This is enforced at the database level and by model validation. If you try to save a model with a duplicate value in a unique field, a django.db.IntegrityError will be raised by the model’ssave() method.

    Note: This option is valid on all field types except ManyToManyField and OneToOneField. When unique is True, you don’t need to specify db_index, because unique implies the creation of an index.

  • unique_for_date/unique_for_month/unique_for_year:

    Set this to the name of a DateField or DateTimeFieldto require that this field be unique for the value of the date field.

    For example, if you have a field title that has unique_for_date="pub_date", then Django wouldn’t allow the entry of two records with the same title and pub_date.

  • db_column:The name of the database column to use for this field. If this isn’t given, Django will use the field’s name.

  • db_index: If True, a database index will be created for this field.

  • editable: If False, the field will not be displayed in the admin or any other ModelForm. They are also skipped during model validation. Default is True.

  • error_messages:

    The error_messages argument lets you override the default messages that the field will raise. Pass in a dictionary with keys matching the error messages you want to override.

    Error message keys include null, blank, invalid, invalid_choice, unique, and unique_for_date. Additional error message keys are specified for each field in the Field typessection below.

    These error messages often don’t propagate to forms. See Considerations regarding model’s error_messages

There are many other options — you can view the full list of field options here.

Common field types

The following list describes some of the more commonly used types of fields.

  • CharField is used to define short-to-large-sized fixed-length strings. You must specify the max_length of the data to be stored.

    The default form widget for this field is a TextInput.

    1
    <input type="text" ...>
  • TextField is used for large arbitrary-length strings. You may specify a max_length for the field, but this is used only when the field is displayed in forms ( However it is not enforced at the model or database level.).

    Renders as: <textarea>...</textarea>

  • IntegerFieldis a field for storing integer (whole number) values, and for validating entered values as integers in forms.

    Values from -2147483648 to 2147483647 are safe in all databases .

    1
    2
    3
    4
    5
    6
    rating_movie = models.IntegerField(
    default = 7,
    validators=[
    MaxValueValidator(10),
    MinValueValidator(1)
    ])

    The default form widget for this field is a NumberInput when localize is False or TextInput otherwise.

  • BigIntegerField is A 64-bit integer, much like an IntegerField except that it is guaranteed to fit numbers from -9223372036854775808 to 9223372036854775807.

    The default form widget for this field is aTextInput

  • DecimalField is A fixed-precision decimal number, represented in Python by a Decimal instance.

    It has as two required arguments:

    DecimalField.max_digits

    The maximum number of digits allowed in the number. Note that this number must be greater than or equal to decimal_places.

    DecimalField.decimal_places

    The number of decimal places to store with the number.

    For example, to store numbers up to 999 with a resolution of 2 decimal places, you’d use:

    1
    models.DecimalField(..., max_digits=5, decimal_places=2)

    The default form widget for this field is a NumberInput when localize is False or TextInput otherwise.

  • DateField and DatetimeField are used for storing/representing dates and date/time information .

    These fields can additionally declare the (mutually exclusive) parameters auto_now=True (to set the field to the current date every time the model is saved),

    auto_now_add (to only set the date when the model is first created) , and default (to set a default date that can be overridden by the user).

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    from django.utils import timezone
    import datetime
    ...
    ## As currently implemented, setting auto_now or auto_now_add to True will cause the field to have editable=False and blank=True set.
    t_create = models.DateTimeField(verbose_name='Time Created', auto_now_add=True)
    t_update = models.DateTimeField(verbose_name='Time Modified', auto_now=True)

    ## If you want to be able to modify this field, set the following instead
    t_publish = models.DateTimeField(verbose_name='Time Published',default=timezone.now)
    t_publish_date = models.DateField(verbose_name='Date Published',default=datetime.date.today())

    The default form widget for this field is a DateInput which Renders as <input type=“text” …>

  • EmailField is used to store and validate email addresses.

    Renders as: <input type="email" ...>

  • FileField and ImageField are used to upload files and images respectively (the ImageField simply adds additional validation that the uploaded file is an image). These have parameters to define how and where the uploaded files are stored.

    It has two optional arguments:

    • FileField.upload_to

      This attribute provides a way of setting the upload directory and file name, and can be set in two ways.

      1
      2
      3
      4
      5
      6
      class MyModel(models.Model):
      # file will be uploaded to MEDIA_ROOT/uploads
      upload = models.FileField(upload_to='uploads/')
      # or...
      # file will be saved to MEDIA_ROOT/uploads/2015/01/30
      upload = models.FileField(upload_to='uploads/%Y/%m/%d/')

      You can also customize your the final destination path like this:

      1
      2
      3
      4
      5
      6
      7
      8
      def create_movie_path(instance, filename):
      ext = filename.split('.')[-1]
      filename = "%s_%s.%s" % (instance.user.id,instance.movie_name, ext)
      # file will be uploaded to MEDIA_ROOT/user_<id>/<filename>
      return 'user_{0}/{1}'.format(instance.user.id, filename)

      class MyModel(models.Model):
      upload = models.FileField(upload_to=create_movie_path)
    • FileField.storage

      A storage object, or a callable which returns a storage object. This handles the storage and retrieval of your files.

      say your MEDIA_ROOT is set to '/home/media', and upload_to is set to 'photos/%Y/%m/%d'. The '%Y/%m/%d' part of upload_to is strftime() formatting; '%Y' is the four-digit year, '%m' is the two-digit month and '%d' is the two-digit day. If you upload a file on Jan. 15, 2007, it will be saved in the directory /home/media/photos/2007/01/15.

  • SlugField is a short label for something, containing only letters, numbers, underscores or hyphens. They’re generally used in URLs.

    Like a CharField, you can specify max_length (read the note about database portability and max_length in that section, too). If max_length is not specified, Django will use a default length of 50.

  • ForeignKeyis a one-to-many relationship.

    Requires two positional arguments: the class to which the model is related and the on_deleteoption.

    (e.g. a car has one manufacturer, but a manufacturer can make many cars). The “one” side of the relationship is the model that contains the key.

    To create a recursive relationship – an object that has a many-to-one relationship with itself – use models.ForeignKey('self', on_delete=models.CASCADE).

    1
    2
    3
    4
    5
    from django.contrib.auth.models import User

    class Post(models.Model):
    ...
    author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='blog_posts')
  • ManyToManyField is used to specify a many-to-many relationship (e.g. a book can have several genres, and each genre can contain several books).

    These have the parameter on_delete to define what happens when the associated record is deleted (e.g. a value of models.SET_NULL would simply set the value to NULL).


  • AutoField is a special type of IntegerField that automatically increments. A primary key of this type is automatically added to your model if you don’t explicitly specify one.

  • BigAutoField is a 64-bit integer, much like an AutoField except that it is guaranteed to fit numbers from 1 to 9223372036854775807.

You can find more model references on Django Models.

MetaData

You can declare model-level metadata for your Model by declaring class Meta, as shown.

1
2
class Meta:
ordering = ['-my_field_name']

One of the most useful features of this metadata is to control the default ordering of records returned when you query the model type. You do this by specifying the match order in a list of field names to the ordering attribute, as shown above. As shown above, you can prefix the field name with a minus symbol (-) to reverse the sorting order.

So as an example, if we chose to sort movies like this by default:

1
ordering = ['-movie_release_date','movie_title']

Another common attribute is verbose_name, a verbose name for the class in singular and plural form:

1
2
verbose_name = 'Mymovie'
verbose_name_plural = 'Mymovies'

The attribute db_table is to set the name of the database table to use for the model:

1
db_table = 'music_album'

The full list of metadata options are available here: Model metadata options (Django docs).

Methods

A model can also have methods.

Minimally, in every model you should define the standard Python class method str() to return a human-readable string for each object. This string is used to represent individual records in the administration site (and anywhere else you need to refer to a model instance). Often this will return a title or name field from the model.

1
2
def __str__(self):
return self.movie_title

Another common method to include in Django models is get_absolute_url(), which returns a URL for displaying individual model records on the website (if you define this method then Django will automatically add a “View on Site” button to the model’s record editing screens in the Admin site). A typical pattern for get_absolute_url() is shown below.

1
2
3
def get_absolute_url(self):
"""Returns the url to access a particular instance of this movie."""
return reverse('movie-detail', args = [str(self.id)])

Note: Assuming you will use URLs like /myapplication/mymodelname/2 to display individual records for your model (where “2” is the id for a particular record), you will need to create a URL mapper to pass the response and id to a “model detail view” (which will do the work required to display the record). The reverse() function above is able to “reverse” your url mapper (in the above case named ‘model-detail-view’) in order to create a URL of the right format.

REFERENCES