Part X – Model creation, ForeignKey relations, testing and the Django Admin

In this part of the TaskBuster tutorial we’re going to continue defining our Models. We will define the Project and Tag models, which have a Foreign Key relationship with the Profile model.

Moreover, we will talk about custom validation, testing and customizing the Admin Site with inline models.

The outline of this part is:

Let’s start! 🙂

UML Diagram Revision

In this part of the tutorial, we’re going to continue defining our models. Remember that last time we created the Profile Model, which had a OneToOne relationship with the User model.

Let’s take a look at the UML diagram of our models:

taskbuster-start-model-diagram-v2

As a recap:

  • the Profile Model has a OneToOne relationship with the User model.
  • Both the Project and the Tag models have a ForeignKey relationship with the Profile Model,
  • and the Task model has
    • a ForeignKey relationship with the Project Model
    • a ManyToMany relationship with the Tag model
    • a self ForeignKey relationship with itself.

A little bit of everything! 🙂

The Project Model: Foreign Key Relationships and custom validators

First, we’re going to define the Project Model, which will be used to group our tasks under multiple tag names.

The detailed UML diagram for this model is:

taskbuster-project-model

In taskbuster/apps/taskmanager/models.py write:

and in taskbuster/apps/taskmanager/managers.py add:

Let’s read step by step this code:

  • The Project model has a Foreign Key relationship with the Profile Model.
    • This means that:
      • each project instance must be related to one User Profile (the profile field is mandatory),
      • and each User Profile can be related to zero, one or more than one projects.
    • From a project instance, named myproject, we can obtain its related profile with:  myproject.user
      • yes, note that the attribute name defined in Project is user and not profile.
    • From a profile instance, named myprofile, we can obtain its related projects with:  myprofile.projects.all() .
      • Without specifying a related_name, by default you should access the projects of a profile with myprofile.project_set.all() .
      • Note that myprofile.project returns an object manager, so that if we want to obtain the project instances, we need to use some of the usual query methods, like all(), filter(), exclude(), etc. We could even call the custom methods defined in the custom ProjectManager class.
    • As we saw in the previous part, verbose name just indicates the human-readable name of this attribute.
      • Note that it uses the translation function ugettext_lazy (read the previous part to see the import).
TaskBuster ForeignKey relationship

One to Many relationship: each orchestra musician has a Foreign Key relationship with its conductor

  • The name of the project it’s a CharField attribute, with a max length of 100 characters.
    • A help_text is a text that will appear in the model forms, so that the user knows what he should write.
  • Color is another CharField attribute, with a max length of 7.
    • We expect an Hex color, which is composed by 3 hexadecimal number that go from 00 to FF, each of them indicating the level of red, green and blue. When put together, they make a 6 character string, plus #, like #XXXXXX.
      • For example, black is #000000 and white is #FFFFFF.
      • However, when the three numbers are formed by pairs of the same number, like #001122, each of them can be abbreviated with a single digit, like #012. This way, black can also be written as #000, and white as #FFF.
      • By default, this field will have a white color.
    • In order to accept only correct values of Hex colors, we use a custom validator. The RegexValidator accepts only strings that match the specified regular expression.
  • We include the custom object manager defined in managers.py, ProjectManager.
  • In Meta, we define:
    • the human-readable name of the class
    • the default ordering when querying project instances
    • the unique_together  property, which defines at the database level, that for the same profile we can’t write two projects with the same name.
  • The __str__ method is called whenever the str() method is called on an object, like in the admin site, or showing the object in the Django templates.

Perfect! Now that we have our models defined, we need to migrate these changes into the database:

Next, let’s write some tests for this Model.

Tests for the Project Model

When writing tests for Django models, I usually focus only in custom attributes and functions, that have some property or behavior that isn’t Django’s default.

For example, I’m not going to test the correct behavior of max_length in a CharField, as it’s a built in feature, that for sure it’s been tested enough by Django developers.

However, I should test that I wrote a good regular expression for the custom validation of the color attribute. Let’s do that then!

In taskbuster/apps/taskmanager/tests.py add the following test:

And let’s explain what it does:

  • The setUp method is executed at the beginning of each test:
    • It creates a user instance
    • the user instance fires a signal that creates the related profile instance
    • both are saved in self for later use.
  • test_validation_color tests different inputs of the color attribute:
    • first it creates a project with the default value and checks that it doesn’t raise a ValidationError
    • next, it checks other correct inputs
    • and then checks that bad inputs raise a ValidationError.
    • Note that in order to raise a ValidationError we need to call the full_clean() method of the instance, as simply saving the method won’t work.
  • The tearDown method is executed at the end of each test:
    • it deletes the user instance
    • deleting the user instance also deletes all the related instances that depend on this one:
      • the profile depends on the user
      • the project depends on the profile
    • this way we leave the testing database as clean as at the beginning of the test

Ok! now that we understand this test, you can run it with:

Hope it worked! 🙂

Django Admin for the Project Model: custom list display and Model Inline

Now that we have our model defined and tested, we can include it in the Admin Site.

However, as Projects are related to a specific user profile, we’re going to modify the ProfileAdmin we defined in the previous post. This way, when editing a specific profile, we’ll be able to add or edit its related projects.

In taskbuster/apps/taskmanager/admin.py, replace the ProfileAdmin with:

You can see the changes by running a server:

and going to the Admin Profile list. We’ll discuss the previous code step by step in a minute.

First, edit (or create if you don’t have one) a Profile instance. You’ll see something similar to:

taskbuster-edit-profile-project

Note that at the bottom, I’ve created two different projects, Blog and TaskBuster. But wait, why do they appear here?

This is thanks to defining the ProjectsInLine class, which inherits from the Django TabularInLine:

the extra parameter indicates how many extra Projects should appear when editing a Profile instance (they will appear empty). Try to change it to 5 and see what happens! 🙂

Moreover, the connection between ProjectsInLine and the ProfileAdmin is done here:

Note that when creating a new project inside a profile instance, the relation between these two objects is automatically set (we don’t need to specify the profile field in the project instance). Moreover, only Projects related to the current profile instance are shown here.

On the other hand, if we go back to the Profile Listing, we’ll see something like:

taskbuster-profile-listing-2

Which shows a list of Profile instances, specifying the username, the interaction value and… how many projects it has?

You thought that you could only show model attributes here? Well, apparently you can also define custom functions 🙂

Let’s take a look at the property list_display:

it contains the username, the interaction and another property _projects that is not a model attribute nor a custom property. However, you’ll see a custom method defined inside the ProfileAdmin:

It takes two arguments: self (the ProfileAdmin instance) and obj (the Profile instance we’re editing).

So this method is simply querying all projects related to the profile instance and counting them. Great! 🙂

Tag Model UML diagramThe Tag Model: Another simple model with a ForeignKey relationship

As we can see in the UML diagram, the Tag Model is very similar to the Project Model:

  • It has a ForeignKey relationship with the Profile model
  • It has a name property

As it doesn’t have any extra functionalities, we can define it straightforward in our models.py and managers.py files:

And edit the admin.py file to add the Tag model inline:

Great! Now migrate your database:

check the results in the admin site:

Profile Model Admin

Run your tests again and make sure nothing is broken! 🙂

and finally, commit your changes:

 

That’s all for today!

Please, give a +1 if 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! :-)