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 web app 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 (web app) 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 web app 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 manager, which in our example is the one representing the president.
Below is a ERD diagram of the recursive relationship we have defined.
Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!
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 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 on 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 screen-shot:
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.