Click to share! ⬇️

Models In Django

Now that we know about routing in Django and how URLs map to views and view functions that send back Html templates to the user, we can now learn about Models in Django. Models introduce the concept of using dynamic data from a database to power the front end of the website that the user sees and interacts with. For example in our small blog application, we want to store a collection of posts in the database. Models in Django are a Class that represents a table in a database. Each type of data, like Posts or Users, is represented by its own model. A Model maps to a single table in the database.


Creating A Post Model

One of the files that were created when we ran the python manage.py startapp posts command is the models.py file. Django created this file for us so we can define one or more Models in this python file.

django modelspy file

To define a Model in this file, we can add a Python Class.

posts/models.py

from django.db import models


# Create your models here.
class Post(models.Model):
    title = models.CharField(max_length=100)
    slug = models.SlugField()
    body = models.TextField()
    date = models.DateTimeField(auto_now_add=True)

Django has many field types for Models. In the code above we made use of four different field types. We have a CharField, SlugField, TextField, and a DateTimeField. Each field defined in a Model is associated with a given widget that will be displayed either in the admin area or the front end of the website where the user can interact with it. The default form widget for a CharField is a TextInput. The default form widget for a TextField is a Textarea. The default form widget for a DateTimeField is a single TextInput. The admin uses two separate TextInput widgets with JavaScript shortcuts.


Django Migrations

The first step for working with a Model in Django is to create the Model class in the models.py file. We took that step in the section above. Migrations take the step of mapping the Model class to a table in the database. Before we create and run our own migration, we need to look at the other migrations that Django has already created. You might have noticed that when you run the python manage.py runserver command in your terminal, a message appears about unapplied migrations.

djangoblog $python manage.py runserver
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).

You have 17 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.

Django version 3.0.3, using settings 'djangoblog.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.

So let’s go ahead and migrate the built-in migrations first by running the python manage.py migrate command.

djangoblog $python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying sessions.0001_initial... OK

For your own Models, you first need to create a migration file or files. A migration file tracks any changes you make to a model. So any time you make an update to a model, you need to also update the migration. We’ll see this in action as we progress further along but right now let’s get started using the python manage.py makemigrations command.

djangoblog $python manage.py makemigrations
Migrations for 'posts':
  postsmigrations001_initial.py
    - Create model Post

As we can see from the output, Django recognized that we had defined a Post Model. Django read that file and it’s Model definition, then created a new migrations file named 0001_initial.py for us. Pretty cool!

django model migration generation

This means that we now once again have unapplied migrations. In fact, if we once again run the server, now we get the message that we have 1 unapplied migration.

djangoblog $python manage.py runserver
Watching for file changes with StatReloader
Performing system checks...
System check identified no issues (0 silenced).

You have 1 unapplied migration(s). Your project may not work properly until you
apply the migrations for app(s): posts.
Run 'python manage.py migrate' to apply them.

Django version 3.0.3, using settings 'djangoblog.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.

We can quit the server now, and run the python manage.py migrate command like so.

djangoblog $python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, posts, sessions
Running migrations:
  Applying posts.0001_initial... OK

Excellent! Our own custom Post Model is now migrated. Our Python Class Model is now mapped to a table in the database. Now, any time we make a new model or change an existing model, we need to go through the same process. In other words, we need to first run python manage.py makemigrations, and then we need to run python manage.py migrate.


The Django ORM

The Django ORM is a great way to interact with our database. The object-relational mapper is what bridges the gap between the code and the database. The ORM provides full create, read, update, and delete functionality all right through the Model we have defined. We can start simply by using the interactive shell in Django before we get to place the code in our application.

The Django Shell

Python itself has a shell where you can simply type python and click Enter to get a prompt where you can enter Python code to execute. This is not that shell and its a fairly important distinction. If you want to use the shell with Django, you need to make sure to use the python manage.py shell command from the root of the Django project. If you try to use the regular Python shell, you will encounter errors when trying to interact with your Models. So when you run the Django shell, you’ll see something like this.

djangoblog $python manage.py shell
Python 3.8.0 (tags/v3.8.0:fa919fd, Oct 14 2019, 19:21:23) [MSC v.1916 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>>

To start working with the Model we created, type the following.

>>> from posts.models import Post
>>> Post

First, we import the Post model from the models.py file that lives in the posts app. Then we just type Post at the command line, and we see it is an instance of the Post model class. Since this class inherits from the models.Model in Django, this means we have access to all kinds of methods to work with the database.

Insert a Post

There are not any posts in the database yet, so let’s go ahead and enter a new post into the database using the ORM. Here is how to do that.

>>> post = Post()
>>> post.title = 'A New Title!'
>>> post.save()

There are more fields to populate, but we just wanted to have a look at using that .save() method to save something to the database.

Fetch All Records

Now that there is one post in the database, we should be able to query the database from the terminal and see a result. Let’s try that now.

>>> Post.objects.all()
]>

Sure enough, we see that there is one result in the QuerySet, and that is the post that we just saved.

Fetching the first record

We only have one record in the database, but to explicitly fetch just the first record, we can do so with this syntax.

>>> Post.objects.all()[0]

As you can see, this returns one instance of a Post, not a QuerySet. Taking it further, we can even drill down to specific fields on the table using this syntax.

>>> Post.objects.all()[0].title
'A New Title!'

So this is pretty neat, we’ve created a Model, entered a new Model into the database, and retrieved that Model from the database all using the Django ORM. We didn’t need to manually write any SQL at all, Django handled all of that for us.

Defining An __str__ function

In order to make our Models a bit more user-friendly, we can add a new function that will determine how a Post will look both in the admin section and in the shell. So this function outputs the string version of the Post not as an object but the title of the instance.

from django.db import models


# Create your models here.
class Post(models.Model):
    title = models.CharField(max_length=100)
    slug = models.SlugField()
    body = models.TextField()
    date = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.title

Let’s try working with the Post Model once again in the interactive shell.

>>> from posts.models import Post
>>> Post.objects.all()
]>

Pretty neat! Now when we fetching all posts using Post.objects.all(), we see the title right in the query set. If we had more posts in the database, we would see those titles as well. In fact, we can add another post with a different title, and then perform the same query to see the result.

>>> post = Post()
>>> post.title = 'Second Post!'
>>> post.save()
>>> Post.objects.all()
, ]>

Interacting With Models Using Admin

We have learned how to configure the Django admin area, so let’s go ahead and enable it on this project as well. Recall the steps to do this involve first creating a superuser with the manage.py file with the command python manage.py createsuperuser.

djangoblog $python manage.py createsuperuser
Username (leave blank to use 'myusername'): admin
Email address: admin@example.com
Password:
Password (again):
Error: Blank passwords aren't allowed.
Password:
Password (again):
Superuser created successfully.

Once you have a superuser created, go ahead and visit the http://127.0.0.1:8000/admin address in a web browser. This forwards to http://127.0.0.1:8000/admin/login/?next=/admin/ and provides the login option we see here. Don’t forget to run python manage.py runserver after creating the superuser.

django admin login

Once you provide the credentials you need, you’ll see you are now logged in.

django superuser logged in

Making Apps Visible In Admin

When first logging into the Admin area for Django, you’ll only see the ability to work with Groups and Users. To tell Django that we want the Posts app to show up in the admin area, we can register our model in the admin.py file of the application. Here is how to do that.

from django.contrib import admin
from .models import Post

# Register your models here.
admin.site.register(Post)

The highlighted code above is what we need to manually add to admin.py. The first highlighted line imports the Post Model so we can use it. Then on the second highlighted line, all we have to do is register this Model so it appears in the admin interface. Sure enough, we can visit the admin interface again and now we have the ability to interact with the Post model.

register model app for admin Django

We had also set up the __str__ method in the Post model. This method changes the string representation of a Post object to use the Post title, rather than the nondescriptive default of something like Object. This is why we see the title of each Post now in the admin interface.

__str__ in admin Django

Now it is easy to interact with the Post Model using the Admin Interface in Django.

post model django

Just for grins and practice, we can go back and test out querying the database using the Django ORM one more time.

djangoblog $python manage.py shell
Python 3.8.0 (tags/v3.8.0:fa919fd, Oct 14 2019, 19:21:23) [MSC v.1916 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from posts.models import Post
>>> post1 = Post.objects.all()[0]
>>> post1.title
'A New Title!'
>>> post1.slug
'a-new-title'
>>> post1.body
'This is the first Post in the Django blog system.  We could have added the slug and body fields using the interactive shell, but this is an example of how to interact with your Model using the Django Admin Interface.'
>>> post2 = Post.objects.all()[1]
>>> post2.title
'Second Post!'
>>> post2.slug
'second-post'
>>> post2.body
'This is the second post of the Blog system in Django.  It was created using the Post model.  There are all kinds of things we could add to this post, but alas, here it is.'

Fetch All Models and Send To Template

Right now if we visit the base URL for the posts app at http://localhost:8000/posts/, we are still seeing the empty placeholder Html. This is good, we have URL routing setup correctly, and our view is correctly rendering a template for display. This is static data, however. We now have some dynamic data in the database thanks to our Post model. So let’s add the ability to fetch all records using the Django ORM, and then pass those posts to the template for display. To begin this process we need to edit the views.py file in the posts app.

posts/views.py

from django.shortcuts import render
from .models import Post


def post_list(request):
    posts = Post.objects.all().order_by('date')
    return render(request, 'posts/post_list.html', {'posts': posts})

There are three highlighted lines of code above, let’s explain what each line does one at a time.

  • The first highlighted line of code imports the Post model into this file so we can make use of it.
  • The second highlighted line of code uses the Django ORM to fetch all Post records in the database while also sorting them by date. This ensures the posts are in order when we display them.
  • The third highlighted line of code is there to emphasize how we pass data from a view to a template in Django. The dictionary that we include as an argument to the render() function is how we pass data from the view to the template.

Leveraging Template Tags in Django

Now that we are passing some dynamic data to our Django Template file, we can make use of template tags in Django to render out this dynamic data in the template file. Recall that there are two types of template tags in Django. We have the {% %} tags for our control structures, and the {{ }} tags for outputting data in the template.

posts/templates/posts/post_list.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Posts</title>
</head>
<body>
<h1>Posts List</h1>

{% for post in posts %}
    <h2>{{ post.title }}</h2>
    <h3>{{ post.body }}</h3>
    <h4>{{ post.slug }}</h4>
    <hr>
{% endfor %}

</body>
</html>

Now we see our posts when visiting the http://localhost:8000/posts/ URL instead of the static page.

index page for posts app

Adding A Custom Model Method

We can add methods to the Model class to provide specific functionality. One such example might be adding a snippet() function that displays only the first 70 characters of the body text. Then, at a later time, we can add a details page that each post can link to in order to view the full text.

posts/models.py
We can modify the Post model like so.

from django.db import models


# Create your models here.
class Post(models.Model):
    title = models.CharField(max_length=100)
    slug = models.SlugField()
    body = models.TextField()
    date = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.title

    def snippet(self):
        return self.body[0:70]

posts/templates/posts/post_list.html
Then in the template, we can output the title, snippet, and slug.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Posts</title>
</head>
<body>
<h1>Posts List</h1>

{% for post in posts %}
    <h2>{{ post.title }}</h2>
    <h3>{{ post.snippet }}...</h3>
    <h4>{{ post.slug }}</h4>
    <hr>
{% endfor %}

</body>
</html>

The result is a shortened body text which only displays the first 70 characters. Pretty cool!

django custom model method

Learn More

Models In Django Summary

A model in Django is a single source of information about the data in your application. It contains all the essential fields and associated behaviors of the data you’re storing. In general, each model maps to a single database table. Each model is a Python class that subclasses django.db.models.Model. Each attribute of the model represents a database field. Once you set up your Models correctly, Django gives you an automatically-generated database-access API using Object Relational Mapper technology.

Click to share! ⬇️