Django for Beginners #2

You can download the source code for this tutorial here.

The model is one of the best features of Django. For a framework like Laravel, you need to create both a model and a migration file. The migration file is a schema for the database, which describes the structure (column names and column types) of the database. The model defines relations and handles the data retrieving based on that schema.

But for Django, you only need a model, and the corresponding migration files can be generated with a simple command, which will save you a lot of time.

Models in Django

Generally, each model corresponds to a migration file, which corresponds to a database table. Here is a very basic example:

1
2
3
4
5
from django.db import models

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

We can generate a migration file using the following command:

1
python manage.py makemigrations

And the generated migration file should look like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Generated by Django 4.0.1 on 2022-01-16 18:54

from django.db import migrations, models


class Migration(migrations.Migration):

    initial = True

    dependencies = [
    ]

    operations = [
        migrations.CreateModel(
            name='Person',
            fields=[
                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('first_name', models.CharField(max_length=30)),
                ('last_name', models.CharField(max_length=30)),
            ],
        ),
    ]

The migration file is a schema which describes how the database table should look like, we can use the following command to apply this schema:

1
python manage.py migrate

As a beginner, you should never try to edit or delete the migration files, just let Django do everything for you unless you absolutely know what you are doing. In most cases, Django will be able to tell the changes you’ve made in the models and generate the migrations accordingly.

In our example, this model will create a database table person, and inside the table, there will be three columns each named id, first_name and last_name. The id column is created automatically, as you can see in the migration file. The id column is by default used as the primary key for indexing.

CharField() is called a field type and it defines the type of the column. max_length is called a field option, and it specifies extra information about that column. You can find a reference of all field types and field options here (https://docs.djangoproject.com/en/4.0/ref/models/fields/).

Fields

To save us some time, I will only introduce some most commonly used field types.

Field Type Description
BigAutoField Creates an integer column that automatically increments. Usually used for the id column.
BooleanField Creates a Boolean value column, with values True or False.
DateField and DateTimeField As their names suggest, adds dates and times.
FileField and ImageField Creates a column that stores the path, which points to the uploaded file or image.
IntegerField and BigIntegerField Integer has values from -2147483648 to 2147483647. Big integer has values from -9223372036854775808 to 9223372036854775807
SlugField Slug is usually a URL-friendly version of the title/name.
CharField and TextField CharField and TextField both create a column for storing strings, except TextField corresponds to a larger text box in the Django admin, which we’ll talk about later.

And field options.

Field Option Description
blank Allows this field to have an empty entry.
choices Gives this field multiple choices, you’ll see how this works after we get to Django Admin.
default Gives the field a default value.
unique Makes sure that every item in the column is unique. Usually used to slug and other fields that are supposed to have unique values.

Makes sure that every item in the column is unique. Usually used to slug and other fields that are supposed to have unique values.

Meta Options

We can also add a Meta class inside the model class, which contains extra information about this model, such as database table name, ordering options and human-readable singular and plural names. This could be a little difficult to understand right now but don’t worry, we’ll come back to this subject with a real example after we get to Django admin.

1
2
3
4
5
6
class Ox(models.Model):
    horn_length = models.IntegerField()

    class Meta:
        ordering = ["horn_length"]
        verbose_name_plural = "oxen"

Model Methods

Model methods are functions defined inside the model class. These functions allow us to perform some custom actions to the current instance of the model object. For example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class Person(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    birth_date = models.DateField()

    def baby_boomer_status(self):
        "Returns the person's baby-boomer status."
        import datetime
        if self.birth_date < datetime.date(1945, 8, 1):
            return "Pre-boomer"
        elif self.birth_date < datetime.date(1965, 1, 1):
            return "Baby boomer"
        else:
            return "Post-boomer"

When the baby_boomer_status() method is called, Django will examine the person’s birth date and return the person’s baby-boomer status.

Objects, methods and properties are some very important concepts in programming languages, and I talked about this in this article (JavaScript Basics #3: Arrays and Objects). However, if you need a Python-specific reference, consider reading this article (Python Classes and Objects).

Model Inheritance

In most web applications, we’ll need more than one model, and some of them will have common fields. In this case, we can create a “parent” model which contains the common fields, and make other models inherit the “parent” model.

1
2
3
4
5
6
7
8
9
class CommonInfo(models.Model):
    name = models.CharField(max_length=100)
    age = models.PositiveIntegerField()

    class Meta:
        abstract = True

class Student(CommonInfo):
    home_group = models.CharField(max_length=5)

Notice that CommonInfo is marked as an abstract model, which means this model doesn’t really correspond to an individual model, instead it is used as a “parent” to other models.

To verify this, generate a new migration file:

1
python manage.py makemigrations
 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
# Generated by Django 4.0.1 on 2022-01-18 18:08

from django.db import migrations, models


class Migration(migrations.Migration):

    initial = True
    
    dependencies = [
    ]
    
    operations = [
        migrations.CreateModel(
            name='Student',
            fields=[
                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('name', models.CharField(max_length=100)),
                ('age', models.PositiveIntegerField()),
                ('home_group', models.CharField(max_length=5)),
            ],
            options={
                'abstract': False,
            },
        ),
    ] 

As you can see, only one table, Student, is created.

Relations

So far, we’ve only talked about how to create individual tables, however, in most applications, these tables aren’t entirely independent, there are usually relations between different tables. For instance, we could have a category that has multiple posts, a post that belongs to a specific user, etc. So how can we describe these relations in Django?

There are primarily three types of database relations, one-to-one, many-to-one and many-to-many.

One-to-one Relation

The one-to-one relation should be the easiest. For example, each person could have one phone, and each phone could belong to one person. We can describe this relation in the models like this:

1
2
3
4
5
6

class Person(models.Model):
    name = models.CharField(max_length=100)

class Phone(models.Model):
    person = models.OneToOneField('Person', on_delete=models.CASCADE)

The OneToOneField field type works just like any other field type, except it takes at least two arguments. The first argument is the name of the model with whom this model has a relationship. And the second argument is on_delete, which defines the action Django will take when data is been deleted. This has more to do with SQL than Django, so we are not going into this topic, but if you are interested, here are some available values for on_delete (https://docs.djangoproject.com/en/4.0/ref/models/fields/#django.db.models.ForeignKey.on_delete).

Let’s now generate and apply migrations for these models and see what happens:

1
2
3

python manage.py makemigrations
python manage.py migrate

Open the SQLite database with DB Browser for SQLite.

One to one relation database setup

As you can see, the OneToOneField created a person_id column inside the phone table, and this column will store the id of the person that owns this phone.

Many-to-one Relation

Each category can have many posts, and each post belongs to one category. This relation is referred to as a Many-to-one relation.

1
2
3
4
5
6

class Category(models.Model):
    name = models.CharField(max_length=100)

class Post(models.Model):
    category = models.ForeignKey('Category', on_delete=models.CASCADE)

ForeignKey will create a category_id column in the post table which stores the id of the category that this post belongs to.

Many-to-many Relation

Many-to-many relation is slightly more complicated. For example, every article could have multiple tags, and each tag could have multiple articles.

1
2
3
4
5
6

class Tag(models.Model):
    name = models.CharField(max_length=100)

class Article(models.Model):
    tags = models.ManyToManyField('Tag')

Instead of creating a new column, this code will create a new table called article_tags, which contains two columns, article_id and tag_id. This allows you to locate all the tags associated with a particular article, and vice versa.

To make things clearer, imaging we have a table like this:

post_id tag_id
1 1
2 1
3 2
1 2
2 3

For a post with id=1, there are two tags, each with id=1 and id=2. If we want to do things backward, find posts through a tag, we can see that for a tag with id=2, there are two posts, each with id=3 and id=1.

The view layer is one of the most important components in a Django application, it is where we write all the backend logic. The most common thing we do with the view is to retrieve data from the database through the corresponding model, process the retrieved data, put them in the corresponding location in the template, and finally, render and return that template back to the user.

Of course, retrieving data is not the only thing we can do. In most web applications, there are four most basic operations we can do with a model, create, read, update and delete, often referred to as CRUD. We are going to investigate all of them in this tutorial.

We’ve already talked about models in the previous article, but we still don’t know how to retrieve or store data using the model. Django offers us a very simple API that allows us to perform all of these operations through models, and that’s where we are going to start in this article.

QuerySet

Suppose that this is our model:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class Category(models.Model):
    name = models.CharField(max_length=100)


class Tag(models.Model):
    name = models.CharField(max_length=200)


class Post(models.Model):
    title = models.CharField(max_length=255)
    content = models.TextField()
    pub_date = models.DateField()
    category = models.ForeignKey(Category, on_delete=models.CASCADE)
    tags = models.ManyToManyField(Tag) 

Creating and Saving Objects

Let’s say we are trying to create a new category, this is what we can do:

1
2
3
4
5
6
7
8
# import the Category model
from blog.models import Category

# create a new instance of Category
category = Category(name="New Category")

# save the newly created category to database
category.save()

This should be very easy for you to understand if you are familiar with the concept of object-oriented programming. If not, here is an article on that subject (https://realpython.com/python3-object-oriented-programming/).

In this example, we simply created a new instance of the Category object, and used the save() method which belongs to that object to save the information to the database.

Now, what about relations? For example, there is a many-to-one relationship between category and post, which we defined using the ForeignKey() field. We assume there are enough records in the database.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
from blog.models import Category, Post

# Post.objects.get(pk=1) is how we retrieve the post with pk=1,
# where pk stands for primary key, which is usually the id.
post = Post.objects.get(pk=1)

# retrieve the category with name="New Category"
new_category = Category.objects.get(name="New Category")

# assign new_category to the post's category field and save it
post.category = new_category
post.save()

There is also a many-to-many relation between posts and tags.

1
2
3
4
5
6
7
8
9
from blog.models import Tag, Post

tag1 = Tag.objects.get(pk=1)
tag2 = Tag.objects.get(pk=2)
tag3 = Tag.objects.get(pk=3)
tag4 = Tag.objects.get(pk=4)
tag5 = Tag.objects.get(pk=5)

post.tags.add(tag1, tag2, tag3, tag4, tag5)

Retrieving Objects

Retrieving objects are slightly more complicated than what we’ve just seen. Imagine we have thousands of records in our database. How do we find one particular record, if we don’t know the id? Or what if instead of one record, we want a collection of records that fits particular criteria?

QuerySet Methods

QuerySet methods allow us to retrieve data based on certain criteria. And they can be accessed using the attribute objects. We’ve already seen an example, get(), which is used to retrieve one particular record.

1
2
first_tag = Tag.objects.get(pk=1)
new_category = Category.objects.get(name="New Category")

We can also retrieve all records using the all() method.

1
Post.objects.all()

The all() method returns what we call a QuerySet, it is a collection of records. And we can further refine that collection by chaining a filter() or exclude() method.

1
Post.objects.all().filter(pub_date__year=2006)

This will return all the posts that are published in the year 2006. And pub_date__year is called a field lookup, we’ll discuss this topic in detail later.

Or we can exclude the posts that are published in the year 2006.

1
Post.objects.all().exclude(pub_date__year=2006)

Besides get(), all(), filter() and exclude(), there are lots of other QuerySet methods just like them. We can’t talk about all of them here, but if you are interested, here is a full list of all QuerySet methods (https://docs.djangoproject.com/en/4.0/ref/models/querysets/#methods-that-return-new-querysets).

Field Lookups

Field Lookups are the keyword arguments for methods get(), filter() and exclude(). If you are familiar with SQL clauses, they work just like the SQL WHERE clause. And they take the form fieldname__lookuptype=value. Notice that it is a double underscore in between.

1
Post.objects.all().filter(pub_date__lte='2006-01-01')

In this example, pub_date is the field name, and lte is the lookup type, which means less than or equal to. This code will return all the posts WHERE the pub_date is less than or equal to 2006-01-01.

Here is a list of all field lookups you can use (https://docs.djangoproject.com/en/4.0/ref/models/querysets/#field-lookups).

Field lookups can also be used to find records that have some kind of relationship with the current record. For example:

1
Post.objects.filter(category__name='Django')

This code will return all posts that belong to the category whose name is "Django".

This works backwards too. For instance, we can return all the categories that own at least one post whose title contains the word "Django".

1
Category.objects.filter(post__title__contains='Django')

We can go across multiple relations as well.

1
Category.objects.filter(post__author__name='Admin')

This will return all categories that own posts that are published by the user Admin. In fact, you can chain as many relationships as you want.

Deleting Objects

The method that we use to delete a record is conveniently named delete(). The following code will delete the post that has pk=1.

1
2
post = Post.objects.get(pk=1)
post.delete()

We can also delete multiple records together.

1
Post.objects.filter(pub_date__year=2005).delete()

This will delete all posts that are published in the year 2005.

However, what if the record we are deleting has some relation with another record. For example, here we are trying to delete a category that has multiple posts.

1
2
category = Category.objects.get(pk=1)
category.delete()

By default, Django emulates the behaviour of the SQL constraint ON DELETE CASCADE, which means all the posts that belong to this category will be deleted as well. If you wish to change that, you can change the on_delete option to something else. Here is a reference of all available options for on_delete (https://docs.djangoproject.com/en/4.0/ref/models/fields/#django.db.models.ForeignKey.on_delete).

View Basics

Manipulating data is about everything that we do inside a view function, but there is a little bit more. Here is an example of a view function. In Django, all the views are written inside the views.py file.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
from django.shortcuts import render
from blog.models import Post

# Create your views here.
def my_view(request):
    posts = Post.objects.all()

    return render(request, 'blog/index.html', {
        'posts': posts,
    })

There are two things we need to pay attention to in this example.

First, notice that this view function takes an input request. This variable request is an HttpRequest object, and it is automatically passed to the view from our URL dispatcher. If you are not familiar with HTTP, please go through this article first.

The request contains a lot of information about the current HTTP request. For example, we can access the HTTP request method and write different codes for different methods.

1
2
3
4
if request.method == 'GET':
    do_something()
elif request.method == 'POST':
    do_something_else()

Here is a list of all the information you can access from the request (https://docs.djangoproject.com/en/4.0/ref/request-response/#httprequest-objects).

Second, notice that we imported a shortcut called render(), and we used the shortcut to pass a variable posts, which contains all the posts in the database to the template 'blog/index.html'.

This is called a shortcut because, by default, we are supposed to load the template with the loader() method, render that template with the retrieved data and return an HttpResponse object. Django simplified this process with the render() shortcut. To make life easier for you, I’m not going to talk about the complex way here, since we are not going to use it in this tutorial anyway.

Here is a list of all shortcut functions in Django (https://docs.djangoproject.com/en/4.0/topics/http/shortcuts/).


comments powered by Disqus