How to add counter column in django-tables2?
Asked Answered
V

8

6

I'm trying to add a counter on the first column of a table using django-tables2, but the solution below is only showing all 0 under the # column. How should I add a column that will have a column that numbers the rows?

tables.py:

import django_tables2 as tables
from profiles.models import Track
import itertools
counter = itertools.count()

class PlaylistTable(tables.Table):

    priority = tables.Column(verbose_name="#", default=next(counter))

    class Meta:
        model = Track
        attrs = {"class": "paleblue"}
        orderable = False
        fields = ('priority', 'artist', 'title')

My template:

{% render_table table %}
Vita answered 8/6, 2016 at 6:38 Comment(1)
You could try setting the default to lambda _: next(counter), although even if that worked it would be pretty ugly/hacky.Broads
W
6

Other answers all have the itertools.count instance in the toplevel scope of the tables.py file. This makes the counter persist between page loads, it will only be reset when the server is restarted. A better solution is to add the counter as instance variable on the table like this:

import django_tables2 as tables
import itertools

class CountryTable(tables.Table):
    counter = tables.Column(empty_values=(), orderable=False)

    def render_counter(self):
        self.row_counter = getattr(self, 'row_counter', itertools.count())
        return next(self.row_counter)

This will make sure the counter is reset every time the table is instantiated.

Wojcik answered 9/6, 2016 at 8:40 Comment(0)
L
4

Building on Jieter's answer, you can handle pagination with this minor modification:

import django_tables2 as tables
import itertools

class CountryTable(tables.Table):
    counter = tables.Column(empty_values=(), orderable=False)

    def render_counter(self):
        self.row_counter = getattr(self, 'row_counter',
                                   itertools.count(self.page.start_index()))
        return next(self.row_counter)

The row numbering will be globally correct even in pages after the first. Note that the index is 1-based in this case.

Lynda answered 27/6, 2019 at 9:16 Comment(0)
A
3

From the documentation of Column

default (str or callable):

The default value for the column. This can be a value or a callable object [1]. If an object in the data provides None for a column, the default will be used instead.

[1] - The provided callable object must not expect to receive any arguments.

What you are passing next(counter) you are passing the result of a function which seems to be an integer.

You can either define a function:

def next_count():
    return next(counter)

and, use it as default:

priority = tables.Column(verbose_name="#", default=next_count)

Or, you can use the lambda function as mentioned in @Sayse's comments:

priority = tables.Column(verbose_name="#", default=lambda: next(counter))
Alyssaalyssum answered 8/6, 2016 at 6:58 Comment(5)
Be weary of lambda's though, django tries to stop you setting model defaults to lambdas (kind of to stop this kind of thing). I'm not sure how well this will support asyncronous requests either.Broads
In that case, the first approach with the function would be useful. :)Alyssaalyssum
Yeah, that would be better as a callable, but the problem remains that the counter is global, even if you were to make the table twice, the counter would be starting from where the last one finished instead of 1 again :)Broads
yeah.. that's true. I am not sure what OP is trying to achieve though.Alyssaalyssum
Sayse was right.. refreshing the page causes the counter to start where the last row finished. I'm just trying to add a counter column to show each row's position for easier row reference.Vita
K
0

Adding to Jieter's answer, Inside your table class, implement these functions. The init method is being used to get the request variable from the view class in order to fetch the page number. Change render_id to render_yourcolumn name.

class ClassName:
    def __init__(self, *args, **kwargs):
        self.request = kwargs.pop('request', None)
        super(ClassName, self).__init__(*args, **kwargs)

    def render_id(self):
        page = int(self.request.GET.get('page', 1)) - 1
        self.row_counter = getattr(self, 'row_counter', itertools.count(start=page*25+1)) # Change 25 to the number of rows in one page
        return next(self.row_counter)

Now in the views file, call your Table class as:

table = ClassName(ModelName.objects.values(), request=request)

By this method your counter will be correct for n number of pages. :)

Kenzie answered 2/9, 2019 at 10:26 Comment(0)
F
0

Had similar problem and didn't find any simple solution that worked on paginated tables without the counter reseting on each page

From official FAQ: How to create a row counter? (doesn't support pagination)

Using already available paginator attributes: https://mcmap.net/q/1770391/-django-row-number-in-pagination (similar general django approach)

import django_tables2 as tables

class NumberedTable(tables.Table):
    serial_column = tables.TemplateColumn(
        verbose_name='#',
        "{{ row_counter|add:table.page.start_index }}",

    )
   ...
Felicle answered 12/11, 2019 at 13:9 Comment(0)
B
0

Here is an unprofessional way to do it, without using itertools module:

import django_tables2 as table

class GenericTable(table.Table):
    """GenericTable

    """
    counter = table.Column(
        verbose_name='#',
        orderable=False,
        accessor='pk',
    )

    def render_counter(self, record):
        """render_counter

        :param record: record
        :return: custom render
        """
        records = list(self.data)
        index = records.index(record)

        # counter = index  # counter starts from 0
        counter = index + 1  # counter starts from 1
        return counter
Brigham answered 10/5, 2020 at 13:36 Comment(0)
C
0

from Jieter answer

if do not want to start every page from zero you can do :

import django_tables2 as tables
import itertools

class CountryTable(tables.Table):
    counter = tables.Column(empty_values=(), orderable=False)

    def render_counter(self):
        self.row_counter = getattr(self, 'row_counter', itertools.count())
        return next(self.row_counter)+self.page.start_index()
        # self.page.sart_index() is default Table function and return number of start index per page
Criticize answered 23/11, 2020 at 16:20 Comment(0)
L
0

from the doc

counter = tables.TemplateColumn("{{ row_counter|add:'1' }}")

Libratory answered 20/11, 2023 at 14:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.