views:

109

answers:

2

Hi there, I have a design question concerning Django. I am not quite sure how to apply the principle of loose coupling of apps to this specific problem:

I have an order-app that manages orders (in an online shop). Within this order-app I have two classes:

class Order(models.Model):
    # some fields
    def order_payment_complete(self):
        # do something when payment complete, ie. ship products
        pass

class Payment(models.Model):
    order = models.ForeignKey(Order)
    # some more fields     
    def save(self):
        # determine if payment has been updated to status 'PAID'
        if is_paid:
            self.order.order_payment_complete()
        super(Payment, self).save()

Now the actual problem: I have a more specialized app that kind of extends this order. So it adds some more fields to it, etc. Example:

class SpecializedOrder(Order):
    # some more fields
    def order_payment_complete(self):
        # here we do some specific stuff
        pass

Now of course the intended behaviour would be as follows: I create a SpecializedOrder, the payment for this order is placed and the order_payment_complete() method of the SpecializedOrder is called. However, since Payment is linked to Order, not SpecializedOrder, the order_payment_complete() method of the base Order is called.

I don't really know the best way to implement such a design. Maybe I am completely off - but I wanted to build this order-app so that I can use it for multiple purposes and wanted to keep it as generic as possible.

It would be great if someone could help me out here! Thanks, Nino

+1  A: 

The thing you want is called dynamic polymorphism and Django is really bad at it. (I can feel your pain)

The simplest solution I've seen so far is something like this:

1) Create a base class for all your models that need this kind of feature. Something like this: (code blatantly stolen from here)

class RelatedBase(models.Model):
    childclassname = models.CharField(max_length=20, editable=False)

    def save(self, *args, **kwargs):
        if not self.childclassname:
            self.childclassname = self.__class__.__name__.lower()
        super(RelatedBase, self).save(*args, **kwargs) 

    @property
    def rel_obj(self):
        return getattr(self, self.childclassname)

    class Meta:
        abstract = True

2) Inherit your order from this class.

3) Whenever you need an Order object, use its rel_obj attribute, which will return you the underlying object.

This solution is far from being elegant, but I've yet to find a better one...

maksymko
I guess your solution is just a workaround for the above contenttype solution by jarret, right?
Nino
@Nino It is more general in the sense that it will work for any class hierarchy, whenever it is used, i.e. you can query parent class table and access child class objects. But for foreign keys, yes, this is just a workaround.
maksymko
+3  A: 

I think what you're looking for is the GenericForeignKey from the ContentTypes framework, which is shipped with Django in the contrib package. It handles recording the type and id of the subclass instance, and provides a seamless way to access the subclasses as a foreign key property on the model.

In your case, it would look something like this:

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

class Payment(models.Model):

    order_content_type = models.ForeignKey(ContentType)
    order_object_id = models.PositiveIntegerField()
    order = generic.GenericForeignKey('order_content_type', 'order_object_id')

You don't need to do anything special in order to use this foreign key... the generics handle setting and saving the order_content_type and order_object_id fields transparently:

s = SpecializedOrder()
p = Payment()
p.order = s
p.save()

Now, when your Payment save method runs:

if is_paid:
    self.order.order_payment_complete()  # self.order will be SpecializedOrder
Jarret Hardie
perfect, that was exactly what I needed. :)
Nino