0

Can I read polymorphic models from a single database table, with their behaviour depending on a (boolean) field of the model?

In one of my models the behaviour is slightly different if the instance is 'forward' vs. 'backward' or 'left' vs. 'right'. That leads to a lot of if-clauses and code duplication. So I want to have a Forward- and a Backward-variant of the model that encapsulate the different behaviours.

But how can I make the models manager return the instances of the right classes? Do I have to overwrite __init__ of the model?

Maybe it's easier to explain with an example. What I'm doing:

class Foo(models.Model):
    forward = models.BooleanField()
    other_fields = ...

    def do_foobar(bar):
        if self.forward:
            gap = bar.end_pos - bar.current_pos
            self.do_forward_move(max = gap)
            if self.pos==bar.end_pos:
                and so on ...
        else:
            gap = bar.current_pos - bar.start_pos
            self.do_backward_move(max = gap)
            if self.pos==bar.start_pos:
                and so on ...

What I want to do:

class Foo(models.Model):
    forward = models.BooleanField()
    other_fields = ...

    def __init__(*args, **kwargs):
        """ return ForwardFoo or BackwardFoo 
        depending on the value of 'forward'"""
        How?

    def do_foobar(bar):
        gap = self.calculate_gap(bar)
        self.do_move(max = gap)
        if self.end_point_reached():
            and so on ...

class ForwardFoo(Foo):
    def calculate_gap(bar):
        return bar.end_pos - bar.current_pos
and so on ...

for f in Foo.objects.all():
    f.do_foobar(bar)

Or is there a totally different way to avoid this kind of code duplication?

jammon
  • 3,404
  • 3
  • 20
  • 29

1 Answers1

1

Proxy models:

class Foo(models.Model):
    # all model attributes here

class ForwardFooManager(models.Manager):
    def get_query_set(self, *args, **kwargs):
        qs = super(ForwardFooManager, self).get_query_set(*args, **kwargs)
        return qs.filter(forward=True)

class ForwardFoo(Foo):
    class Meta:
        proxy = True

    objects = ForwardsFooManager()

    # methods for forward model

class BackwardFooManager(models.Manager):
    def get_query_set(self, *args, **kwargs):
        qs = super(BackwardFooManager, self).get_query_set(*args, **kwargs)
        return qs.filter(forward=False)

class BackwardFoo(Foo):
    class Meta:
        proxy = True

    objects = BackwardFooManager()

    # methods for backward model

The above creates two proxy models: one for forward, one for backward. Proxy models do not have their own database table; they use the same database table as the model they inherit from. (This also means you cannot add any additional fields to the proxy model, only methods.)

There's also a custom manager for to force each one to only return the subset of items that belong to each. Just add whatever specific methods you need and you're done.

Chris Pratt
  • 232,153
  • 36
  • 385
  • 444
  • Great, thank you! I wasn't aware of proxy models up to now. But they still don't do everything that I wanted. I still have to use `ForwardFooManager` and `BackwardFooManager`. I cannot simply call `Foo.objects.all()` and get `ForwardFoo` and `BackwardFoo` instances depending on the value of `forward`. So I'm leaving this question open, maybe somebody comes up with a different idea. – jammon May 31 '11 at 15:49
  • That's not possible based on how inheritance works in Python, in general, and Django, in particular. `Foo` will only ever return `Foo` objects. See https://docs.djangoproject.com/en/dev/topics/db/models/#s-querysets-still-return-the-model-that-was-requested – Chris Pratt May 31 '11 at 16:06
  • I'm afraid you are right. I will rephrase the problem in a different, more general question. Maybe somebody has a fundamentally different idea. Thank you anyway for dealing with my somewhat longish question. – jammon Jun 01 '11 at 05:23
  • I marked your answer as 'accepted' since it brought me the idea of proxy models. Researching further I found [django_polymorphic](https://github.com/bconstantin/django_polymorphic), which looks very promising. Maybe I can combine this clever solution for real polymorphic querysets with the use of proxy models, obviating the need for multiple database queries. – jammon Jun 01 '11 at 13:16