Defining a dynamic model factory

The basic principle that allows us to create dynamic classes is the built-in function type(). Instead of the normal syntax to define a class in Python:

class Person(object):
    name = "Julia"

The type() function can be used to create the same class, here is how the class above looks using the type() built-in:

Person = type("Person", (object,), {'name': "Julia"})

Using type() means you can programatically determine the number and names of the attributes that make up the class.

Django models

Django models can be essentially defined in the same manner, with the one additional requirement that you need to define an attribute called __module__. Here is a simple Django model:

class Animal(models.Model):
    name = models.CharField(max_length=32)

And here is the equivalent class built using type():

attrs = {
    'name': models.CharField(max_length=32),
    '__module__': 'myapp.models'
}
Animal = type("Animal", (models.Model,), attrs)

Any Django model that can be defined in the normal fashion can be made using type().

Django’s model cache

Django automatically caches model classes when you subclass models.Model. If you are generating a model that has a name that may already exist, you should firstly remove the existing cached class.

There is no official, documented way to do this, but current versions of Django allow you to delete the cache entry directly:

from django.db.models.loading import cache
try:
    del cache.app_models[appname][modelname]
except KeyError:
    pass

Note

When using Django in non-official or undocumented ways, it’s highly advisable to write unit tests to ensure that the code does what you indend it to do. This is especially useful when upgrading Django in the future, to ensure that all uses of undocumented features still work with the new version of Django.

Using the model API

Because the names of model fields may no longer be known to the developer, it makes using Django’s model API a little more difficult. There are at least three simple approaches to this problem.

Firstly, you can use Python’s ** syntax to pass a mapping object as a set of keyword arguments. This is not as elegant as the normal syntax, but does the job:

kwargs = {'name': "Jenny", 'color': "Blue"}
print People.objects.filter(**kwargs)

A second approach is to subclass django.db.models.query.QuerySet and provide your own customisations to keep things clean. You can attach the customised QuerySet class by overloading the get_query_set method of your model manager. Beware however of making things too nonstandard, forcing other developers to learn your new API.

from django.db.models.query import QuerySet
from django.db import models

class MyQuerySet(QuerySet):
    def filter(self, *args, **kwargs):
        kwargs.update((args[i],args[i+1]) for i in range(0, len(args), 2))
        return super(MyQuerySet, self).filter(**kwargs)

class MyManager(models.Manager):
    def get_query_set(self):
        return MyQuerySet(self.model)

# XXX Add the manager to your dynamic model...

# Warning: This project uses a customised filter method!
print People.objects.filter(name="Jenny").filter('color', 'blue')

A third approach is to simply provide a helper function that creates either a preprepared kwargs mapping or returns a django.db.models.Q object, which can be fed directly to a queryset as seen above. This would be like creating a new API, but is a little more explicit than subclassing QuerySet.

from django.db.models import Q

def my_query(*args, **kwargs):
    """ turns my_query(key, val, key, val, key=val) into a Q object. """
    kwargs.update((args[i],args[i+1]) for i in range(0, len(args), 2))
    return Q(**kwargs)

print People.objects.filter(my_query('color', 'blue', name="Jenny"))

What comes next?

Although this is enough to define a Django model class, if the model isn’t in existence when syncdb is run, no respective database tables will be created. The creation and migration of database tables is covered in database migration.

Also relevant is the appropriately time regeneration of the model class, (especially if you want to host using more than one server) see model migration and, if you would like to edit the dynamic models in Django’s admin, admin migration.