Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Class based default value for field in Django model inheritance hierarchy

How can one accomplish class-based default value in following scheme? I mean, I would like to inherited classes set default value for "number" differently:

class OrderDocumentBase(PdfPrintable):
    number = models.PositiveIntegerField(default=self.create_number())

    @classmethod
    def create_number(cls):
        raise NotImplementedError

class Invoice(OrderDocumentBase):
    @classmethod
    def create_number(cls):
        return 1

class CreditAdvice(OrderDocumentBase):
    @classmethod
    def create_number(cls):
        return 2

I have looked at this stackoverflow question, but it doesn't address the same problem. The only thing I thought would work was overloading OrderDocumentBase's __init__ method like this:

def __init__(self, *args, **kwargs):
    """
    Overload __init__ to enable dynamic set of default to number
    """
    super(OrderDocumentBase, self).__init__(*args, **kwargs)
    number_field = filter(lambda x: x.name == 'number', self._meta.fields)[0]
    number = self.__class__.create_number()
    number_field.default = number

This works, but only partially and behaves quite wierdly. In admin interface, I can see the default being set only after second or latter page refresh. On first try, None is being set :(

Second possibility is redefinition of number field in each class, but that doesn't seem too much pretty. Is there any other way?

Can someone help?

like image 993
xaralis Avatar asked Sep 24 '10 12:09

xaralis


2 Answers

It does feel nicer to do this via default=, but anything you use there doesn't have a way to get at your class or specific model. To have it show up properly in places like the admin, you could set it in init() instead of save().

class OrderDocumentBase(PdfPrintable):
    number = models.PositiveIntegerField()

    def __init__(self, *args, **kwargs):
        super(OrderDocumentBase, self).__init__(*args, **kwargs)
        if not self.pk and not self.number:
            self.number = self.DEFAULT_NUMBER

class Invoice(OrderDocumentBase):
    DEFAULT_NUMBER = 2

class CreditAdvice(OrderDocumentBase):
    DEFAULT_NUMBER = 3
like image 58
tgecho Avatar answered Sep 30 '22 08:09

tgecho


There are a couple of problems here. First, the self.method is not going to work. There is no self in the context of the body of the class, which is where you are declaring the PositiveIntegerField.

Second, passing a callable will not work as the callable gets bound at compile time and does not change at runtime. So if you define say,

class OrderDocumentBase(PdfPrintable):
    create_number = lambda: return 0
    number = models.PositiveIntegerField(default=create_number)

class Invoice(OrderDocumentBase):
    create_number = lambda: return 1

All Invoice instances will still get 0 as default value.

One way I can think of to tackle this is to override the save() method. You can check if the number has not been supplied and set it to a default before saving.

class OrderDocumentBase(PdfPrintable):
    number = models.PositiveIntegerField()

    def save(self, *args, **kwargs):
        if not self.number:
            self.number = self.DEFAULT
        super(OrderDocumentBase, self).save(*args, **kwargs)

class Invoice(OrderDocumentBase):
    DEFAULT = 2

class CreditAdvice(OrderDocumentBase):
    DEFAULT = 3

I tested the above with a small change (made OrderDocumentBase abstract as I did not have PdfPrintable) and it worked as expected.

like image 21
Manoj Govindan Avatar answered Sep 30 '22 07:09

Manoj Govindan