Part IX – Model creation, OneToOne relations, signals and the Django Admin

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:

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:

taskbuster-start-model-diagram

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.

TaskBuster OneToOne Relationship

Django’s favorite OneToOne relationship

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:

Next, we are going to create our fist app, called 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

Finally, we need to add our app into the settings/base.py file:

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:

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

uml-profile-modelFirst, 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:

And in managers.py, define a na√Įve¬†ProfileManager:

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:

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:

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:

And you’ll see the following error:

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:

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

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:

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!

taskbuster-admin-profile

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!

Google+TwitterLinkedInFacebookReddit

Please, add +Marina Mele in your comments. This way I will get a notification email and I will answer you as soon as possible! :-)