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)
64👍
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 areField
instances (at least,
not at the moment). If a base class has a field calledauthor
, you
cannot create another model field calledauthor
in any class that
inherits from that base class.
- [Django]-Change a Django form field to a hidden field
- [Django]-How to set environment variables in PyCharm?
- [Django]-Set Django IntegerField by choices=… name
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).
- [Django]-How to use MySQLdb with Python and Django in OSX 10.6?
- [Django]-How to reset db in Django? I get a command 'reset' not found error
- [Django]-<Django object > is not JSON serializable
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
- [Django]-Python + Django page redirect
- [Django]-Django.db.migrations.exceptions.InconsistentMigrationHistory
- [Django]-Can't install via pip because of egg_info error
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
- [Django]-Python Django Gmail SMTP setup
- [Django]-Django unit tests without a db
- [Django]-How to combine django "prefetch_related" and "values" methods?
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.
- [Django]-405 "Method POST is not allowed" in Django REST framework
- [Django]-How to unit test file upload in django
- [Django]-Django rest framework lookup_field through OneToOneField
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
- [Django]-How do I make many-to-many field optional in Django?
- [Django]-How to change a django QueryDict to Python Dict?
- [Django]-How to send email via Django?
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 !
- [Django]-Using the reserved word "class" as field name in Django and Django REST Framework
- [Django]-Http POST drops port in URL
- [Django]-CSS styling in Django forms
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
- [Django]-How to view corresponding SQL query of the Django ORM's queryset?
- [Django]-How to make an auto-filled and auto-incrementing field in django admin
- [Django]-ImproperlyConfigured: You must either define the environment variable DJANGO_SETTINGS_MODULE or call settings.configure() before accessing settings
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”
- [Django]-How do I create a slug in Django?
- [Django]-Django Server Error: port is already in use
- [Django]-PHP Frameworks (CodeIgniter, Yii, CakePHP) vs. Django