← Back to list

Django: Quickly generate a table for related models

Learn three different techniques for easily generating a table for related models on a detail view for many-to-one or m2m model relationship

Adrien Van Thong in Django Unleashed · 2026-05-27 06:16 · 15 claps · 5.8 min read
#python #django #django-class-based-views #django-tutorial #data-science
Open on Medium ↗
Wiki topics: ML · Machine Learning 🌐 · Web Development 🔬 · Science · General 💑 · Relationships

Photo by Mahbod Akhzami on Unsplash

Photo by Mahbod Akhzami on Unsplash

Django: Quickly generate a table for related models

In a recent article, I described how to leverage the django-tables plugin’s CBVs to very quickly generate a table view for our CBVs. The plugin provides a highly flexible framework for customizing and abstracting tables, gives us sorting and filtering out of the box and saves us from having to manually create and manage HTML tables ourselves.

The example we had in that article was very straightforward: quickly generate a simple table for a list of records for a specific model. We accomplished this by inheriting from the SingleTableView CBV provided by the django-table framework.

For today’s article, I will take this a step further and describe how we can create our own custom mixin which does the same thing, but this time we’ll modify the behaviour to display all the related records for the other model in a foreign key relationship: for example, on a “author detail” page, generate a table containing all the books published by the current author.

Example

Let’s start with a simple situation. We have a Django webapp for a library, which tracks books and authors. Here is are the models we will use for this:

from django.db import models

class Author(models.Model):
    first_name = models.CharField(max_length=255)
    last_name = models.CharField(max_length=255)

class Book(models.Model):
    title = models.CharField(max_length=255)
    author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='books')
    isbn = models.CharField(max_length=13, unique=True)

For the purposes of this article, we want to create a single view, AuthorDetailView which provides all the relevant information about the author:

from django.views.generic import DetailView
from .models import Author

class AuthorDetailView(DetailView):
    model = Author
    template_name = 'author_details.html'

The next step for this view is we want to show a nice table containing all the books this author has written. Of course, we want to leverage django-tables so the table is sortable, filterable, paginated, etc. Here is our table class to show the books:

from django_tables2.tables import Table
from .models import Book

class BookTable(Table):
    class Meta:
        model = Book
        fields = ('title', 'author', 'isbn')

In this article, we’ll explore three different ways to construct a CBV to show all the details for an author, including connecting this Table class to neatly display the complete list of books published by that author.

Solution 1: DetailView

The first solution is to keep our view as-is, and enhance it slightly by adding a Table object to the context data sent to the template. Here is what that would look like:

from django.views.generic import DetailView
from .models import Author
from .tables import BookTable

class AuthorDetailView(DetailView):
    model = Author
    template_name = 'author_details.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        books_table = BookTable(self.object.books.all())
        context['table'] = books_table
        return context

On the template side, we simply load in the plugin and pass forward the table object as we normally would:

{% extends "base.html" %}
{% load django_tables2 %}
{% block title %}Author Details: {{ author }}{% endblock %}

{% block content %}
<h1>Author Details: {{ author }}</h1>
Total books: {{ author.books.count }}
{% render_table table %}
{% endblock %}

While this resolves the problem in a fairly straightforward way, we end up re-implementing a lot of what we get “for-free” with the SingleTableView. A more pressing concern is that because this is operating independent of the SingleTableView, we cannot leverage any other mixins which build upon the SingleTableView. Let’s try to address that in our next attempt.

Solution 2: SingleTableView

For this second attempt, we’re going to abandon the DetailView and instead inherit directly from the SingleTableView. This will preserve our ability to integrate with other mixins which interact with the SingleTableView. This will require use to shift our perspective from AuthorDetailView to BookTableView. In other words, we’re approaching this from the other side of the relationship between these two models.

We’ll start with a trivial SingleTableView over the Book model:

from django_tables2.views import SingleTableView
from .models import Book
from .tables import BookTable

class AuthorBooksTableView(SingleTableView):
    model = Book
    table_class = BookTable
    template_name = 'author_details.html'

However, because we are trying to specifically show only the books associated with a particular author, we’re going to need to overwrite the get_queryset method to filter down the Book records being shown on the page:

  def get_queryset(self):
      return super().get_queryset().filter(author_id=self.kwargs['pk'])

Similarly, we also need to fetch the relevant Author record, and make sure it is sent as part of the context data to the template:

  def get_context_data(self, **kwargs):
      context = super().get_context_data(**kwargs)
      context['author'] = get_object_or_404(Author, pk=self.kwargs['pk'])
      return context

Note that due to how we implemented this, we can point this CBV at the same template as our earlier AuthorDetailView was using, and without a single change to the template, the page is still rendered exactly the same:

from django.shortcuts import get_object_or_404
from django_tables2.views import SingleTableView
from .models import Author, Book
from .tables import BookTable

class AuthorBooksTableView(SingleTableView):
    model = Book
    table_class = BookTable
    template_name = 'author_details.html'

    def get_queryset(self):
        return super().get_queryset().filter(author_id=self.kwargs['pk'])

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['author'] = get_object_or_404(Author, pk=self.kwargs['pk'])
        return context

While this solution has preserved our ability to integrate our view with any other SingleTableView-compatible mixins within our environment, we’ve unfortunately sacrificed the core functionality that DetailView provided us, and as a result ended up with a lot of unnecessary boilerplate code. This problem gets duplicated with each new similar view we create.

In our last solution, let’s attempt to merge together the best of both worlds.

Solution 3: Create new mixin

Our first solution was the Author-centric approach which leveraged Django’s built-in DetailView, but sacrificed SingleTableView's built-functionality.

Our second solution reversed that by taking a Book-centric approach directly on top of SingleTableView but resulted in a fair amount of unnecessary boilerplate code which DetailView already took care of for us.

In this approach, we’re going to create our own mixin which inherits the core functionality from both views, and creates a new ready-to-go mixin which can do both. I’m going to call it DetailWithRelatedTableMixin (yeah, it’s a bit long…suggest alternatives in the comments below). The secret to creating this is to combine two existing Mixins: SingleTableMixin (which SingleTableView inherits), and SingleObjectMixin (which DetailView inherits). Together, these two mix-ins will accomplish almost everything we need in a single place. The only thing left is for us to “glue” them together.

First, for our mixin to be usable by child classes, we’ll need to define the related name for traversing the relationship between the two models. In our case, this will be books. We’ll abstract this into a configurable field which subclasses which define, and a new method get_related_queryset which subclasses can optionally overwrite to further filter the queryset:

from django.views.generic.detail import SingleObjectMixin
from django_tables2 import SingleTableMixin

class DetailWithRelatedTableMixin(SingleTableMixin, SingleObjectMixin):
    related_name = None  # The name of the related manager to use for the table queryset.

    def get_related_queryset(self):
        if not self.related_name:
            raise AttributeError(f"{self.__class__.__name__}: related_name must be set")
        return getattr(self.object, self.related_name).all()

Now that we have a method which defines what data will go into the table, we need to actually feed this data into the table. This is accomplished very easily by overwriting the get_table_data method:

from django.views.generic.detail import SingleObjectMixin
from django_tables2 import SingleTableMixin

class DetailWithRelatedTableMixin(SingleTableMixin, SingleObjectMixin):
    related_name = None  # The name of the related manager to use for the table queryset.

    def get_related_queryset(self):
        if not self.related_name:
            raise AttributeError(f"{self.__class__.__name__}: related_name must be set")
        return getattr(self.object, self.related_name).all()

    def get_table_data(self):
        return self.get_related_queryset()

Let’s test this new mixin out with a new CBV, AuthorDetailWithBooksView which will inherit our new mixin:

from django.views.generic import DetailView
from .mixins import DetailWithRelatedTableMixin
from .models import Author
from .tables import BookTable

class AuthorDetailWithBooksView(DetailWithRelatedTableMixin, DetailView):
    model = Author
    template_name = 'author_details.html'
    context_object_name = 'author'
    related_name = 'books'
    table_class = BookTable

As we can see our new CBV is extremely straight-forward to use, and the best part is we can re-use this mixin for all sorts of other views with no repeated boilerplate code.

We tried three different approaches for displaying table data for a related model.

First, we started with a standard DetailView with a Table class shimmied into the get_context_data method. This approach works best for very simple and straightforward implementation that will not need to integrate with other table features.

Next, we tried from the relate model’s perspective using a SingleTableView. While it has all the features we needed, including the ability to integrate with other mixins, it resulted in a lot of boilerplate code that would end up being re-written any other time we employed this strategy.

Our last approach was to combine the two other view’s parent mixins to create a new mixin which can be readily used by any number of views which need to satisfy this same goal. This is my favourite approach as it provides and extensible and re-usable solution.

What are your thoughts? Do you have a preferred solution? Is there a better way not listed here? Sound off in the comments below.

Recommended Reading


메타데이터
post_id
d41b02c1f5c4
slug
django-quickly-generate-a-table-for-related-models-d41b02c1f5c4
url
https://medium.com/django-unleashed/django-quickly-generate-a-table-for-related-models-d41b02c1f5c4
canonical_url
https://medium.com/django-unleashed/django-quickly-generate-a-table-for-related-models-d41b02c1f5c4
author_url
https://medium.com/@adrienvanthong
status
ok
fetched_at
2026-06-09 15:37:30