Skip to content

Marina Mele's site

Reflections on family, values, and personal growth

Menu
  • Home
  • About
Menu
TaskBuster OneToOne Relationship

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:

  • 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:

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:

$ 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

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:

# -*- 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!

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!

Leave a Reply Cancel reply

You must be logged in to post a comment.

Categories

  • Personal Growth and Development
  • Mindful Parenting and Family Life
  • Productivity and Time Management
  • Mindfulness and Wellness
  • Values and Life Lessons
  • Posts en català
  • Other things to learn
Follow @marina_mele
  • Cookie Policy
  • Privacy Policy
©2023 Marina Mele's site | Built using WordPress and Responsive Blogily theme by Superb
This website uses cookies to improve your experience. If you keep navigating through this website, we'll assume you're ok with this, but you can opt-out if you wish.Accept Read More
Privacy & Cookies Policy

Privacy Overview

This website uses cookies to improve your experience while you navigate through the website. Out of these, the cookies that are categorized as necessary are stored on your browser as they are essential for the working of basic functionalities of the website. We also use third-party cookies that help us analyze and understand how you use this website. These cookies will be stored in your browser only with your consent. You also have the option to opt-out of these cookies. But opting out of some of these cookies may affect your browsing experience.
Necessary
Always Enabled
Necessary cookies are absolutely essential for the website to function properly. This category only includes cookies that ensures basic functionalities and security features of the website. These cookies do not store any personal information.
Non-necessary
Any cookies that may not be particularly necessary for the website to function and is used specifically to collect user personal data via analytics, ads, other embedded contents are termed as non-necessary cookies. It is mandatory to procure user consent prior to running these cookies on your website.
SAVE & ACCEPT