使用Django Dynamic Models

任务: html

在数据库中按用户id生成表。 python

由于用户的数量在增长,因此表的生成时动态的。 sql

Django框架里,使用Model类把每条数据库记录生成一个对象。一个Model对应一张表。换句话说就是建立动态Model。 数据库

官方文档中简述建立Model类django

Internally, Django uses metaclasses to create models based on a class you provide in your source code. …, that means that rather than your classes being the actual models, Django receives a description of your class, which it uses to create a model in its place. 从内部来看,Django是根据你在源代码中提供的类,用动态建立类建立Model。也就是说,Django只是收到了你提供的类在其所在 位置建立Model的描述,而不是实际的Model。 app

由于BDFL的积极推进,Unifying types and classes in Python 2.2,因此建立一个类可使用type句型。 框架

Creating

tpye建立Model: ide

from Django.db import models
Secretcode = type('Secretcode', (models.Model,), {
    'timestamp': models.DateTimeField(auto_now=True),
    'uid': models.CharField(max_length=32,),
    'secretcode':models.CharField(max_length=10),
    'cid':models.CharField(max_length=20,blank=True,null=True),
})

这实际上等价于: ui

from Django.db import models

class Secretcode(models.Model):
    timestamp=models.DateTimeField(autonow=True)
    uid= models.CharField(max_length=32,)
    secretcode=models.CharField(max_length=10)
    cid=models.CharField(max_length=20,blank=True,null=True)

以此能够实现一个动态建立Model的普适方法(Officail Docs provided): this

def create_model(name, fields=None, app_label='', module='', options=None, admin_opts=None):
    """
    建立指定model
    """
    class Meta:
        # Using type('Meta', ...) gives a dictproxy error during model creation
        pass

    if app_label:
        # app_label必须用Meta内部类来设定
        setattr(Meta, 'app_label', app_label)

    # 若提供了options参数,就要用更新Meta类
    if options is not None:
        for key, value in options.iteritems():
            setattr(Meta, key, value)

    # 建立一个字典来模拟类的声明,module和当前所在的module对应
    attrs = {'__module__': module, 'Meta': Meta}

    # 加入全部提供的字段
    if fields:
        attrs.update(fields)

    # 建立这个类,这会触发ModelBase来处理
    model = type(name, (models.Model,), attrs)

    # 若是提供了admin参数,那么建立Admin类
    if admin_opts is not None:
        class Admin(admin.ModelAdmin):
            pass
        for key, value in admin_opts:
            setattr(Admin, key, value)
        admin.site.register(model, Admin)

    return model

app_label和module能够指定为想要依附的app的对应信息。

其实,models.Model类只是封装了数据库操做。换句话说,假若用户了解数据库的中某张表描述信息,那么用上边的方法建立对应的Model也能够正确地对该表进行操做。

因此,创建一个Model最重要的是提供对正确的表的描述,以便于在任意时刻都能用上边的办法创建或重建咱们想要的Model(它也能够被表述成:指定数据表操做集)

考虑要重建,那么保存好建立初提供的Model信息就相当重要。

数据库驱动方法——由于动态Model的所有信息可按类别分开,将这些信息保存到按类别建立的数据表中,重建的时候只要取出Model信息再使用上边的办法便可。

这是一个绝妙的方法。

!如下实现代码未验证。

from django.core.validators import ValidationError
from django.db import models

class MyApp(models.Model):
    name = models.CharField(max_length=255)
    module = models.CharField(max_length=255)

    def __str__(self):
        return self.name

class MyModel(models.Model):
    app = models.ForeignKey(MyApp, related_name='mymodels')
    name = models.CharField(max_length=255)

    def __str__(self):
        return self.name

    def get_django_model(self):
        "Returns a functional Django model based on current data"
        # Get all associated fields into a list ready for dict()
        fields = [(f.name, f.get_django_field()) for f in self.myfields.all()]

        # Use the create_model function defined above
        return create_model(self.name, dict(fields), self.app.name, self.app.module)

    class Meta:
        unique_together = (('app', 'name'),)

def is_valid_field(self, field_data, all_data):
    if hasattr(models, field_data) and issubclass(getattr(models, field_data), models.Field):
        # It exists and is a proper field type
        return
    raise ValidationError("This is not a valid field type.")

class MyField(models.Model):
    model = models.ForeignKey(Model, related_name='myfields')
    name = models.CharField(max_length=255)
    type = models.CharField(max_length=255, validators=[is_valid_field])

    def get_django_field(self):
        "Returns the correct field type, instantiated with applicable settings"
        # Get all associated settings into a list ready for dict()
        settings = [(s.name, s.value) for s in self.mysettings.all()]

        # Instantiate the field with the settings as **kwargs
        return getattr(models, self.type)(*dict(settings))

    class Meta:
        unique_together = (('model', 'name'),)

class MySetting(models.Model):
    field = models.ForeignKey(Field, related_name='mysettings')
    name = models.CharField(max_length=255)
    value = models.CharField(max_length=255)

    class Meta:
        unique_together = (('field', 'name'),)

官方提供的这个实现代码很是严谨,从上到下,自第二个起先后创建外键关系,前面的class反向检索用的关系名(related_name)是后面的class_name的小写。

  • MyApp储存app_label和module。
  • MyModel储存model_name,但最终生成Model靠的是它。(MyModel_instance.get_django_model)
  • MyField决定的是列名和字段类型。
  • MySetting决定的是字段参数。

Database Migration

上面几个方法建立的Model仅仅在Runtime时的cache里面,它们的对象不能往数据库里写入一点实质内容。由于它们在数据库里没有他们对应的表。

通常状况下,表的建立须要经过manage.py命令——syncdb实现,这里可使用Djangp自带的sql语句生成执行钩子:

 One workaround for basic models uses an internal portion of django.core.management to install a basic table definition to the database.

def install(model):
    from django.core.management import sql, color
    from django.db import connection

    # Standard syncdb expects models to be in reliable locations,
    # so dynamic models need to bypass django.core.management.syncdb.
    # On the plus side, this allows individual models to be installed
    # without installing the entire project structure.
    # On the other hand, this means that things like relationships and
    # indexes will have to be handled manually.
    # This installs only the basic table definition.

    # disable terminal colors in the sql statements
    style = color.no_style()

    cursor = connection.cursor()
    statements, pending = sql.sql_model_create(model, style)
    for sql in statements:
        cursor.execute(sql)

还可使用金牌插件south提供的钩子:

def create_db_table(model_class):
    """ Takes a Django model class and create a database table, if necessary.
    """
    # XXX Create related tables for ManyToMany etc

    db.start_transaction()
    table_name = model_class._meta.db_table

    # Introspect the database to see if it doesn't already exist
    if (connection.introspection.table_name_converter(table_name) 
                        not in connection.introspection.table_names()):

        fields = _get_fields(model_class)

        db.create_table(table_name, fields)
        # Some fields are added differently, after table creation
        # eg GeoDjango fields
        db.execute_deferred_sql()
        logger.debug("Created table '%s'" % table_name)

    db.commit_transaction()

def delete_db_table(model_class):
    table_name = model_class._meta.db_table
    db.start_transaction()
    db.delete_table(table_name)
    logger.debug("Deleted table '%s'" % table_name)
    db.commit_transaction()

这里推荐后者,不只是由于其鲁棒性更好,还由于其比起Django自带database migration更加良心。

Summary

Dynamic Model的基本实现方法和原理以上就是了,可是还有不少人为此研究更加适合production的具体方法:

Django dynamic model fields:As of today, there are four available approaches, two of them requiring a certain storage backend… 

  1. Django-eav
  2. Django-hstore
  3. Django MongoDB
  4. Dynamic models based on syncdb and South-hooks

continute reading…

这里须要单独说明的是Will Hardy‘s approach,也就是上面引用中提到的第四种方法。考虑周全,严丝合缝,提供的demo源代码阅读起来有些不适。但其关键代码在utils.py中,其余模块是为这个demo实现功能服务的。切莫一叶障目。

总结:

  1. Dynamic Model是由两部分组成:动态建立Model和动态建立database table
  2. Dynamic Model建立后将保存在Django cache中,若无心外,始终存在。
  3. 若出意外,动态建立的Model的重建只要保证model_name、app_label、module和fields信息相同,就能保证还原的Model出最初建立的那一个效果同样,也就能对你想要的那张表进行操做。
  4. 假若Model的建立信息(如app_name、module、fields的值)也因需求不一样而变化,那么为了Django重启后能正确重建Model,最好的解决方案是数据库驱动的方法。

 

参考:

Dynamic modelshttps://code.djangoproject.com/wiki/DynamicModels

Stack Overflow-Django dynamic model fields:http://stackoverflow.com/questions/7933596/django-dynamic-model-fields/7934577#7934577

Runtime Dynamic Models with Django:http://dynamic-models.readthedocs.org/en/latest/index.html#runtime-dynamic-models-with-django

相关文章
相关标签/搜索