Welcome to the lesson on creating the To-Do model in Django. In this lesson, we will focus on building the foundational model for our To-Do application. The goal is to help you understand how to define a model in Django, set up corresponding views, and map these views to URLs so that you can create and display To-Do items via an API endpoint.
Models in Django are used to define the structure of your database tables. By the end of this lesson, you will be able to create a Todo
model with fields for the task description and its completion status and expose this data through an API that you can test.
In Django, a model defines the structure of your database tables. Each model is a Python class that subclasses django.db.models.Model
. It contains fields that represent the columns of the table. Let's create our Todo
model.
Here is the code for creating the basic Todo
model in models.py
:
Python1from django.db import models 2 3class Todo(models.Model): 4 task = models.CharField(max_length=200) 5 completed = models.BooleanField(default=False) 6 7 def __str__(self): 8 return self.task
task
: This field is aCharField
, which is used to store text data. Note thatCharField
must have amax_length
parameter. We set themax_length
to 200, which means this field can store up to 200 characters.completed
: This field is aBooleanField
that stores aTrue
orFalse
value. We set the default value toFalse
, meaning that a task is not completed by default.__str__(self)
: This method returns the model's string representation. It is used when you print an instance of the model, or when the instance is displayed in the Django admin interface or Django shell. As we won't need it in the first courses of this path, we define it as simply thetask.name
. However, in your project, you can come up with any__str__
method behaviour you find comfortable.
This model represents an SQL table for storing tasks. The columns of this table will be id
, task
, and completed
. Note that we don't need to define the id
column manually, Django will create it automatically.
Here is an illustration of how the SQL table would look:
id | task | completed |
---|---|---|
1 | Example Task 1 | False |
2 | Example Task 2 | True |
To give you a broader understanding, here's a table explaining common Django model fields and their corresponding Python types:
Django Field | Python Type | Description |
---|---|---|
CharField | str | Used to store text data with a maximum length. |
BooleanField | bool | Used to store True /False values. |
IntegerField | int | Used to store integer values. |
FloatField | float | Used to store floating-point numbers. |
DateField | datetime.date | Used to store dates (without time). |
DateTimeField | datetime.datetime | Used to store date and time. |
TextField | str | Used to store large text data. |
EmailField | str | Used to store email addresses. |
By understanding these field types, you can design your database tables to suit various kinds of data that your application might need to handle.
Once we define the model, we need to create the corresponding database table. Django uses a system called migrations to handle database schema changes. Here’s how to generate and apply migrations for our Todo
model.
First, generate the migration for the Todo
model by running:
1python manage.py makemigrations
This command will create a new migration file in the migrations
directory of your app. The migration file contains the instructions to create the table for the Todo
model. You can check the migrations
directory to see the newly created migration file, which will have a name like 0001_initial.py
.
Next, apply the migration to create the table in the database by running:
1python manage.py migrate
This command will apply all pending migrations, including the one for the Todo
model. Now, the database should have a new table corresponding to the Todo
model, ready to store tasks.
By using migrations, we can easily manage changes to the database schema over time, and Django ensures that our database always matches our models.
Important Note: Whenever you make any changes in the model's configuration, including adding a new field, adding a new model, or even slightly adjusting a field parameter, you should make and apply migrations to reflect this in the database. Pay attention to this when solving tasks, as forgetting to apply migrations is a common mistake.
When adding a new field to a model, Django may prompt you to provide a default value for migrations to handle existing rows without this field.
For instance, if adding a priority
field to the Todo
model, you may see:
1You are trying to add a non-nullable field 'priority' to todo without a default.
There are three ways to address this issue:
-
One-off Default Value: Provide a default value during the migration prompt. This sets the specified value for all existing rows temporarily. It can be any valid python value.
-
Add Default in Models: Update your model to include a default value for the new field:
Python1priority = models.IntegerField(default=1)
This applies the default to all future objects as well. After updating, run
makemigrations
andmigrate
. -
Allow Null Values: Set
null=True
for the new field to permit existing rows without a default value:Python1priority = models.IntegerField(null=True)
These options help in managing database schema updates while keeping data consistent.
We will create a view to list all To-Do items available in our database.
Here is the code for the todo_list
view in views.py
:
Python1from rest_framework.views import APIView 2from rest_framework.response import Response 3from .models import Todo 4 5class TodoList(APIView): 6 def get(self, request): 7 todos = Todo.objects.all().values('task', 'completed') 8 return Response({"data": todos})
get(self, request)
: This method handles GET requests.Todo.objects.all().values('task', 'completed')
: This line fetches all To-Do objects from the database and retrieves the values for thetask
andcompleted
fields.- Finally, the method returns a Response with all the retrieved items.
Using this view, we can fetch all To-Do items and display them in a JSON format.
URL routing in Django maps URL patterns to views. We need to set up the URL configuration for our To-Do API.
Here is the code for adding the URL pattern in urls.py
:
Python1# project/myapp/urls.py 2from django.urls import path 3from .views import TodoList 4 5urlpatterns = [ 6 path('todos/', TodoList.as_view()), 7]
The path('todos/', TodoList.as_view())
line maps the URL path /todos/
to the todo_list
view.
With this URL configuration in place, our new view will be accessible at the /todos/
endpoint.
To ensure our To-Do API works correctly, we will test it using the requests
library. Below is the code for testing the To-Do API using a simple script in test_script.py
:
Python1import requests 2 3URL = 'http://0.0.0.0:3000/api/todos/' # Ensure the URL matches your endpoint 4response = requests.get(URL) 5print(response) # {'data': []}
By now, there should be no TODOs in the database, so we will get an empty list.
We will cover creating new objects in deep detail in the next course. However, let's implement a quick way to send post requests so we can test our app.
Here is the code for the updated TodoList
view in views.py
:
Python1from rest_framework.views import APIView 2from rest_framework.response import Response 3from rest_framework import status 4from .models import Todo 5 6class TodoList(APIView): 7 def get(self, request): 8 todos = Todo.objects.all() 9 return Response({"data": todos}) 10 11 def post(self, request): 12 task = request.data.get('task') 13 completed = request.data.get('completed', False) 14 todo = Todo.objects.create(task=task, completed=completed) 15 return Response({"data": {"task": todo.task, "completed": todo.completed}}, status=status.HTTP_201_CREATED)
The new post
method allows us to send requests with some new task data to create new objects. Let's break it down.
post(self, request)
: This method handles POST requests.request.data.get('task')
: This retrieves thetask
value from the request data.request.data.get('completed', False)
: This retrieves thecompleted
value from the request data; if not provided, it defaults toFalse
.Todo.objects.create(task=task, completed=completed)
: This creates a new To-Do item in the database.status=status.HTTP_201_CREATED
: This indicates that a new resource has been created successfully, which is the standard status code for a successful POST request in REST APIs.
The Response
function from rest_framework.response
is used to generate an HTTP response with the given data and status code. For POST methods, it serializes the returned data into JSON format and sends it back to the client.
Now, we can test both POST and GET requests with our testing script:
Python1import requests 2 3URL = 'http://0.0.0.0:3000/api/todos/' # Ensure the URL matches your endpoint 4 5# Create a new To-Do item 6new_todo = {'task': 'Learn Django', 'completed': False} 7post_response = requests.post(URL, json=new_todo) 8print(post_response.json()) # Expect to see the newly created TODO item 9 10# Fetch all To-Do items 11get_response = requests.get(URL) 12print(get_response.json()) # Expect to see a list containing the new TODO item
In this lesson, we covered the following steps to create our To-Do model and expose it through an API:
- Recapped the initial setup of the Django project and app.
- Created the
Todo
model to store task descriptions and completion status. - Added a view to fetch and respond with the To-Do items.
- Set up URL routing to map the view to an endpoint.
- Tested the To-Do API using a simple script.
Now, you are ready to practice these steps on your own using the CodeSignal IDE. Continue with the practice exercises to reinforce what you’ve learned and ensure you’re comfortable with building models, views, and URL configurations in Django. Happy coding!