18

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?

Zxaos
  • 7,791
  • 12
  • 47
  • 61

5 Answers5

9

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)
Tolli
  • 362
  • 5
  • 12
  • This works except that it also gets called when retrieving data, which can cause `TypeError: Bar() got both positional and keyword arguments for field 'value'`. So, it is likely best to use another method. – Akaisteph7 Aug 10 '23 at 22:07
3

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...
Community
  • 1
  • 1
bdombro
  • 1,251
  • 11
  • 17
2

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.

Sarah Messer
  • 3,592
  • 1
  • 26
  • 43
2

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.

juliomalegria
  • 24,229
  • 14
  • 73
  • 89
  • If I were simply to define default values for all elements in the base class, could we then just override the save method for all child classes, or will the defaults defined in the base class cause the instance variables to never be empty? (That is, if I define a default value in the parent class, is that default value added when saving or when initializing the object?) – Zxaos Nov 15 '11 at 18:06
  • 1
    Again, you have to see the inheritance in Django as a FK to the super-class, so if the super-class have a field with a default value, the field in the sub-class will also have the field with the default value, becasue the field is no actually from the sub-class but from the super-class. – juliomalegria Nov 15 '11 at 18:36
0

It is likely a newer feature, but in Django 4.2.4, I am able to just redefine the field and Django properly recreates it in the migration file.

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

    class Meta:
        abstract = True

class Bar(Foo):
    value=models.IntegerField(default=9)
Akaisteph7
  • 5,034
  • 2
  • 20
  • 43