Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you change field arguments in Django model subclasses?

Let's say I have some Django model that is an abstract base class:

class Foo(models.Model):
    value=models.IntegerField()

    class Meta:
        abstract = True

and it has two derived classes, where I'd like the default value of the field to be different for each child classes. I can't simply override the field

class Bar(Foo):
    value=models.IntegerField(default=9)

because Django won't let you override fields in subclasses. I've seen posts about trying to changes available choices, but in this case I care mostly about changing the default value. Any advice?

like image 991
Zxaos Avatar asked Oct 25 '11 04:10

Zxaos


4 Answers

The problem with redefining the save method as suggested in the other answer is that your value will not be set until you save your model object. Another option that doesn't have this problem is to redefine the __init__ in the child class (Bar class):

def __init__(self, *args, **kwargs):
    if 'value' not in kwargs:
        kwargs['value'] = 9
    super(Bar, self).__init__(*args, **kwargs)
like image 142
Tolli Avatar answered Oct 07 '22 17:10

Tolli


I think that what you're trying to do is not possible, at least not in Django. You have to see inheritance in Django as a ForeignKey to the super-class (that's pretty much it), and you can't change the default value of an attribute in a FK relation.

So, the best thing you could do is to redefine the save() method. It would be something like:

def save(self, *args, **kwargs):
    if not self.value:
        self.value = 9
    super(Bar, self).save(*args, **kwargs)

And, in Foo (super-class):

    value = models.IntegerField(blank=True)

to avoid NOT NULL problems with the database.

like image 36
juliomalegria Avatar answered Oct 07 '22 17:10

juliomalegria


See https://stackoverflow.com/a/6379556/15690:

You can actually do this as follows:

class BaseMessage(models.Model):
    is_public = models.BooleanField(default=False)
    # some more fields...

    class Meta:
        abstract = True

BaseMessage._meta.get_field('is_public').default = True

class Message(BaseMessage):
    # some fields...
like image 31
bdombro Avatar answered Oct 07 '22 19:10

bdombro


This defines a metaclass (and a base class) that provides all subclasses a field of a given type, with defaults that can be set in the subclass, but don't have to be:

from django.db import models
class DefaultTextFieldMetaclass(models.base.ModelBase):
    DEFAULT_TEXT = 'this is the metaclass default'

    def __new__(mcs, name, parents, _dict):
        if not (('Meta' in _dict) and hasattr(_dict['Meta'], 'abstract') and _dict['Meta'].abstract):
            # class is concrete
            if 'DEFAULT_TEXT' in _dict:
                default = _dict['DEFAULT_TEXT']
            else:       # Use inherited DEFAULT_TEXT if available
                default_set = False
                for cls in parents:
                    if hasattr(cls, 'DEFAULT_TEXT'):
                        default = cls.DEFAULT_TEXT
                        default_set = True
                if not default_set:
                    default = mcs.DEFAULT_TEXT
            _dict['modeltext'] = models.TextField(default=default)
        return super(DefaultTextFieldMetaclass, mcs).__new__(mcs, name, parents, _dict)


class BaseTextFieldClass(models.Model):
    class Meta(object):
        abstract = True
    __metaclass__ = DefaultTextFieldMetaclass
    DEFAULT_TEXT = 'superclass default'


class TextA(BaseTextFieldClass):
    DEFAULT_TEXT = 'A default for TextA'

class TextB(BaseTextFieldClass):
    DEFAULT_TEXT = 'The default for TextB'
    number = models.IntegerField(default=43)

class TextC(BaseTextFieldClass):
    othertext = models.TextField(default='some other field')

Unless you have a bunch of subclasses and/or multiple methods/attributes and assumptions that tie into the BaseTextFieldClass, this is probably overkill... but it should do what OP requested.

like image 43
Sarah Messer Avatar answered Oct 07 '22 18:10

Sarah Messer