In this part of the tutorial, we’re going to start writing the Models of the main app of the TaskBuster project: the TaskManager app.
First, we’re going to look and understand the model structure of this app through an UML diagram. Defining this type of diagrams is very important, as it not only serves as documentation, but also helps to have a clear structure of how you want your models to behave.
Next, we will create the first model, which will be related to the User model through a OneToOne relationship. Moreover, you’ll also learn about signals, custom attributes for your models, the Django Admin interface, and as usual, tests 🙂
The outline of this part is:
- UML Diagram of the TaskManager app
- Create the TaskManager app
- Profile Model: OneToOne relationship with the User Model
- Django Signals: create a Profile instance when a new user is created
- The Django Admin for the Profile Model
Let’s start! 😉
UML Diagram of the TaskManager app
When creating an app, it’s very important to have a clear structure of your database and models. One way to show or develop your models is using UML diagrams (where UML stands for Unified Modeling Language).
Here’s the diagram of what we are going to do:
So we’re going to define four different models to manage tasks:
1. Profile Model:
This model is going to be the top level model of our app. Each user will have its own profile instance, and all the projects, tags and tasks, will be somehow connected to this profile.

Its properties are:
- A OneToOne relationship with the Django User model. As we want that each user has its profile instance, we’re going use Django signals to create a profile instance every time a user is created.
- An interaction attribute, which is like a counter of the user interaction with the app. Every time the user completes a task, this parameter will increase by one.
2. and 3. Project and Tag Models:
These two models represent two different ways to organize our tasks. They both have a name attribute, and the project model also has a color attribute (which will be a string representing a hex color value).
4. Task Model:
This is the main model of our app. Its properties are:
- name
- priority (a string that will indicate if this task is urgent, important, etc)
- completed (a boolean)
- due date (optional)
- completed_date (optional)
And the relation with the other models are:
- a ForeignKey relationship to the Project Model: each tasks is related to a project instance, and each project can have more than one task.
- a ManyToMany relationship to the Tag Model: each task can be related to zero, one or more than one tag instance, and each tag can be related to zero, one or more than one task.
- a self relationship with the Task Model, though a ForeignKey relationship. Each task can be related to multiple tasks. Depending on the direction of the relationship, the related task will be a subtask or a parent task.
Create the TaskManager app
Usually, a project has multiple apps, each of them provides a specific functionality. To maintain your project with a clean structure, you can place all these apps inside a common folder, named apps.
Open your terminal and go to the top folder of your project (where the manage.py file resides). From there, you can crete this folder and make it a package with:
$ mkdir taskbuster/apps $ touch taskbuster/apps/__init__.py
Next, we are going to create our fist app, called taskmanager:
$ cd taskbuster/apps $ python ../../manage.py startapp taskmanager
This will create a folder with the following files inside:
- __init__.py indicates that this folder is a python package
- admin.py is used to define the Django admin
- models.py is where we are going to define our models
- tests.py to save our tests
- views.py is where the views are stored
- migrations is a folder that contains the database migrations
Moreover, we’re going to create the following files inside this folder: urls.py and managers.py
$ touch urls.py managers.py
Finally, we need to add our app into the settings/base.py file:
INSTALLED_APPS = ( ... # TaskBuster apps 'taskbuster.apps.taskmanager', ... )
Now we’re ready to edit the models.py file and create our models. I recommend that for each model, you define the attributes and methods in this order:
class MyModel(models.Model): # Relations # Attributes - Mandatory # Attributes - Optional # Object Manager # Custom Properties # Methods # Meta and String
this way, it’s easier to read the different attributes and methods, specially if someone else reads our code 😉
Profile Model: OneToOne relationship with the User Model
First, we’re going to write the Profile model. As mentioned before, this model is going to be the top model of our app: it will be related to a specific user, and all the tasks, tags and projects will be somehow related to this profile.
So we need to define:
- a one to one relationship with the User model (it can only be one profile for each user)
- and an interaction attribute, which is an integer that we’re going to use to account for the user interaction. Every time the user completes a task, this value will increase by one unit.
In the models.py file, write the following:
# -*- coding: utf-8 -*- from django.db import models from django.conf import settings from django.utils.translation import ugettext_lazy as _ from . import managers class Profile(models.Model): # Relations user = models.OneToOneField( settings.AUTH_USER_MODEL, related_name="profile", verbose_name=_("user") ) # Attributes - Mandatory interaction = models.PositiveIntegerField( default=0, verbose_name=_("interaction") ) # Attributes - Optional # Object Manager objects = managers.ProfileManager() # Custom Properties @property def username(self): return self.user.username # Methods # Meta and String class Meta: verbose_name = _("Profile") verbose_name_plural = _("Profiles") ordering = ("user",) def __str__(self): return self.user.username
And in managers.py, define a naïve ProfileManager:
# -*- coding: utf-8 -*- from django.db import models class ProfileManager(models.Manager): pass
Let’s take a look at this code:
- The profile model has a one to one relationship with the User model.
- The User model is imported using AUTH_USER_MODEL from settings.py. This is because sometimes you want to define a custom user model, and therefore, the profile should have a one to one relationship with the custom user model instead of the Django build-in model.
- related_name is used to define how can you access a profile instance from the user model. For example, if myuser is a User instance, you can access its profile with myprofile = myuser.profile. However, for a one to one relationship, Django uses this access key by default (the name of the class in lowercase). But as I sometimes change the names slightly, I like to write them explicitly on the code.
- verbose_name is used to define a more readable name. Note that this name is wrapped around the ugettext_lazy function. This function is used to translate the string if the traslation is available (remember that we covered internationalization and the ugettext_lazy function in part V of this tutorial).
- The interaction attribute is a positive integer that takes a zero value by default.
- The object manager is used to make queries. Things like MyModel.objects.filter(…) sounds familiar right? By defining a custom object manager, we will be able to define custom functions to make queries, like MyModel.objects.get_by_start_date().
- By now, the ProfileManager is a naïve object manager that only inherits the default functionalities from the Manger Django class (so you’ll be able to perform all the default queries of the object manager).
- don’t worry, we’ll extend the manager functionalities soon so you’ll understand how it can be used 🙂
- username is a custom property of this model. This means that you’ll be able to access this property using profile.username, but it won’t create a row in your database table (so it’s different than a model attribute).
- I defined the username so that you can access it more easily, instead of writing profile.user.username.
- As a custom property doesn’t touch your database, you can define or change them without having to migrate your code 🙂
- The Meta class is used to define other behaviors of your model.
- verbose_name and verbose_name_plural are the user-friendly names of your model
- ordering defines how you want this model to be ordered in a query result. If you specify another order in a query, with order_by, the latter will be used.
Great! Now that we have our model defined, let’s apply these changes to the database. Go back to the root folder, which contains the manage.py file and run:
$ python manage.py check $ python manage.py makemigrations taskmanager $ python manage.py migrate taskmanager
Yeah, we’re missing our tests… don’t worry, that’s what we’ll do next! 😉
Django Signals: create a Profile instance when a new user is created
As we’ve said before, each User is going to have a Profile instance. How are we going to handle the creation of a profile every time a new user registers to our app?
Well, that’s easy, we’re going to use Django signals so that when a new user is registered, it will trigger a function that creates a new profile instance.
But before we proceed, let’s write a test for this behavior. In the taskbuster/apps/taskmanager/test.py file write the following:
# -*- coding: utf-8 -*- from django.test import TestCase from django.contrib.auth import get_user_model from . import models class TestProfileModel(TestCase): def test_profile_creation(self): User = get_user_model() # New user created user = User.objects.create( username="taskbuster", password="django-tutorial") # Check that a Profile instance has been crated self.assertIsInstance(user.profile, models.Profile) # Call the save method of the user to activate the signal # again, and check that it doesn't try to create another # profile instace user.save() self.assertIsInstance(user.profile, models.Profile)
Note that we’re using the function get_user_model to get the User Model. Again, this is because sometimes we define custom user models, and this functions returns our custom User model if exists, or the Django default otherwise.
Run this test with:
$ python manage.py test taskbuster.apps.taskmanager
And you’ll see the following error:
django.db.models.fields.related.RelatedObjectDoesNotExist: User has no profile.
as expected. No profile is created when a new user is registered.
To solve this, we’re going to define a Django signal. Just below the definition of the Profile Model, write the following:
from django.dispatch import receiver from django.db.models.signals import post_save @receiver(post_save, sender=settings.AUTH_USER_MODEL) def create_profile_for_new_user(sender, created, instance, **kwargs): if created: profile = Profile(user=instance) profile.save()
Note: it’s very important that signals are read at the beginning of a Django app. That’s why we place them in the same models.py file. If you have many signals and want to place them into a separate file, that’s fine, but import that file (all your signals) at some point in your models.py file.
So we’re defined a signal for the User model, that is triggered every time a User instance is saved.
The arguments used in the create_profile_for_new_user are:
- sender: the User model class
- created: a boolean indicating if a new User has been created
- instance: the User instance being saved
Note: this arguments might be different depending on the specific signal you’re creating. Remember that in this case, we are dealing with a post_save signal. For more information about signals and their arguments, check the official docs.
Now you’ll be ready to understand what’s this signal doing. It checks if a new instance of the User model has been created, and if true, it creates a Profile instance using the new user instance.
Great! Let’s run the test again then
$ python manage.py test taskbuster.apps.taskmanager
Yes! they work! 😉
A Note on Signals: Don’t abuse them! If your code is full of signals that control and modify the behavior of your models, it can be difficult to understand for someone reading your code (and also to maintain by yourself!) If possible, use a custom save method (we’ll see them shortly).
The Django Admin for the Profile Model
Finally, we’re going to configure the Admin so that we can manage our Profile instances from there.
Edit the admin.py file inside the taskmanager app and write:
# -*- coding: utf-8 -*- from django.contrib import admin from . import models @admin.register(models.Profile) class ProfileAdmin(admin.ModelAdmin): list_display = ("username", "interaction") search_fields = ["user__username"]
Next, open the admin at http://127.0.0.1:8000/en/admin, where you’ll see a link to the Profile Model.
If you click on that link, you’ll see the following (see image below). I already created a Profile instance so that the list is not empty 🙂 go ahead and create one for yourself!
That’s what we have done in the admin.py file:
- First, define ProfileAdmin as a ModelAdmin instance.
- The admin.register decorator registers the ProfileAdmin as a ModelAdmin of the Profile Model (here’s where we connect the admin instance to the model we want to manage).
- The list_display allows us to define which fields will be displayed when listing the profile instances. In the image above, you’ll see two columns: one for the username and another for the interaction (the two fields defined in list_display).
- Note that as we defined a custom attribute for the username, we can use username instead of user__username.
- search_fields creates a searching box (see image above). The list of fields you specify indicate inside which fields it will look for.
- For the search field, we need to use a normal model attribute, so we need to write user__username instead of simply username.
Great! That’s all for this part!
In the next part we’re going to define the Project Model, which has a Foreign Key relationship with the profile model: Part X – Model creation, ForeignKey relations, testing and the Django Admin.
+1 if it was useful! Thanks!