Recursive Model Relationships in Django

The Need for Recursive Relationships

There arises many times in the development of modern web applications where the business requirements inherently describe relationships that are recursive. One well known example of such a business rule is in the description of employees and their relationship to their managers, which are also employees. Notice the circular nature of that statement. This is exactly what is meant by a recursive relationship. In this article we will be developing a bare bones demo in Django of a human resources (HR) employee listing application with this recursive relationship between employees and managers.

The code for this article can be found in this GitHub repo.

Setting up the Django Project Structure

To get going with a Django project you'll want to create a new python virtual environment (preferably Python3). If you are unfamiliar with virtual environments please see this article. Once inside your activated virtual environment, pip install Django.

(venv) $ pip install django

With Django installed you can utilize Django's admin utilities to generate the project boilerplate, which we'll call "webapp". You can learn more about Django project setup in our article, Flask vs Django.

(venv) $ django-admin startproject webapp

Now cd into the new webapp directory so we can further utilize another set of Django tools via the manage.py script. We use this to create our project's application, which we'll name "hrmgmt". This creates another directory called "hrmgmt" which is where the code for this application will reside.

(venv) $ cd webapp
(venv) $ python manage.py startapp hrmgmt

The last part of project setup includes letting the project (webapp) know about the "hrmgmt" application. In "webapp/settings.py" find the section with a comment of "Application definition" above the list INSTALLED_APPS and add an entry of hrmgmt.apps.HrmgmtConfig, like so:

# Application definition

INSTALLED_APPS = [  
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'hrmgmt.apps.HrmgmtConfig'
]

Configuring the Routes

In Django the directory that matches the name of the project, "webapp" in our case, is where the major settings and entry point to the routes for the built in admin app and any additional custom applications reside. So in "webapp/urls.py" use the following code to direct all routes prefixed with "/hr" to the "hrmgmt" application.

# webapp/urls.py
from django.conf.urls import url, include  
from django.contrib import admin

urlpatterns = [  
    url(r'^admin/', admin.site.urls),
    url(r'^hr/', include('hrmgmt.urls'))
]

Over in the custom "hrmgmt" application create a new file named "urls.py" and place the following code. This specifies a view that will return a list of all employees. The below code uses a regular expression to indicate that when a route of "/hr/" is requested from our server then a view function named index should handle the request and return a response.

# hrmgmt/urls.py
from django.conf.urls import url

import views

urlpatterns = [  
    # /hr/
    url(r'^$', views.index, name='index')
]

Next we will talk about what the index view function does.

Stubbing the Index View Function

Now let's implement the aforementioned index view function to handle requests to the "/hr/" route and return a text response to let us know we have configured things correctly. Later we will come back and turn this into a more proper view function to list our employees.

In hrmgmt/views.py include the following code:

# hrmgmt/views.py
from django.http import HttpResponse

def index(request):  
    response = "My List of Employees Goes Here"
    return HttpResponse(response)

Within the webapp directory, fire up the Django development server and test that we've configured our route and view function correctly:

(venv) $ python manage.py runserver

Now go to your browser and enter http://localhost:8000/hr/ and you should see a text response of "My List of Employees Goes Here"

Designing our Model Classes

Finally we're getting to the good part! In this section we define our model classes which will translate into database tables, all done by writing Python code. Or using what the .NET folks have coined as a "code first" approach to database design.

In hrmgmt/models.py place in the following code:

# hrmgmt/models.py
from django.db import models

class Employee(models.Model):  
    STANDARD = 'STD'
    MANAGER = 'MGR'
    SR_MANAGER = 'SRMGR'
    PRESIDENT = 'PRES'

    EMPLOYEE_TYPES = (
        (STANDARD, 'base employee'),
        (MANAGER, 'manager'),
        (SR_MANAGER, 'senior manager'),
        (PRESIDENT, 'president')
    )

    role = models.CharField(max_length=25, choices=EMPLOYEE_TYPES)
    first_name = models.CharField(max_length=100)
    last_name = models.CharField(max_length=100)
    manager = models.ForeignKey('self', null=True, related_name='employee')

    def __str__(self):
        return "<Employee: {} {}>".format(self.first_name, self.last_name)

    def __repr__(self):
        return self.__str__()

There is quite a bit going in these few lines of code so let's break them down. The first thing to note is that a Python class named Employee is being declared, which inherits from the django.db.models.Model class. This inheritance gives the Employee class the functionality to access the database through Django's ORM.

Next are the definitions of four class fields that are constants (STANDARD, MANAGER, SR_MANAGER, PRESIDENT) and their use to further define a tuple class field constant. These are sort of like enums which specify the different roles an employee can assume. In fact, the tuple of tuples constant is passed to the definition of the roles class field to signify what values the class should be allowed to accept.

Next the first_name and last_name class fields are defined as character fields with a max length of 100 characters.

The final field being defined is perhaps the most meaningful one, the manager field. It is a foreign key that defines a recursive relationship between employees and their managers. This means that the implicit auto incrementing integer id column that Django makes on models that inherits from django.db.models.Model will be available as a foreign key value for the same class (or table).

This will satisfy our use-case which could be stated as, "an employee may only have one direct manager or no manager in the case of the president, but an employee may manage many different employees". By specifying self as the first parameter of the model.ForeignKey call, Django will set this up as a recursive relationship. Then by specifying null=True the model will allow for an employee without a manger, which in our example is the one representing the president.

Below is a ERD diagram of the recursive relationship we have defined.

Migrating our Class Definition to the Database

In order to transform the code we used to define our Employee class into DDL SQL we will again make use of a Django utility accessed via the "manage.py" script and collectively known as migrations.

In the command line, within a our virtual environment of course, run the following to create the default tables which all Django apps utilize. By default, this database is a sqlite database within the root project folder.

(venv) $ python manage.py migrate

Once complete, we can make a new migration that defines the table that will back our Employee class. Do this by issuing the following commands and make sure you observe the output as shown below:

(venv) $ python manage.py makemigrations
(venv) $ python manage.py migrate
Operations to perform:  
  Apply all migrations: admin, auth, contenttypes, hrmgmt, sessions
Running migrations:  
  Applying hrmgmt.0001_initial... OK

You can view the actual DDL SQL that creates the table by running the below the command:

(venv) $ python manage.py sqlmigrate hrmgmt 0001

BEGIN;  
--
-- Create model Employee
--
CREATE TABLE "hrmgmt_employee" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "role" varchar(25) NOT NULL, "first_name" varchar(100) NOT NULL, "last_name" varchar(100) NOT NULL, "manager_id" integer NULL REFERENCES "hrmgmt_employee" ("id"));  
CREATE INDEX "hrmgmt_employee_manager_id_43028de6" ON "hrmgmt_employee" ("manager_id");  
COMMIT;  

Exploring Models with the Django Shell

At the command line enter the following command to get the interpreter up and running with our Django app's context preloaded into the REPL:

(venv) $ python manage.py shell

Now that the Python interpreter is up and running enter the following commands:

>>> from hrmgmt.models import Employee
>>> janeD = Employee.objects.create(first_name='Jane', last_name='Doe', role=Employee.PRESIDENT)
>>> johnD = Employee.objects.create(first_name='John', last_name='Doe', role=Employee.MANAGER, manager=janeD)
>>> joeS = Employee.objects.create(first_name='Joe', last_name='Scho', role=Employee.STANDARD, manager=johnD)
>>> johnB = Employee.objects.create(first_name='John', last_name='Brown', role=Employee.STANDARD, manager=johnD)

The above code creates four fictitious employees. Jane Doe is the president. Then John Doe has a manager role and is managed by his mother Jane Doe (yes, there is clearly some nepotism here). Under John Doe's supervision is Joe Schmo and John Brown who both have the roles of a standard or base employee.

We can test our relationship field of employee by inspecting the output of calling employee on our johnD variable:

>>> johnD.employee.all()
<QuerySet [<Employee: Joe Scho>, <Employee: John Brown>]>  

As well as with the janeD variable:

>>> janeD.employee.all()
<QuerySet [<Employee: John Doe>]>  

Similarly we will want to test our manager field to make sure it is performing as desired:

>>> johnD.manager
<Employee: Jane Doe>  

Great! It looks like things are working as expected.

Setting Up Our View

In the same directory as our "hrmgmt" directory make another directory called "templates". Then within the "templates" directory make yet another directory called "hrmgmt". Finally within the "hrmgmt/templates/hrmgmt" directory make an HTML file called "index.html". It is within this file that we will write the code to build out our listing of employees.

Copy and paste in the following code:

<!-- hrmgmt/templates/hrmgmt/index.html -->  
<!DOCTYPE html>  
<html lang="en">  
    <head>
        <title>Employee Listing</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
        <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/js/bootstrap.min.js" integrity="sha384-h0AbiXch4ZDo7tp9hKZ4TsHbi047NrKGLO3SEJAg45jXxnGIfYzk4Si90RDIqNm1" crossorigin="anonymous"></script>
    </head>
    <body>
        <div class="container">
            <div class="row">
                <div class="col-md-12">
                    <h1>Employee Listing</h1>
                </div>
            </div>
            <div class="row">
                <dov class="col-md-12">
                    <table class="table table-striped">
                        <thead class="thead-inverse">
                            <tr>
                                <th>Employee ID</th>
                                <th>First Name</th>
                                <th>Last Name</th>
                                <th>Role</th>
                                <th>Manager</th>
                            </tr>
                        </thead>
                        <tbody class='table-striped'>
                            {% for employee in employees %}
                            <tr>
                                <td>{{ employee.id }}</td>
                                <td>{{ employee.first_name }}</td>
                                <td>{{ employee.last_name }}</td>
                                <td>{{ employee.get_role_display }}</td>
                                <td>{% if employee.manager %}{{ employee.manager.first_name }} {{ employee.manager.last_name }}{% endif %}</td>
                            </tr>
                            {% endfor %}
                        </tbody>
                    </table>
                </dov>
            </div>
        </div>
        <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.11.0/umd/popper.min.js" integrity="sha384-b/U6ypiBEHpOf/4+1nzFpr53nxSS+GLCkfwBdFNTxtclqqenISfwAzpKaMNFNmj4" crossorigin="anonymous"></script>
    </body>
</html>  

This file is known as a template in the Django web framework. Templates represent a blueprint for reproducible HTML that is dynamically generated based off the data that is passed to it. In our case the data being passed to our "index" template represents our list of employees.

In order to serve up our template we will need to make a couple of changes to our view function. Namely we need to import the render helper function from Django shortcuts, then instead of returning HttpResponse we will return a call to render, passing in the request object, the path to our template, and a dictionary containing the data to pass to our template.

# hrmgmt/views.py
from django.shortcuts import render

from .models import Employee

def index(request):  
    employees = Employee.objects.order_by('id').all()
    context = {'employees': employees}
    return render(request, 'hrmgmt/index.html', context)

Again, fire up our Django development server and in a browser type http://localhost:8000/hr/ into the URL field then press "Enter". You should see the output similar to the following screenshot:

You can see in the resulting "Manager" column of the table that we've successfully linked an Employee to an Employee using Django models.

Conclusion

In this article we have gone over the use case for why we would implement a recursive relationship within a Django model. We walked through the code for defining such a recursive relationship as well as how to interact with the models to persist them to the database then how to retrieve them. Finally, we wrapped things up by seeing how to display the information in our database backed models in a Django template.

If you have made it this far I would like to thank you for reading my article. I hope that this article inspires you to further investigate web development with the Django web framework. As always I invite any and all comments, suggestions, or criticisms.

Author image
Lincoln, Nebraska Twitter