Search code examples
pythondjangodjango-tables2

django-table2 multi-column sorting UI


I am trying to do multi-column sorting in django-tables2.

I can add ?sort=date&sort=job_number to the end of my url and it will sort by date, then job number.

But when a user clicks a column heading, it will replace the current sort querystring with the new one! Is there a way to more elegantly expose multi-column sort to the end users?

I am using the 'querystring' tag from django-tables2, but as stated above, it rewrites the value instead of appending it.


Solution

  • Okay, I have worked out a solution that works, but it isn't quite perfect, so if anyone wants to propose something better, I'm all ears!

    First, I created a new templatetag (see https://docs.djangoproject.com/en/1.8/howto/custom-template-tags/ for details about where to put a custom templatetag)

    from django import template
    
    from django_tables2.templatetags.django_tables2 import querystring
    
    register = template.Library()
    
    @register.inclusion_tag('django_tables2/header.html', takes_context=True)
    def render_header(context):
        for column in context['table'].columns:
            column.sort_existing = False
            if 'sort' in context['request'].GET:
                if column.name in context['request'].GET['sort']:
                    column.sort_existing = True
    
        return context
    

    Then, I created a custom template called django_tables2/header.html for that tag to use:

    {% load querystring from django_tables2 %}
    
    <thead>
    <tr>
        {% for column in table.columns %}
            {% if column.orderable %}
              {% if column.sort_existing %}
                <th {{ column.attrs.th.as_html }}><a href='{% querystring table.prefixed_order_by_field=column.order_by_alias.next %}'>{{ column.header }}</a></th>
              {% else %}
                <th {{ column.attrs.th.as_html }}><a href='{% querystring %}&{{ table.prefixed_order_by_field }}={{ column.order_by_alias }}'>{{ column.header }}</a></th>
                {% endif %}
            {% else %}
                <th {{ column.attrs.th.as_html }}>{{ column.header }}</th>
            {% endif %}
        {% endfor %}
    </tr>
    </thead>
    

    And Finally, I altered my django_tables2/table.html template to use my custom templatetag to render the table header, replace the table.thead block with:

        {% block table.thead %}
            {% render_header %}
        {% endblock table.thead %}
    

    And that should do the trick! Clicking on multiple column headers will sort them in the order clicked, clicking on a the same one twice will clear previous selections (and reverse the order). It isn't perfect. Perhaps I'll improve upon it later, but it works for my immediate use case.

    Perhaps I will reach out to the django_tables2 project to see if they are interested in including my custom template tag into the main project :)

    EDIT: I should note, this requires 'django.core.context_processors.request' or equivalent in your context processors in your settings.

    EDIT: Fixed table.html to correct code. Also, see https://github.com/bradleyayers/django-tables2/issues/223 to track this issue.