You could use a JSONfield to store all the rows for all the tables. Something like
class DynamicTableSchema( models.Model):
table_name = models.CharField( max_length= ...)
schema = models.JSONField( ...)
class DynamicTable( models.Model):
table_name = models.CharField( max_length= ...)
# table = models.ForeignKey( 'DynamicTableSchema', ..., related_name='rows', ) # alternative
data = models.JSONField( ...)
schema would contain a list of valid column names. You might also store associated information for validation here, like what is acceptable data, and what is the human-readable column label.
data would contain the data for the rows in the named table.
Conceptual examples:
table = TableSchema.objects.get( name='MyTable')
print(table.schema)
{ 'quantity':{
'type':'integer',
'default': 0,
'label': 'quantity'
},
'description':{
'type':'string',
'default': None,
'label': 'Item Description',
'max_len': 80
}
}
foo = DynamicTable.objects.filter( table_name='foo').first()
# or using alternative ForeignKey,
# foo = table.rows.first()
print( foo.data)
{
'quantity': 42,
'description': 'Wooly Socks',
}
If your underlying DB is Postgres, then its abilities to search such data by queryset is considerable.
You will be responsible for dealing with validation of the user's data against the schema (dynamically created forms?) and dealing with insertion of default values when a new column is created (or implicitly generating a default value when such a column is first accessed). There's a pretty close relationship to be found between form.valid_data and data in the model.
The Python package Cerberus might also be helpful.