任務: 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句型。 框架
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的小寫。
上面幾個方法建立的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更加良心。
Dynamic Model的基本實現方法和原理以上就是了,可是還有不少人爲此研究更加適合production的具體方法:
Django dynamic model fields:As of today, there are four available approaches, two of them requiring a certain storage backend…
- Django-eav
- Django-hstore
- Django MongoDB
- Dynamic models based on syncdb and South-hooks
這裏須要單獨說明的是Will Hardy‘s approach,也就是上面引用中提到的第四種方法。考慮周全,嚴絲合縫,提供的demo源代碼閱讀起來有些不適。但其關鍵代碼在utils.py中,其餘模塊是爲這個demo實現功能服務的。切莫一葉障目。
總結:
參考:
Dynamic models:https://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