Vlad B
Vlad B

Reputation: 333

django-tables2 add dynamic columns to table class from hstore

My general question is: can I use the data stored in a HStoreField (Django 1.8.9) to generate columns dynamically for an existing Table class of django-tables2? As an example below, say I have a model:

from django.contrib.postgres import fields as pgfields

GameSession(models.Model):
    user = models.ForeignKey('profile.GamerProfile')
    game = models.ForeignKey('games.Game')
    last_achievement = models.ForeignKey('games.Achievement')
    extra_info = pgfields.HStoreField(null=True, blank=True)

Now, say I have a table defined as:

GameSessionTable(tables.Table):
    class Meta(BaseMetaTable):
        model = GameSession
        fields = []
        orderable=False

    id = tables.LinkColumn(accessor='id', verbose_name='Id', viewname='reporting:session_stats', args=[A('id')], attrs={'a':{'target':'_blank'}})
    started = DateTimeColumn(accessor='startdata.when_started', verbose_name='Started')
    stopped = DateTimeColumn(accessor='stopdata.when_stopped', verbose_name='Stopped')
    game_name = tables.LinkColumn(accessor='game.name', verbose_name='Game name', viewname='reporting:game_stats', args=[A('mainjob.id')], attrs={'a':{'target':'_blank'}})

I want to be able to add columns for each of the keys stored in the extra_info column for all of the GameSessions. I have tried to override the init() method of the GameSessionTable class, where I have access to the queryset, then make a set of all the keys of my GameSession objects, then add them to self, however that doesn't seem to work. Code below:

def __init__(self, data, *args, **kwargs):
    super(GameSessionTable, self).__init__(data, *args, **kwargs)

    if data:
        extra_cols=[]
        # just to be sure, check that the model has the extra_info HStore field
        if data.model._meta.get_field('extra_info'):
            extra_cols = list(set([item for q in data if q.extra_info for item in q.extra_info.keys()]))
        for col in extra_cols:
            self.columns.columns[col] = tables.Column(accessor='extra_info.%s' %col, verbose_name=col.replace("_", " ").title())

Just a mention, I have had a look at https://spapas.github.io/2015/10/05/django-dynamic-tables-similar-models/#introduction but it's not been much help because the use case there is related to the fields of a model, whereas my situation is slightly different as you can see above.

Just wanted to check, is this even possible or do I have to define an entirely different table for this data, or potentially use an entirely different library altogether like django-reports-builder?

Upvotes: 1

Views: 1361

Answers (1)

Vlad B
Vlad B

Reputation: 333

Managed to figure this out to a certain extent. The code I was running above was slightly wrong, so I updated it to run my code before the superclass init() gets run, and changed where I was adding the columns.

As a result, my init() function now looks like this:

def __init__(self, data, *args, **kwargs):
    if data:
        extra_cols=[]
        # just to be sure, check that the model has the extra_info HStore field
        if data.model._meta.get_field('extra_info'):
            extra_cols = list(set([item for q in data if q.extra_info for item in q.extra_info.keys()]))
        for col in extra_cols:
            self.base_columns[col] = tables.Column(accessor='extra_info.%s' %col, verbose_name=col.replace("_", " ").title())

    super(GameSessionTable, self).__init__(data, *args, **kwargs)

Note that I replaced self.columns.columns (which were BoundColumn instances) with self.base_columns. This allows the superclass to then consider these as well when initializing the Table class.

Might not be the most elegant solution, but it seems to do the trick for me.

Upvotes: 1

Related Questions