如何在Django ORM中建模具有任意数量任意字段类型引用的对象?

3 投票
3 回答
1099 浏览
提问于 2025-04-15 12:48

我想定义一组模型/对象,用来表示一种关系:一个字段集合(field_set)可以包含多个字段,而这些字段是django.db.model中的字段对象,比如IPAddressField、FilePathField等。

我的目标是创建一个支持特定类型“API”的ORM模型。

假设在一个控制器视图中:

# Desired api below
def homepage(request):
  from mymodels.models import ProfileGroup, FieldSet, Field

  group = ProfileGroup()
  group.name = 'Profile Information'
  group.save()

  geographicFieldSet = FieldSet()
  # Bind this 'field set' to the 'profile group'
  geographicFieldSet.profilegroup = group
  address_field = Field()
  address_field.name = 'Street Address'
  address_field.f = models.CharField(max_length=100)
  # Bind this field to the geo field set
  address_field.fieldset = geographicFieldSet 

  town_field = Field()
  town_field.name = 'Town / City'
  town_field.f = models.CharField(max_length=100)
  # Bind this field to the geo field set
  town_field.fieldset = geographicFieldSet 

  demographicFieldSet = FieldSet()
  demographicFieldSet.profilegroup = group
  age_field = Field()
  age_field.name = 'Age'
  age_field.f = models.IntegerField()
  # Bind this field to the demo field set
  age_field.fieldset = demographicFieldSet 

  # Define a 'weight_field' here similar to 'age' above.

  for obj in [geographicFieldSet, town_field, address_field, 
              demographicFieldSet, age_field, weight_field]:
    obj.save()


  # Id also add some methods to these model objects so that they 
  # know how to render themselves on the page...

  return render_to_response('page.templ', {'profile_group':group})

基本上,我想支持“逻辑分组的字段”,因为我希望能够支持多种不同类型的“字段集合”,所以我想要一个有意义的抽象。

我想定义这个模型,以便可以定义一个字段组,字段的数量和类型都是可以随意的。比如,我可能有一个名为“地理”的字段组,其中包含“州”(CharField,带选择项)、“城镇”(TextField)等字段。

这是我目前想到的:

class ProfileGroup(models.Model):
  name = models.CharField(max_length=200)

# FieldSets have many Fields
class FieldSet(models.Model):
  name = models.CharField(max_length=200)
  profilegroup = models.ForeignKey(ProfileGroup)

class Field(models.Model):
  f = models.Field()
  fieldset = models.ForeignKey(FieldSet)

不过,使用这些模型在命令行中会出现错误,最终无法让我存储任意字段。

In [1]: from splink.profile_accumulator.models import Field, FieldSet, ProfileGroup
In [2]: import django.db
In [3]: profile_group = ProfileGroup()
In [4]: profile_group.name = 'profile group name'
In [5]: profile_group.save()
In [6]: field_set = FieldSet()
In [7]: field_set.name = 'field set name'
In [8]: field_set.profilegroup = profile_group
In [9]: field_set.save()
In [10]: field = Field()
In [11]: field.name = 'field name'
In [12]: field.f = django.db.models.FileField()
In [13]: field.save()
---------------------------------------------------------------------------
ProgrammingError                          Traceback (most recent call last)

/var/www/splinkpage.com/splinkpage.pinax/splink/<ipython console> in <module>()

/usr/lib/pymodules/python2.5/django/db/models/base.pyc in save(self, force_insert, force_update)
    309             raise ValueError("Cannot force both insert and updating in "
    310                     "model saving.")
--> 311         self.save_base(force_insert=force_insert, force_update=force_update)
    312
    313     save.alters_data = True

/usr/lib/pymodules/python2.5/django/db/models/base.pyc in save_base(self, raw, cls, force_insert, force_update)
    381             if values:
    382                 # Create a new record.
--> 383                 result = manager._insert(values, return_id=update_pk)
    384             else:
    385                 # Create a new record with defaults for everything.

/usr/lib/pymodules/python2.5/django/db/models/manager.pyc in _insert(self, values, **kwargs)
    136 
    137     def _insert(self, values, **kwargs):
--> 138         return insert_query(self.model, values, **kwargs)
    139 
    140     def _update(self, values, **kwargs):

/usr/lib/pymodules/python2.5/django/db/models/query.pyc in insert_query(model, values, return_id, raw_values)
    890     part of the public API.
    891     """
    892     query = sql.InsertQuery(model, connection)
    893     query.insert_values(values, raw_values)
--> 894     return query.execute_sql(return_id)

/usr/lib/pymodules/python2.5/django/db/models/sql/subqueries.pyc in execute_sql(self, return_id)
    307
    308     def execute_sql(self, return_id=False):
--> 309         cursor = super(InsertQuery, self).execute_sql(None)
    310         if return_id:
    311             return self.connection.ops.last_insert_id(cursor,

/usr/lib/pymodules/python2.5/django/db/models/sql/query.pyc in execute_sql(self, result_type)
   1732
   1733         cursor = self.connection.cursor()
-> 1734         cursor.execute(sql, params)
   1735
   1736         if not result_type:

/usr/lib/pymodules/python2.5/django/db/backends/util.pyc in execute(self, sql, params)
     17         start = time()
     18         try:
---> 19             return self.cursor.execute(sql, params)
     20         finally:
     21             stop = time()

ProgrammingError: can't adapt   

所以我在想,这是不是完全错误的方法,或者我是否需要以不同的方式使用django的模型类,才能实现我想要的效果。

3 个回答

0

为什么不这样做呢?

class Info(models.Model):
    info_type = models.ForeignKey('InfoType', blank=False, null=False, default='')
    info_int = models.IntegerField(null=True, blank=True)
    info_img = models.ImageField(upload_to='info',null=True, blank=True)
    info_date = models.DateTimeField(null=True, blank=True)
    info_text = models.TextField(null=True, blank=True)
    info_bool = models.BooleanField(null=True, blank=True)
    info_char = models.CharField(max_length=128,null=True, blank=True)
    info_dec = models.DecimalField(max_digits=20, decimal_places=12, null=True, blank=True)
    info_float = models.FloatField(null=True, blank=True)
    parent_info = models.ForeignKey('self', blank=True, null=True)
class InfoType(models.Model):
    type = models.CharField(max_length=64, blank=False, null=False, default='')
    info_field = models.CharField(max_length=32, blank=False, null=False, default='')

所以根据我们选择的类型,我们就知道在哪个地方可以找到这个值。

0

在SQL中,没有可以拥有可变列数或可变类型列的表。而且,Django在运行时不会修改数据库的结构,也就是说,它不会执行ALTER TABLE这样的命令,至少我知道是这样的。

在Django中,数据模型必须在你运行应用程序之前完全定义好。

你可能会觉得这个文档页面对使用“多对一”关系很有帮助。

举个例子:

#profile
class ProfileGroup(models.Model):
    ...

#fieldset
class FieldSet(models.Model):
    profile = models.ForeignKey(ProfileGroup)

#field 1
class Address(models.Model)
    owner = models.ForeignKey(FieldSet,related_name='address')
    #related name above adds accessor function address_set to Profile
    #more fields like street address, zip code, etc

#field 2
class Interest(models.Model)
    owner = models.ForeignKey(FieldSet,related_name='interest')
    description = models.CharField()

#etc.

填充和访问字段:

f = FieldSet()
f.interest_set.create(description='ping pong')
f.address_set.create(street='... ', zip='... ')
f.save()

addresses = f.address_set.all()
interests = f.interest_set.all()
#there are other methods to work with sets

在这个例子中,集合模拟了一个Profile表中的可变字段。然而,在数据库中,兴趣和地址数据是存储在不同的表里的,并且通过外键与Profile表关联。

如果你想通过一个访问函数来获取所有这些数据,你可以写一个函数来封装对所有相关集合的调用。

虽然你不能动态修改模型,但你可以添加新的模型,然后执行

manage.py syncdb

这将会在数据库中创建新的表。不过,你不能通过'syncdb'来修改现有表中的字段,因为Django不支持这个。你需要手动输入SQL命令来完成这项工作。(据说web2py平台可以自动处理这个问题,但不幸的是,web2py的文档还不够完善,不过在API的质量上可能会比Django更好,值得一看)

1

我看到代码中有几个问题。首先是这个类的定义:

class Field(models.Model):
  f = models.Field()
  fieldset = models.ForeignKey(FieldSet)

这里的类 models.Field 其实不应该直接用来定义字段。它是 Django 中所有字段类型的基础类,所以它没有特定字段类型的具体细节,不能直接用。

第二个问题出现在下面这一行:

In [12]: field.f = django.db.models.FileField()

当你给你的 Field 实例的属性 f 赋值时,你应该给一个具体的值,这个值是要保存到数据库里的。比如,如果你用 CharField 来定义 Field.f,那么你应该在这里赋一个字符串。但是 models.Field 并没有具体可以赋的值。你试图赋一个显然无法保存到数据库的东西,这里是 models.FileField 的定义。

所以,Django 在“调整”你赋给字段属性的值时遇到了困难,主要有两个原因。首先,models.Field 类型没有定义可以赋的值,因为它是一个“抽象类”,也就是特定字段类型定义的基础类。其次,你不能把“字段定义”赋给一个属性,然后希望它能被保存到数据库。

我理解你的困惑。你基本上是在从数据库和 Django 的角度做一些不可能的事情。

不过,我想你的设计问题可能有解决办法。如果你能详细描述一下你想要实现的目标,或许有人能给你一些建议。

撰写回答