En kodare

Anders Hovmöller
GitHub
twitter
email
About Blog Apps

iommi vs django-tables2+django-filters

2024-09-19

Someone asked on the Unofficial Django Discord about a performance problem with django-tables2 + django-filters. It’s a pretty clear example of what iommi can give you.

Here is the original code:

Model

class Assembly(models.Model):
    status = models.CharField(max_length=20)
    id_number = models.IntegerField(unique=True)
    location = models.ForeignKey(Location, ...)

Table definition

class AssemblyTable(tables.Table):
    id_number = tables.columns.LinkColumn(
        "dashboard:assembly-update", args=[A("id")])

    class Meta:
        model = Assembly
        fields = [
            "id_number",
            "status",
            "location",
        ]

Filter definition

class AssemblyFilter(FilterSet):
    class Meta:
        model = Assembly
        fields = {
            "id_number": ["exact"],
        }

View

class AssemblyListView(
        LoginRequiredMixin, 
        SingleTableMixin, 
        FilterView):
    model = Assembly
    table_class = AssemblyTable
    filter_class = AssemblyFilter
    template_name = "dashboard/assembly_list.html"

Template

{% block content %}
  <div class="mb-2">
    <div class="d-flex text-center"></div>
    <div class="row">
      <div class="col-lg-10 col-xs-12 p-2 pt-0">
        {% render_table table %}
      </div>
      <div class="filters col-lg-2 col-xs-12">
        <div class="card text-bg-light">
          <div class="card-body p-2">
            <form method="get">
              {{ filter.form.as_p }}
            </form>
          </div>
        </div>
      </div>
    </div>
  </div>
{% endblock %}

The problem was that the code has an N+1 issue, which is fixed by overriding get_queryset on AssemblyListView and doing select_related('location').

iommi version

The corresponding code in iommi looks like this:

assembly_table = Table(
    auto__model=Assembly,
    auto__include=[
        "id_number",
        "status",
        "location",
    ],
    columns__id_number=dict(
         cell__url=lambda row, **_: 
            reverse('dashboard:assembly-update', row.id),
         filter__include=True,
    )
)

This code does not have the N+1 issue as the trivial cases where you need select_related or prefetch_related are handled automatically for you.

Maybe the biggest difference is that there no need for a template.

You might have seen such demos before, but when you’ve tried to use the library you have noticed that you have to rewrite everything from scratch if you want any customization, almost no matter how trivial. This is not the case for iommi. We have customization on every level, from the entire table, to the row, to the cell; and for forms, the entire form, the field, the input, the label, etc. You can customize by inserting or removing individual CSS classes, attributes, style definitions, and if that all fails you can fall back to rendering the cell/row/table/field/whatever with a custom template.

But maybe more than that: we consider it a serious bug if a customization you need to do is not easy to do. I think this attitude is what really sets us apart in the long run.

« Why we wrote a new form library for Django Permissions in Django: must_check »