Using Bootstrap Buttons with Django-Tables2

Lots of my Django projects involve displaying some list of results in a table. Doing that in with a for loop in a template is easy enough, but if a project involves more than one page of results, using django-tables2 can make it even easier. It offers a few advantages over hand-built tables:

  • Clean templates

  • DRY with classes

  • Automatic sorting with clickable headers

  • Automatic pagination

  • Integration with django-filter

When displaying a list of results, I often want to have an open or edit button to let users go to the details form for each item. An example from my training database:

/images/tables_open_button.png

This post won't cover the full use of django-tables2. For that see the docs.

In my settings I've told django-tables2 to use the built-in Bootstrap4 template.

settings.py

DJANGO_TABLES2_TEMPLATE = "django_tables2/bootstrap4.html"

To add the Open button we need to add a column to our table class. In the example below the date, description, tags, and points columns are all part of the model. The edit column is where we add our button.

tables.py

from django_tables2 import tables, TemplateColumn
from my_app.models import Training

class TrainingTable(tables.Table):
    class Meta:
         model = Training
         attrs = {'class': 'table table-sm'}
         fields = ['date', 'description', 'tags', 'points', 'edit']

     edit = TemplateColumn(template_name='pages/tables/training_update_column.html')

The TemplateColumn lets you load arbitrary HTML into a cell. The HTML can be a snippet of a Django template. The column will pass in useful data that you can use in your template. In this case I'm using 'record', but you can also pass in any arbitrary data using the 'extra_context'.

In this case I'm also setting a few Bootstrap classes to style the table the way I like it using attrs in Meta. This is optional.

The template itself is just a single line that defines the button using some Bootstrap classes and builds the link in the normal Django way. 'training_update' is the name of a path in urls.py. The important bit is that record is passed in by the table. It represents the row and because this table is using a model it has access to all the fields in the model; even those not displayed in the table. In this case record.id is the primary key that the path expects.

pages/tables/training_update_column.html

<a class="btn btn-info btn-sm" href="{% url 'training_update' record.id %}">Open</a>

Because the page template is already loading Bootstrap there is no reason to do it here. The templates aren't limited to a single line. You can load as much as you like.

To reuse the template across multiple tables you could easily use the extra_context to pass in the path name for the contents in the table and then access it from the template. In my case, the tables have different styling and since the templates are so small, the duplication in code is minimal and I prefer the simplicity of having a separate template for each table's edit column.

One of the best parts, as mentioned above, is how clean the page's template becomes:

pages/training_list.html

{% extends "base.html" %}
{% load django_tables2 %}

{% block content %}
{% render_table table %}
{% endblock content %}

In this case the template pulls in my base template (where we pull in the Bootstrap stuff), loads the django_tabes2 requirements and then renders the table. The entire table is handled by the {% render_table table %}. All the styling is handled by the classes we added in the TrainingTable class definition.

While this example uses Bootstrap throughout, that isn't at all necessary. You could use another framework or pure, handcrafted, artisanal HTML. Whatever fits your project.

Comments

Comments powered by Disqus