122

I'm looking to do this:

class Place(models.Model):
   name = models.CharField(max_length=20)
   rating = models.DecimalField()

class LongNamedRestaurant(Place):  # Subclassing `Place`.
   name = models.CharField(max_length=255)  # Notice, I'm overriding `Place.name` to give it a longer length.
   food_type = models.CharField(max_length=25)

This is the version I would like to use (although I'm open to any suggestion): http://docs.djangoproject.com/en/dev/topics/db/models/#id7

Is this supported in Django? If not, is there a way to achieve similar results?

Johnny 5
  • 1,521
  • 3
  • 10
  • 11

10 Answers10

83

Updated answer: as people noted in comments, the original answer wasn't properly answering the question. Indeed, only the LongNamedRestaurant model was created in database, Place was not.

A solution is to create an abstract model representing a "Place", eg. AbstractPlace, and inherit from it:

class AbstractPlace(models.Model):
    name = models.CharField(max_length=20)
    rating = models.DecimalField()

    class Meta:
        abstract = True

class Place(AbstractPlace):
    pass

class LongNamedRestaurant(AbstractPlace):
    name = models.CharField(max_length=255)
    food_type = models.CharField(max_length=25)

Please also read @Mark answer, he gives a great explanation why you can't change attributes inherited from a non-abstract class.

(Note this is only possible since Django 1.10: before Django 1.10, modifying an attribute inherited from an abstract class wasn't possible.)

Original answer

Since Django 1.10 it's possible! You just have to do what you asked for:

class Place(models.Model):
    name = models.CharField(max_length=20)
    rating = models.DecimalField()

    class Meta:
        abstract = True

class LongNamedRestaurant(Place):  # Subclassing `Place`.
    name = models.CharField(max_length=255)  # Notice, I'm overriding `Place.name` to give it a longer length.
    food_type = models.CharField(max_length=25)
qmarlats
  • 1,018
  • 1
  • 10
  • 13
  • 8
    Place needs to be abstract, no? – DylanYoung Sep 02 '16 at 16:12
  • Really answering a different question here since the OP was using multi-table inheritance. – Hayden Crocker Jan 25 '17 at 12:02
  • 4
    I don't think I answered a different question since I'm just saying that the code posted in the question is now working since Django 1.10. Note that according to the link he posted about what he wanted to use, he has forgotten to make the Place class abstract. – qmarlats Jan 28 '17 at 15:50
  • 2
    Not sure why this is the accepted answer... OP is using multi-table inheritance. This answer is only valid for abstract base classes. – MrName Mar 12 '18 at 21:39
  • 1
    abstract classes were available long before Django 1.10 – rbennell Mar 26 '18 at 17:05
  • @qmarlats didn't try it but what the difference between the 2 solutions? in both cases `LongNamedRestaurant` inherit from a model you defined as abstract True in it's Meta – NoamG Jul 17 '19 at 06:13
  • 3
    @NoamG In my original answer, `Place` was abstract, hence it was **not** created in database. But OP wanted both `Place` and `LongNamedRestaurant` to be created in database. Therefore I updated my answer to add the `AbstractPlace` model, which is the “base” (ie. abstract) model both `Place` and `LongNamedRestaurant` inherit from. Now both `Place` and `LongNamedRestaurant` are created in database, as OP asked for. – qmarlats May 09 '20 at 22:30
  • This still doesn't quite address the original question, instead assuming `Place` _can_ be rewritten. However, if `Place` comes from another library, there is no way to change any aspect of it. So, without changing the original model, is it possible to take the field from the superclass, and tell Django that one of the field's settings (in this case `max_length`. but the same goes for `blank` or `editable`, etc) should have a different value set for the subclass? – Mike 'Pomax' Kamermans Jul 23 '20 at 15:05
  • I'm curious which solution would be more preferred and typical in the Django world the abstract class based solution or the over-riding of ``clean())``` mentioned in several posts below. – radlab Mar 23 '21 at 19:13
63

No, it is not:

Field name “hiding” is not permitted

In normal Python class inheritance, it is permissible for a child class to override any attribute from the parent class. In Django, this is not permitted for attributes that are Field instances (at least, not at the moment). If a base class has a field called author, you cannot create another model field called author in any class that inherits from that base class.

David Cain
  • 16,484
  • 14
  • 65
  • 75
ptone
  • 884
  • 7
  • 5
  • 11
    See my answer for why it's impossible. People like this because it does make sense, it's just not immediately obvious. – Mark Aug 10 '13 at 07:16
  • 4
    @leo-the-manic I think `User._meta.get_field('email').required = True` could work, not sure thought. – Jens Timmerman Feb 24 '14 at 17:09
  • @leo-the-manic, @JensTimmerman, @utapyngo Setting the property value of your class won't have an effect on inherited fields. You have to act on the `_meta` of the parent class, e.g. `MyParentClass._meta.get_field('email').blank = False` (to make an inherited `email` field mandatory in the Admin) – Peterino Sep 25 '14 at 10:48
  • 1
    Ooops, sorry, @utapyngo's code above is correct, but it has to be placed *outside* of the class body, afterwards! Setting the parent class' field as I suggested may have unwanted side-effects. – Peterino Sep 25 '14 at 20:13
  • I want a field in each of the subclasses to be of a different type than a field with the same name in the abstract parent class in order to guarantee that all the subclasses all have a field with a certain name. utapyngo's code doesn't meet this need. – Daniel Jun 10 '15 at 17:02
  • The solution below works, from django 1.10 it's possible! – holms Mar 07 '18 at 10:16
  • This seems to focus on "redeclaring the field" rather than on what the question is really about: changing the `max_length` property for an existing field in the subclass. That might be covered by the same code, but this explanation is really insuficcient to say "no, this cannot be done", as there is no reason to assume the only way that could be effected was to declare a same-name field. After all, there's a lot of magic in Django. – Mike 'Pomax' Kamermans Jul 23 '20 at 15:10
30

That is not possible unless abstract, and here is why: LongNamedRestaurant is also a Place, not only as a class but also in the database. The place-table contains an entry for every pure Place and for every LongNamedRestaurant. LongNamedRestaurant just creates an extra table with the food_type and a reference to the place table.

If you do Place.objects.all(), you also get every place that is a LongNamedRestaurant, and it will be an instance of Place (without the food_type). So Place.name and LongNamedRestaurant.name share the same database column, and must therefore be of the same type.

I think this makes sense for normal models: every restaurant is a place, and should have at least everything that place has. Maybe this consistency is also why it was not possible for abstract models before 1.10, although it would not give database problems there. As @lampslave remarks, it was made possible in 1.10. I would personally recommend care: if Sub.x overrides Super.x, make sure Sub.x is a subclass of Super.x, otherwise Sub cannot be used in place of Super.

Workarounds: You can create a custom user model (AUTH_USER_MODEL) which involves quite a bit of code duplication if you only need to change the email field. Alternatively you can leave email as it is and make sure it's required in all forms. This doesn't guarantee database integrity if other applications use it, and doesn't work the other way around (if you want to make username not required).

Mark
  • 18,730
  • 7
  • 107
  • 130
  • I guess it's because of changes in 1.10: "Allowed overriding model fields inherited from abstract base classes." https://docs.djangoproject.com/en/2.0/releases/1.10/#models – lampslave Apr 24 '18 at 12:53
  • I doubt it since it wasn't out yet at the time, but that is a good thing to add, thanks! – Mark Apr 24 '18 at 17:11
19

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

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

    class Meta:
        abstract = True

class Message(BaseMessage):
    # some fields...
Message._meta.get_field('is_public').default = True
Community
  • 1
  • 1
blueyed
  • 27,102
  • 4
  • 75
  • 71
14

My solution is as simple as next monkey patching, notice how I changed max_length attribute of name field in LongNamedRestaurant model:

class Place(models.Model):
   name = models.CharField(max_length=20)

class LongNamedRestaurant(Place):
    food_type = models.CharField(max_length=25)
    Place._meta.get_field('name').max_length = 255
NoamG
  • 1,145
  • 10
  • 17
9

Pasted your code into a fresh app, added app to INSTALLED_APPS and ran syncdb:

django.core.exceptions.FieldError: Local field 'name' in class 'LongNamedRestaurant' clashes with field of similar name from base class 'Place'

Looks like Django does not support that.

Brian Luft
  • 1,153
  • 8
  • 12
7

This supercool piece of code allows you to 'override' fields in abstract parent classes.

def AbstractClassWithoutFieldsNamed(cls, *excl):
    """
    Removes unwanted fields from abstract base classes.

    Usage::
    >>> from oscar.apps.address.abstract_models import AbstractBillingAddress

    >>> from koe.meta import AbstractClassWithoutFieldsNamed as without
    >>> class BillingAddress(without(AbstractBillingAddress, 'phone_number')):
    ...     pass
    """
    if cls._meta.abstract:
        remove_fields = [f for f in cls._meta.local_fields if f.name in excl]
        for f in remove_fields:
            cls._meta.local_fields.remove(f)
        return cls
    else:
        raise Exception("Not an abstract model")

When the fields have been removed from the abstract parent class you are free to redefine them as you need.

This is not my own work. Original code from here: https://gist.github.com/specialunderwear/9d917ddacf3547b646ba

Devin
  • 1,262
  • 15
  • 20
6

Maybe you could deal with contribute_to_class :

class LongNamedRestaurant(Place):

    food_type = models.CharField(max_length=25)

    def __init__(self, *args, **kwargs):
        super(LongNamedRestaurant, self).__init__(*args, **kwargs)
        name = models.CharField(max_length=255)
        name.contribute_to_class(self, 'name')

Syncdb works fine. I dont tried this example, in my case I just override a constraint parameter so ... wait & see !

JF Simon
  • 1,235
  • 9
  • 13
  • 1
    also the arguments to contribute_to_class seem strange (also wrong way around?) Seems like you typed this from memory. Could you please supply the actual code you tested? If you did get this to work, I would love to know exactly how you did it. – Michael Bylstra Jan 24 '13 at 07:34
  • This isn't working for me. Would be interested in a working example as well. – garromark Nov 25 '13 at 02:13
  • please see http://blog.jupo.org/2011/11/10/django-model-field-injection/ it should be contribute_to_class(, ) – goh Dec 15 '13 at 13:26
  • 3
    `Place._meta.get_field('name').max_length = 255` in the class body should do the trick, without overriding `__init__()`. Would be more concise, too. – Peterino Sep 25 '14 at 10:59
4

I know it's an old question, but i had a similar problem and found a workaround:

I had the following classes:

class CommonInfo(models.Model):
    image = models.ImageField(blank=True, null=True, default="")

    class Meta:
        abstract = True

class Year(CommonInfo):
    year = models.IntegerField() 

But I wanted Year's inherited image-field to be required while keeping the image field of the superclass nullable. In the end I used ModelForms to enforce the image at the validation stage:

class YearForm(ModelForm):
    class Meta:
        model = Year

    def clean(self):
        if not self.cleaned_data['image'] or len(self.cleaned_data['image'])==0:
            raise ValidationError("Please provide an image.")

        return self.cleaned_data

admin.py:

class YearAdmin(admin.ModelAdmin):
    form = YearForm

It seems this is only applicable for some situations (certainly where you need to enforce stricter rules on the subclass field).

Alternatively you can use the clean_<fieldname>() method instead of clean(), e.g. if a field town would be required to be filled in:

def clean_town(self):
    town = self.cleaned_data["town"]
    if not town or len(town) == 0:
        raise forms.ValidationError("Please enter a town")
    return town
SaeX
  • 17,240
  • 16
  • 77
  • 97
pholz
  • 684
  • 8
  • 19
1

You can not override Model fields, but its easily achieved by overriding/specifying clean() method. I had the issue with email field and wanted to make it unique on Model level and did it like this:

def clean(self):
    """
    Make sure that email field is unique
    """
    if MyUser.objects.filter(email=self.email):
        raise ValidationError({'email': _('This email is already in use')})

The error message is then captured by Form field with name "email"

Phoenix49
  • 11
  • 2
  • The question is about extending max_length of a char field. If this is enforced by the database, then this "solution" doesn't help. A workaround would be to specify the longer max_length in the base model and use the clean() method to enforce the shorter length there. – DylanYoung Sep 02 '16 at 16:15