【问题标题】:Using a Proxy Model when accessing foreign key访问外键时使用代理模型
【发布时间】:2013-02-11 23:16:34
【问题描述】:

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

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

所以,我的代码基本上是这样的:

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)

这很好用——我的大部分代码查询都是在 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")

所以现在当我需要提升性能时,我的代码可以执行以下操作:

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.__init__ 中的代码进行可怕的重构?理想情况下,我可以在我的调用代码中发出信号“当现在访问person_fact.person 时,请实例化LightweightPerson 而不是Person”。或者,或者,我希望能够在 PersonFact 上声明一个“代理相关字段”,它会隐藏相同的数据库列,但 Django 的内部知道只与数据库列交互一次。

【问题讨论】:

  • 我在 IRC 的#django 中进行了类似的讨论,基本上他们告诉我我在做什么被认为是一个错误。有时这是唯一可行的解​​决方案,这有点令人沮丧。

标签: django django-models


【解决方案1】:

到目前为止我想出的最好的解决方案是在相关模型的__init__上做一些可怕的事情:

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)

这显然向对象管理器隐藏了该字段的存在以进行更新和插入,因此不会发生“列指定多次”错误;当我选择现有数据时,该字段仍会被填充。

这似乎可行,但它非常可怕——我不知道它是否会对我的代码的其他部分产生副作用。

【讨论】:

  • 对于它的价值,这似乎足够安全..我的代码还没有爆炸。
猜你喜欢
  • 2015-01-16
  • 2016-06-14
  • 2014-09-05
  • 2019-04-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多