Intro to the Django QuerySet API using a Todo List App

andrewrobles

Andrew Robles

Posted on March 29, 2023

Intro to the Django QuerySet API using a Todo List App

This tutorial is designed for people are are new to coding and have taken a few coding tutorials in any programming language.

Project Setup

Download and install the following required software if you don't already have them:

  cd ~/Documents
  git clone https://github.com/andrewrobles/to-do-project.git
Enter fullscreen mode Exit fullscreen mode
  • Open up the project in Visual Studio Code by going to File > Open and navigating to ~/Documents/to-do-project
  • Enable auto save by going to File > Auto Save
  • Open up a terminal window by going to View > Terminal
  • Start the project by running docker-compose up and opening the URL in a Google Chrome browser window provided in the terminal output - http://0.0.0.0:8000/
  • You should see something like this:

Install worked

Create the TodoItem Model

In the code/todo/models.py file we will define all objects called Models - this is a place in which we will define our to-do item.

Let's open the code/todo/models.py in the code editor, remove everything from it, and write code like this:

from django.db import models

class TodoItem(models.Model):
    text = models.CharField(max_length=200)
    done = models.BooleanField(default=False)
Enter fullscreen mode Exit fullscreen mode

All lines starting with from or import are lines that add some bits from other files. So instead of copying and pasting the same things in every file, we can include some parts with from ... import ...

class TodoItem(models.Model): - this line defines our model.

  • class is a special keyword that indicates that we are defining an object.
  • TodoItem is the name of our model. We can give it a different name (but we must avoid special characters and whitespace). Always start a class name with an uppercase letter.
  • models.Model means that the TodoItem is a Django Model, so Django knows that it should be saved in the database.

We also defined two different attributes that each to-do item should have: text ("Do math homework", for example) and done (True if the to-do item is done, otherwise False). The data field types of each of these attributes are the following:

  • models.CharField - this is how you define text with a limited number of characters.
  • models.BooleanField - this is how you define values that can either be True or False.

In a separate terminal window from where you ran the docker-compose command, run the following:

./manage.sh makemigrations
Enter fullscreen mode Exit fullscreen mode

You should see something similar to the following:

Migrations for 'todo':
  todo/migrations/0001_initial.py
    - Create model TodoItem
Enter fullscreen mode Exit fullscreen mode

By running makemigrations, you're telling Django that you've made some changes to your models (in this case, you've made new ones) and that you'd like the changes to be stored as a migration.

Migrations are how Django stores changes to your models (and thus your database schema) - they're files on disk. You can read the migration for your model if you like; it's the file todo/migrations/0001_initial.py. Don't worry, you're not expected to read them every time Django makes one, but they're designed to be human-editable in case you want to manually tweak how Django changes things.

There's a command that will run the migrations for you and manage your database schema automatically - that's called migrate.

Now, run migrate again to create those model tables in your database:

./manage.sh migrate
Enter fullscreen mode Exit fullscreen mode

You should see something similar to the following:

Operations to perform:
  Apply all migrations: admin, auth, contenttypes, todo, sessions
Running migrations:
  Rendering model states... DONE
  Applying todo.0001_initial... OK
Enter fullscreen mode Exit fullscreen mode

The migrate command takes all the migrations that haven't been applied (Django tracks which ones are applied using a special table in your database called django_migrations) and runs them against your database - essentially, synchronizing the changes you made to your models with the schema in the database.

If you ever want to make future changes to your models.py file again, remember the three-step guide to making model changes:

  • Change your models (in models.py).
  • Run python manage.py makemigrations to create migrations for those changes
  • Run python manage.py migrate to apply those changes to the database.

Playing with the API

Now, let’s hop into the interactive Python shell and play around with the free API Django gives you. To invoke the Python shell, use this command:

./manage.sh shell
Enter fullscreen mode Exit fullscreen mode

Once you’re in the shell, explore the database API:

# Import the model classes we just wrote.
>>> from todo.models import TodoItem  

# No todo items are in the system yet.
>>> TodoItem.objects.all()
<QuerySet []>

# Create a new TodoItem.
>>> t = TodoItem(text="Go for a run", done=False)

# Save the object into the database. You have to call save() explicitly.
>>> t.save()

# Now it has an ID.
>>> t.id
1

# Access model field values via Python attributes.
>>> t.text
'Go for a run'

# Change values by changing the attributes, then calling save().
>>> t.text = 'Go to the gym'
>>> t.save()

# objects.all() displays all the todo items in the database.
>>> TodoItem.objects.all()
<QuerySet [<TodoItem: TodoItem object (1)>]>

# Exit out of your Python shell
>>> exit()
Enter fullscreen mode Exit fullscreen mode

Wait a minute. <TodoItem: TodoItem object (1)> isn’t a helpful representation of this object. Let’s fix that by editing the TodoItem model (in the code/todo/models.py file) and adding a __str__() method to TodoItem:

from django.db import models

class TodoItem(models.Model):
    # ...
    def __str__(self):
        return self.text
Enter fullscreen mode Exit fullscreen mode

It’s important to add __str__() methods to your models, not only for your own convenience when dealing with the interactive prompt, but also because objects’ representations are used throughout Django’s automatically-generated admin.

Run ./manage.sh shell again:

# Import the model class
>>> from todo.models import TodoItem

# Make sure our __str__() addition worked.
>>> TodoItem.objects.all()
<QuerySet [<TodoItem: Go to the gym>]>

# Exit out of your Python shell
>>> exit()
Enter fullscreen mode Exit fullscreen mode

Let’s update the contents of code/todo/models.py to:

from django.db import models

class TodoItem(models.Model):
    text = models.CharField(max_length=200)
    done = models.BooleanField(default=False)

    @property
    def striked_text(self):
        STRIKE_CHARACTER = '\u0336'
        new_text = ''
        for letter in self.text:
            new_text = new_text + letter + STRIKE_CHARACTER
        return new_text

    def __str__(self):
        if self.done == True:
            return self.striked_text
        else:
            return self.text
Enter fullscreen mode Exit fullscreen mode

Start a new Python interactive shell by running ./manage.sh shell again:

# Import the model class
>>> from todo.models import TodoItem

# Make sure our custom method worked.
>>> t = TodoItem.objects.get(id=1)
>>> t.striked_text
G̶o̶ ̶t̶o̶ ̶t̶h̶e̶ ̶g̶y̶m̶

# Request an ID that doesn't exist, this will raise an exception.
>>> TodoItem.objects.get(id=2)
Traceback (most recent call last):
    ...
DoesNotExist: TodoItem matching query does not exist.

# Create three todo items. The create call constructs a new
# TodoItem object, and saves to the database in one step
>>> TodoItem.create(text='Cook dinner', done=True)
<TodoItem: C̶o̶o̶k̶ ̶d̶i̶n̶n̶e̶r̶>
>>> TodoItem.create(text='Take out the trash', done=False)
<Choice: Take out the trash>

# Another useful function is the ability to filter objects by its attributes:
# First show all todo items
>>> TodoItem.objects.all()
<QuerySet [<TodoItem: G̶o̶ ̶t̶o̶ ̶g̶y̶m̶>, <TodoItem: Cook dinner>, <TodoItem: Take out the trash>]>

# Filter todo items by those marked as done
>>> TodoItem.objects.filter(done=True)
<QuerySet [<TodoItem: G̶o̶ ̶t̶o̶ ̶g̶y̶m̶>]>
Enter fullscreen mode Exit fullscreen mode

Play around with the app

  • Add the following code to code/todo/admin.py
from django.contrib import admin

from .models import TodoItem

admin.site.register(TodoItem)
Enter fullscreen mode Exit fullscreen mode
  • With the server running, open http://0.0.0.0:8000/admin and login using admin as the username and 1234 as the password.
  • Click on Todo items and try adding a todo item and marking it as done.

Troubleshooting

If you ever want to start from scratch and reset your project, you should run docker system prune -a and delete db.sqlite3.

Note: Some of the content in this document was taken from tutorials on the Django website https://www.djangoproject.com/ and Django Girls https://djangogirls.org/en/

💖 💪 🙅 🚩
andrewrobles
Andrew Robles

Posted on March 29, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related