多个字段到同一个DB列

时间:2013-03-13 12:58:57

标签: python django django-models django-database

我们正试图通过prefetch_related来加速我们的应用。它可以遵循GenericForeignKey关系,并且可以更深入地使用__但不幸的是,如果相关模型没有这样的字段,它将会失败。

以下是模型结构的一些示例

class ModelA(models.Model):
    event_object = models.ForeignKey(SomeModelA)

class ModelB(models.Model):
    event = models.ForeignKey(SomeModelB)

class ModelC(models.Model):
    content_type = models.ForeignKey(ContentType)
    object_id = models.PositiveIntegerField()
    content_object = generic.GenericForeignKey()

因此ModelC实例可以指向ModelAModelB。我可以使用这样的查询集来预取A和B模型:ModelC.objects.all().prefetch_related('content_object')不幸的是我还需要选择事件对象(SomeModelASomeModelB

如果我尝试运行

ModelC.objects.all().prefetch_related('content_object', 'content_object__event_object')

如果我只有ModelC个实例指向ModelA,那么它会有效,但在这种情况下它会失败,因为ModelB没有event_object字段并且而是event

此模型在代码中的许多位置使用,因此重命名字段不是一个好主意。所以我想知道是否有办法为字段/列创建别名。

我试图这样做:

class ModelB(models.Model):
    event = models.ForeignKey(SomeModelB)
    event_object = models.ForeignKey(SomeModelB, db_column='event_id', related_name='+')

使两个字段指向DB表中的同一列。但是这不起作用,因为它打破了save方法。 Django创建一个UPDATE SQL查询,其中一列放置两次并获得DatabaseError

有没有办法创建这样的别名?或者可能有另一种解决方案让prefetch_related不要抛出异常?

更新:在save方法中,有一个update_fields参数可用于排除此字段。然而,它是在1.5中引入的,我们使用的是1.4。所以我继续寻找答案。

更新#2 :@ shx2让我提供追溯。有2个可能的追溯。 1st - 当第一个对象缺少属性时:

Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/home/igor/workspace/projectname/eggs/Django-1.4.2-py2.7.egg/django/db/models/query.py", line 72, in __repr__
    data = list(self[:REPR_OUTPUT_SIZE + 1])
  File "/home/igor/workspace/projectname/eggs/Django-1.4.2-py2.7.egg/django/db/models/query.py", line 97, in __iter__
    len(self)
  File "/home/igor/workspace/projectname/eggs/Django-1.4.2-py2.7.egg/django/db/models/query.py", line 89, in __len__
    self._prefetch_related_objects()
  File "/home/igor/workspace/projectname/eggs/Django-1.4.2-py2.7.egg/django/db/models/query.py", line 570, in _prefetch_related_objects
    prefetch_related_objects(self._result_cache, self._prefetch_related_lookups)
  File "/home/igor/workspace/projectname/eggs/Django-1.4.2-py2.7.egg/django/db/models/query.py", line 1664, in prefetch_related_objects
    (attr, first_obj.__class__.__name__, lookup))
AttributeError: Cannot find 'event_object' on ModelB object, 'content_object__event_object' is an invalid parameter to prefetch_related()

如果prefetch_related参数对第一个对象有效,那么我得到第二个回溯:

Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/home/igor/workspace/projectname/eggs/Django-1.4.2-py2.7.egg/django/db/models/query.py", line 72, in __repr__
    data = list(self[:REPR_OUTPUT_SIZE + 1])
  File "/home/igor/workspace/projectname/eggs/Django-1.4.2-py2.7.egg/django/db/models/query.py", line 97, in __iter__
    len(self)
  File "/home/igor/workspace/projectname/eggs/Django-1.4.2-py2.7.egg/django/db/models/query.py", line 89, in __len__
    self._prefetch_related_objects()
  File "/home/igor/workspace/projectname/eggs/Django-1.4.2-py2.7.egg/django/db/models/query.py", line 570, in _prefetch_related_objects
    prefetch_related_objects(self._result_cache, self._prefetch_related_lookups)
  File "/home/igor/workspace/projectname/eggs/Django-1.4.2-py2.7.egg/django/db/models/query.py", line 1680, in prefetch_related_objects
    obj_list, additional_prl = prefetch_one_level(obj_list, prefetcher, attr)
  File "/home/igor/workspace/projectname/eggs/Django-1.4.2-py2.7.egg/django/db/models/query.py", line 1803, in prefetch_one_level
    qs = getattr(obj, attname).all()
AttributeError: 'ModelB' object has no attribute 'event_object'

1 个答案:

答案 0 :(得分:2)

这看起来像是django中的错误或疏忽。作为一种变通方法,您可以尝试定义一个执行两阶段预取的自定义管理器。

from django.db import models
from django.db.models import Q
from django.contrib.contenttypes.models import ContentType

class PrefetchWorkaroundManager(models.Manager):
    def get_queryset(self):
        q = super(PrefetchWorkaroundManager, self).get_queryset()
        content_typeA = ContentType.objects.get_for_model(ModelA)
        content_typeB = ContentType.objects.get_for_model(ModelB)
        return q.filter(content_type__pk = content_typeA.id).prefetch_related('content_object', 'content_object__event_object') | \
               q.filter(content_type__pk = content_typeB.id).prefetch_related('content_object', 'content_object__event')

class ModelC(models.Model):
    ...

    objects_prefetched = PrefetchWorkaroundManager()

每个要进行预取的来电者都应该访问ModelC.objects_prefetched而不是ModelC.objects

ModelC.objects_prefetched.filter(...)

我承认,我没有对它进行测试,因此它可能无法正常工作。但我相信这种方法是合理的。