任务:
在数据库中按用户id生成表。
因为用户的数量在增加,所以表的生成时动态的。
Django框架里,使用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…
- Django-eav
- Django-hstore
- Django MongoDB
- Dynamic models based on syncdb and South-hooks
这里需要单独说明的是Will Hardy‘s approach,也就是上面引用中提到的第四种方法。考虑周全,严丝合缝,提供的demo源代码阅读起来有些不适。但其关键代码在utils.py中,其他模块是为这个demo实现功能服务的。切莫一叶障目。
总结:
- Dynamic Model是由两部分组成:动态创建Model和动态创建database table
- Dynamic Model创建后将保存在Django cache中,若无意外,始终存在。
- 若出意外,动态创建的Model的重建只要保证model_name、app_label、module和fields信息相同,就能保证还原的Model出最初创建的那一个效果一样,也就能对你想要的那张表进行操作。
- 倘若Model的创建信息(如app_name、module、fields的值)也因需求不同而变化,那么为了Django重启后能正确重建Model,最好的解决方案是数据库驱动的方法。
参考:
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