Representing Relationships in Django Templates Without Writing Extra Code (RelatedManager and ManyRelatedManager)

I’m writing an application that deals with some slightly complex relationships. There are several offices, and each office has several workers. Workers can have multiple projects, and each project can have multiple workers. In addition, each project can serve multiple clients.

Here’s what that’d look like in a Django models.py file:

class Office(models.Model):
   office_code = models.CharField(max_length=24, blank=True)
   street_num = models.CharField(max_length=24)
   street_name = models.CharField(max_length=64)
   bldg_no = models.CharField(max_length=12, blank=True)
   suite = models.CharField(max_length=12, blank=True)
   city = models.CharField(max_length=100)
   state = USStateField() # grandiose assumption
   zipcode = models.CharField(max_length=10)
   main_phone = PhoneNumberField()
   site_mgr = models.ForeignKey(User, unique=True)

class Worker(models.Model):
   user = models.ForeignKey(User, unique=True)
   extension = models.CharField(max_length=8)
   office = models.ForeignKey(Office)

class Client(models.Model):
   fname = models.CharField(max_length=64)
   lname = models.CharField(max_length=64)
   street_num = models.CharField(max_length=24, blank=True)
   street_name = models.CharField(max_length=128, blank=True)
   apt_no = models.CharField(max_length=24, blank=True)
   city = models.CharField(max_length=128, blank=True)
   state = USStateField(blank=True)
   zipcode = models.CharField(max_length=10, blank=True)

class Project(models.Model):
   date_started = models.DateTimeField()
   worker = models.ManyToManyField(Worker)
   office = models.ForeignKey(Office)
   client = models.ManyToManyField(Client)

While writing the template for my worker detail page, I decided that I didn’t want to just list the projects for that worker, but I also wanted to list the clients for each project. I ran into a bit of an issue at first in doing this. I tried something like this:

{% block content %}
   <h2>Projects for {{object.user.first_name}} {{object.user.last_name}}</h2>
   <ul>
   {% for project in object.projects %}
      <li><a href="{{project.get_absolute_url}}">{{project.id}} (Opened: {{project.date_started.date}})</a>
      <ul>
         {% for obj in project.get_clients %}
            <li>{{obj.lname}}, {{obj.fname}}</li>
         {% endfor %}
      </ul>
   <h2>Clients for project {{project.id}}</h2>
{% endfor %}</ul>
{% endblock %}

Looking back at the models, you’ll note that there’s no “projects” attribute of the Worker class. There’s also no “get_clients” method for the Project class. After reading some forum and blog posts, I got the idea to add these to my models manually. It seems a lot of people solve similar issues this way, and I don’t believe it’s necessary, which is why I’m posting this. What I added to my models looked something like this:

###
### in Worker model
###
def projects(self):
   return self.project_set.filter(worker = self.pk)

###
### in Project model
###
def get_clients(self):
   return self.client_set.all()

Adding these to the models actually does solve the problem, but it’s reinventing the wheel. Perhaps at some point in history Django’s ORM didn’t have the functionality it does now, but these days Django takes care of accessing the objects of related entities for you through the use of the RelatedManager (for one-to-one, foreign key relationships) and the ManyRelatedManager (for many-to-many relationships).

When you create a ForeignKey field or ManyToMany field in a Django model, Django’s ORM becomes aware of the relationship, and implements lots of shortcuts to help you in managing/exploiting it.

After reading some online documentation (see the “Related Objects” area, for one), I was able to get all of the data I wanted into my template without adding a single character of code to my models, which is what I had hoped for. Here’s the right way to do this, unless I’m mistaken (please let me know the best way if I am):

{% block content %}
<h2>Projects for {{object.user.first_name}} {{object.user.last_name}}</h2>
<ul>
   {% for project in object.project_set.all %}
      <li><a href="{{project.get_absolute_url}}">{{project.id}} (Opened: {{project.date_started.date}})</a>
      <ul>
         <h2>Clients for project {{project.id}}</h2>
         {% for obj in project.client.all %}
            <li>{{obj.lname}}, {{obj.fname}}</li>
         {% endfor %}
      </ul>
   {% endfor %}
</ul>
{% endblock %}

I’ve replaced “object.projects” with “object.project_set.all”. Note that, in a Django template, unless you specify otherwise, the name of a single object passed to a template is “object”, so in this case, “object” is a “Worker” object. The Worker model makes no mention at all of projects, and yet I’m able to easily grab data about project objects. This is because Django’s ORM actually gives you access to related object data from either side of a relationship. Since the Project model has a ManyToMany field referencing Worker, you can access project data from the worker object by using “object._set.all”, where is replaced with the lower-cased name of the model that points to “object”. Hence, “object.project_set.all”.

Now, in the second case, I’ve replaced “project.get_clients” with “project.client.all”. The Project model directly contains a field named “client” that is a ForeignKey to the Client model. When this condition exists, Django will happily traverse the relationship for you by just referencing the model’s field directly! The “all” method is a standard method of any Manager object I’m aware of, and it’s inherited by the RelatedManager and ManyRelatedManager objects.

One interesting thing I found, too, was that there’s no mention of “RelatedManager” or “ManyRelatedManager” in the online Django documentation. This is highly unusual. In my experience, Django’s documentation blows away the docs for just about any project in existence. Did I miss something?

  • http://speno.blogspot.com/ John P. Speno

    Nice clear write up. What you discovered seems to make perfect sense in terms of Django’s template context variable lookups. Maybe it’s one of those things that’s just too obvious to be worth documenting?

    Take care.

  • Malcolm Tredinnick

    Django’s documentation doesn’t mention things like RelatedManager because they’re internal implementation details. You never need to know what they are to use Django and they aren’t part of the public API. Django does document how to traverse to related fields, as you’ve discovered and that is the relevant piece of information here. There’s a mild argument to be made that we could title a couple of sections slightly differently and maybe tweak the text, since it’s sometimes easy to miss.

  • m0j0

    @malcolm — I suppose that’s fair, though it would’ve made debugging issues I had a breeze if I could’ve gotten something back when searching the django docs for “ManyRelatedManager”. I mean, Manager objects are covered, and related objects are covered. Maybe it’s more an argument for either more advanced documentation (and maybe this is out there and I just don’t know about it), or a troubleshooting reference section away from the main docs or something. Perhaps this feat is actually best left to the community. I don’t have the answer. Maybe it’s a case of Django being a victim of its own outstanding documentation: the expectations of the users are raised, and can never be met anyway. Still, I applaud the project for trying harder than most.

  • Pingback: Daily Digest for August 31st | William Stearns

  • http://www.doveandjosh.com Joshua Russo

    I just implemented something similar to this using generic views. I got the idea from the tutorial that talks about using modelX.relationY_set. Something to remember about the object variable in templates (or any object you pass into your context) is that you have access to any of the properties and methods that do not require arguments. (I could be wrong, in that there may be a way to call methods with arguments but I have yet to need them)

  • http://phpcommit.wordpress.com Julio

    save my day, thanks

  • Pingback: Representing Relationships in Django Templates Without Writing Extra Code (RelatedManager and ManyRelatedManager) | Musings of an Anonymous Geek

  • Jean-Baptiste

    Hello, It seems that the Django documentation was updated since your post : https://docs.djangoproject.com/en/dev/ref/models/relations/.