I have an abstract mixin class that adds a Django model field to any concrete class that inherits from it.
At class initialisation - when makemigrations is run - I'd like the inheriting class to define whether an inherited field is required or optional via the blank= True or False property.
I've tried various Meta and __new__ approaches, but can't figure out how the abstract mixin class can get the information from the inheriting class.
Here's a naive attempt:
from django.db import models
class DescriptionMixin(models.Model):
class Meta:
abstract = True
description = models.TetxField(
# how to get value here?
blank=inheriting_class.description_required
)
class OptionalDescription(DescriptionMixin, SomeOtherClass):
class Meta:
verbose_name = 'Optional description'
description_required = False
class RequiredDescription(DescriptionMixin, SomeOtherClass):
class Meta:
verbose_name = 'Required description'
description_required = True
Thanks in advance for any help offered.
You can't do this at the database level. makemigrations doesn't actually initialise your models to create the migration files.
But since you're trying to enforce this on a TextField, which cannot be enforced at the database level anyway (blank is only used when validating a model through the full_clean() method), you could just override the clean() method on the DescriptionMixin, checking the value of self.blank and raising a ValidationError appropriately.
Solved using this (it's actually Wagtail on top of Django):
def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs)
self._meta.get_field('description').blank = not getattr(self, 'description_required', False)
I have some model classes defined:
class ModelA(models.Model):
class Meta:
abstract = True
class ModelB(ModelA):
class Meta:
abstract = False
So, now I have a class object, I want to check if it is abstract, is there any way to do this?
For example, I want something like:
>>> ModelA.abstract
True
>>> ModelB.abstract
False
Oh, I found that it is easy to get the Meta class by _meta field of the class:
>>> ModelA._meta.abstract
True
technically, strictly speaking, no external method should access a method or property that begins with an underscore (_) as it is private and protected from outsider access. To solve this, in your model file, add a property:
class Foo(models.Model):
class Meta:
verbose_name = "Foolish"
#property
def verbose_name(self):
return self._meta.verbose_name
Then your view can "properly" access the Meta.verbose_name via Foo().verbose_name as opposed to Foo._meta.verbose_name
I have a abstract django model
from django_fsm import FSMField, transition
from django.db.models import Model, NullBooleanField, TextField
class ApprovalMixin(Model):
status = FSMField(default='new')
is_approved = NullBooleanField(blank=True, null=True)
class Meta:
"""Meta Attributes"""
abstract = True
Then I am inhering this class in another model:
class Request(ApprovalMixin):
notes = TextField(blank=True, null=True, help_text="ts notes")
#transition(field=status, source='new',
target='rejected')
def manager_rejection(self):
pass
I am getting the following error:
NameError: name 'status' is not defined why is this? The status should be part of Request models right since I am inheriting from the ApprovalMixin. If I am wrong, Please help me out on how to make this work.
From django-fsm docs:
The field parameter accepts both
a string attribute name or an actual
field instance.
I have two model
class ClassProfile(models.Model):
classname = models.CharField(max_length=100, blank=True)
class UserProfile(models.Model):
user = models.OneToOneField(User)
class = models.ManyToManyField('ClassProfile')
I try to get all the famulymember like this
class = Class.objects.get(pk=1)
members = class.userprofile_set.all()
this will rasie an error
'ClassProfile' object has no attribute 'userprofile_set'
what's wrong with my code?
What makes you think django uses CamelCase anywhere? By default, the reverse accessor is lowercaseclsname_set
So class.userprofile_set.all() should do it.
Aside from the fact that it is not a good idea to name a variable class, I think you have a typo in this line:
class = Class.objects.get(pk=1)
You probably meant:
class = ClassProfile.objects.get(pk=1)
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?
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)
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.
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).
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
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
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.
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
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 !
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
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"