Django 访问外键时使用代理模型

Django 访问外键时使用代理模型,django,django-models,Django,Django Models,我有两个相关的Django模型。其中一个模型在其\uuuu init\uuuu中进行了昂贵的计算,如果没有不可接受的成本/风险,我无法将其转移到其他地方 事实证明,并非所有情况下都需要这些昂贵的计算,因此我引入了一个绕过它们的代理模型。然而,它们往往是需要的,因此将昂贵的一个作为代理是不切实际的 因此,我的代码基本上如下所示: class Person(models.Model): def __init__(self, *args, **kw): models.Model.__ini

我有两个相关的Django模型。其中一个模型在其
\uuuu init\uuuu
中进行了昂贵的计算,如果没有不可接受的成本/风险,我无法将其转移到其他地方

事实证明,并非所有情况下都需要这些昂贵的计算,因此我引入了一个绕过它们的代理模型。然而,它们往往是需要的,因此将昂贵的一个作为代理是不切实际的

因此,我的代码基本上如下所示:

class Person(models.Model):
  def __init__(self, *args, **kw):
    models.Model.__init__(self, *args, **kw)
    do_some_really_expensive_things()

class LightweightPerson(Person):
  class Meta:
    proxy = True

  def __init__(self, *args, **kw):
    models.Model.__init__(self, *args, **kw)

class PersonFact(models.Model):
  fact = models.TextField()
  person = models.ForeignKey(Person)
facts = PersonFact.objects.select_related(
             "lightweight_person").all()
for fact in facts:
  do_something_with(fact.lightweight_person)
这很好——我的大多数代码查询都是在
Person
上进行的。在代码不需要真正昂贵的东西的少数地方,它查询的是
LightweightPerson
,性能更好

但是,我的一些代码从
PersonFact
实例开始,并为每个
PersonFact
访问相关的
person
。这段代码不需要真正昂贵的人员计算,这些昂贵的计算对性能的影响是不可接受的。因此,我希望能够在此上下文中实例化一个
LightweightPerson
,而不是
Person

我提出的方法是添加第二个引用代理类的
ForeignKey
,并使用相同的数据库列:

class PersonFact(models.Model):
  fact = models.TextField()
  person = models.ForeignKey(Person, db_column="person_id")
  lightweight_person = models.ForeignKey(
     LightweightPerson, db_column="person_id", 
     related_name="lightweight_personfact_set")
因此,现在当我需要性能提升时,我的代码可以执行以下操作:

class Person(models.Model):
  def __init__(self, *args, **kw):
    models.Model.__init__(self, *args, **kw)
    do_some_really_expensive_things()

class LightweightPerson(Person):
  class Meta:
    proxy = True

  def __init__(self, *args, **kw):
    models.Model.__init__(self, *args, **kw)

class PersonFact(models.Model):
  fact = models.TextField()
  person = models.ForeignKey(Person)
facts = PersonFact.objects.select_related(
             "lightweight_person").all()
for fact in facts:
  do_something_with(fact.lightweight_person)
这真是太棒了!直到我尝试保存一个新的
PersonFact

>>> fact = PersonFact(fact="I like cheese", person=some_guy_i_know)
>>> fact.save()
Traceback (most recent call last):
...
DatabaseError: column "person_id" specified more than once
:-(


有没有什么方法可以做到这一点,而不必对
Person中当前存在的代码进行可怕的重构呢?\uuu init\uuuu
?理想情况下,我可以发出信号“当访问
Person时”。Person
现在,请实例化一个
轻量级的Person
,而不是
Person
”或者,我希望能够声明一个“代理相关字段”在
PersonFact
上,隐藏相同的数据库列,但Django的内部知道只与数据库列交互一次。

到目前为止,我提出的最佳解决方案是在相关模型的
\uuuu init\uuuuuu>上做一些可怕的事情:

class PersonFact(models.Model):
  fact = models.TextField()
  person = models.ForeignKey(Person, db_column="person_id")
  lightweight_person = models.ForeignKey(
     LightweightPerson, db_column="person_id", 
     related_name="lightweight_personfact_set")

def __init__(self, *args, **kw):
    models.Model.__init__(self, *args, **kw)
    index = None
    for i, field in enumerate(self._meta.local_fields):
        if field.name == 'lightweight_person':
            index = i
            break
    if index is not None:
        self._meta.local_fields.pop(index)
这显然对更新和插入的对象管理器隐藏了该字段的存在,因此不会出现“多次指定的列”错误;并且在选择现有数据时,该字段仍然会填充


这似乎有效,但相当可怕——我不知道它是否会在我的代码的其他部分产生副作用。

我在IRC的#django中进行了类似的讨论,基本上他们告诉我我所做的被认为是一个bug。有时这是唯一可行的解决方案,这有点令人沮丧。值得一提的是,这似乎是安全的我的代码还没有爆炸。