So for a Django project, I would really like to be able to generate and display tables (not based on querysets) dynamically without needing to know the contents or schema beforehand.
It looks like the django-tables2 app provides nice functionality for rendering tables, but it requires that you either explicitly declare column names by declaring attributes on a custom-defined Table subclass or else provide a model for it infer the columns.
I.e, to use a column named "name", you'd do:
class NameTable(tables.Table):
name = tables.Column()
The Tables class does not provide a method for adding columns post-facto because, from reading the source, it seems to use a metaclass that sweeps the class attributes on __new__ and locks them in.
It seemed like very simple metaprogramming would be an elegant solution. I defined a basic class factory that accepts column names are arguments:
def define_table(columns):
class klass(tables.Table): pass
for col in columns:
setattr(klass, col, tables.Column())
return klass
Sadly this does not work. If I run `
x = define_table(["foo", "bar"])(data)
x.foo
x.bar
I get back:
But if I list the columns:
print x.base_columns
I get back nothing i.e. {}
I realize that there are probably simpler solutions (e.g. just bite the bullet and define every possible data configuration in code, or don't use django-tables2 and roll my own), but I am now treating this as an opportunity to learn more about meta programming, so I would really like to make this work this way.
Any idea what I'm wrong doing wrong? My theory is that the __new__ method (which is redefined in the metaclass Table uses) is getting invoked when klass is defined rather than when it's instantiated, so by the time I tack on the attributes it's too late. But that violates my understanding of when __new__ should happen. Otherwise, I'm struggling to understand how the metaclass __new__ can tell the difference between defined-in-code attributes vs. dynamically defined ones.
Thanks!
解决方案
You're on the right track here, but instead of creating a barebones class and adding attributes to it, you should use the type() built-in function. The reason it's not working the way you're trying, is because the metaclass has already done its work.
Using type() allows you to construct a new class with your own attributes, while setting the base class. Meaning - you get to describe the fields you want as a blueprint to your class, allowing the Tables metaclass to take over after your definition.
Here's an example of using type() with django. I've used this myself for my own project (with some slight variations) but it should give you a nice place to start from, considering you're already almost there.
def define_table(columns):
attrs = dict((c, tables.Column()) for c in columns)
klass = type('DynamicTable', (tables.Table,), attrs)
return klass