使用Django Dynamic Models

任务:

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

因为用户的数量在增加,所以表的生成时动态的。

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

官方文档中简述创建Model类

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。

因为BDFL的积极推动,Unifying types and classes in Python 2.2,所以创建一个类可以使用type句型。

Creating

tpye创建Model:

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),
})

这实际上等价于:

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):

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

转载于:https://my.oschina.net/feiliang/blog/155239

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值