Using a proxy model when accessing a foreign key

I have two related Django models. One model does costly computation in my own __init__

, which I cannot move elsewhere without unacceptable cost / risk.

It turns out that these expensive computations are unnecessary in all contexts, so I presented a proxy model that gets around them. However, they are needed more often than not, so it is impractical to turn an expensive one into a proxy.

So my code basically looks like this:

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)

      

This works well - most of my code requests are for Person

. And in those few places where the code doesn't need really expensive stuff, it queries LightweightPerson

instead and works better.

However, some of my codes start with instances PersonFact

and access related ones Person

for each PersonFact

. This code does not need really costly human computation, and the performance caused by this costly computation is unacceptable. So I would like to create an instance LightweightPerson

instead Person

in this context.

The approach I came up with was to add a second ForeignKey

one that references the proxy class and uses the same database column:

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")

      

So now, when I need to improve performance, my code can do things like this:

facts = PersonFact.objects.select_related(
             "lightweight_person").all()
for fact in facts:
  do_something_with(fact.lightweight_person)

      

And it works great! Until I try to save the new one 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

      

: - (

Is there a way to do this without a lot of dreaded refactoring of the code currently in Person.__init__

? Ideally I could either just signal "when accessing person_fact.person

right now, create an instance LightweightPerson

instead of Person

" in my codebase. Or, conversely, I would like to declare a "proxy related field" to PersonFact

that shadows the same database column, but Django internals know it only interacts with the database column once.

+3


source to share


1 answer


The best solution I've come up with so far is to do something scary on the linked model __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)

      



This seems to hide the existence of this field from the object manager for updates and inserts, so the "column specified more than once" error does not occur; and the field is still populated when I select existing data.

This seems to work, but it's pretty scary - I have no idea if it will have side effects in other parts of my code.

0


source







All Articles