使用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

相關文章
相關標籤/搜索