Having two models, parent and child, I want to set the value of one of the attributes the child inherits.
For example, in the following code the color attribute would be set when creating a RedCat object.
# Parent class
class Cat(models.Model):
name = models.CharField(max_length=10)
color = models.CharField(max_length=10)
class Meta:
abstract = True
# Child class
class RedCat(Cat):
color = 'red' # <-- Doesn't work!
I'm considering either overriding the parent attribute or having the attribute only on the children, but I wonder, is there a right/better way to set a default value in a Django model for an inherited attribute?
You can link using ForeignKey and then override the parent field value in child.
models.py
# Parent class
class Cat(models.Model):
name = models.CharField(max_length=10)
color = models.CharField(max_length=10)
# Child class
class RedCat(Cat):
color_type = models.ForeignKey(Cat, on_delete=models.CASCADE)
def save(self, *args, **kwargs):
self.color_type.color = "Red"
self.color_type.save()
super(RedCat, self).save(*args, **kwargs)
First, the description of being a child of a parent in django, I think it is not by subclassing the parent class.
Instead, Considering if RedCat is a child of Cat, you should create a new model that has a ForeignKey field that aims to the Cat module.
I think what you mean is to set default values for a field, which can be done by using the default attr on the field.
Models.py
class Cat(models.Model):
...
class RedCat(models.Model):
cat = models.ForeignKey(Cat, on_delete=models.CASCADE)
color = models.CharField(
max_length=10,
default='red',
)
When I first made the question I was confused because I couldn't see the value for the attribute being set in the subclass in the /admin interface, but the code actually works.
The parent class is an abstract class that contains the definition of the attribute.
The child class inherits this attribute correctly and sets its value.
When navigating the admin panel for this model, the value of the attribute will not show, as it is established by default and will never change, but this attribute is still correctly set and accessible from everywhere else.
See Django docs on abstract model classes
Related
I am using the package django-polymorphic-tree inside of a Django application.
The PolymorphicMPTTModel abstract model from said package has the following Meta:
class PolymorphicMPTTModel(MPTTModel, PolymorphicModel, metaclass=PolymorphicMPTTModelBase):
"""
The base class for all nodes; a mapping of an URL to content (e.g. a HTML page, text file, blog, etc..)
"""
# ... fields
class Meta:
abstract = True
ordering = (
"tree_id",
"lft",
)
base_manager_name = "objects"
Here's a model I wrote that inherits from it:
class MyNodeModel(PolymorphicMPTTModel):
parent = PolymorphicTreeForeignKey(
"self",
blank=True,
null=True,
related_name="children",
on_delete=models.CASCADE,
)
# ... fields
class Meta:
ordering = (
"tree_id",
"-lft",
)
As you can see, I'm trying to overwrite the parent's Meta.ordering attribute.
However, if I do this inside of an instance of MyNodeModel:
print(self.Meta.ordering)
it prints:
('tree_id', 'lft')
which is the parent's value for that field. It appears as though the child class is failing to override that property. Why does this happen?
In Django, I have an abstract class:
class AbstractResult(models.Model):
specimen = models.ForeignKey(Specimen, on_delete=models.CASCADE, related_name='%(class)s')
...
class Meta:
abstract = True
This gives me some control over the related name of this field for each concrete class, but not full control. I'd like to be able to set this name in each concrete class.
I thought maybe I could use a callable:
class AbstractResult(models.Model):
def get_related_name(self):
raise NotImplementedError("Override this method in your concrete model classes!")
specimen = models.ForeignKey(Specimen, on_delete=models.CASCADE, related_name=get_related_name)
But that didn't work, as the related_name arg doesn't take a callable.
I also thought maybe I could do:
class AbstractResult(models.Model):
related_name = 'OVERRIDE THIS ATTRIBUTE!'
specimen = models.ForeignKey(Specimen, on_delete=models.CASCADE, related_name=related_name)
and then try to use it in the concrete class:
class TestA_Result(AbstractResult):
related_name = "test_a_results"
but that didn't work either.
Why didn't that work? Is there any simple way to make it work?
Update:
It looks like the "problem" is that the related_name=related_name in my last example gets evaluated when the AbstractResult class is being constructed - not when the TestA_Result class is being constructed. This isn't a Django thing - this is just how Python works:
class AbstractClass:
field_one = 'field one set in abstract class'
field_two = field_one
class ConcreteClass(AbstractClass):
field_one = 'set in concrete class'
inst = ConcreteClass()
inst.field_one # 'set in concrete class'
inst.field_two # 'field one set in abstract class'
Is there a way around this that would allow me to set the related_name in Django's concrete class?
I have many classes with same field named state, which is CharField with choices. And all of this classes inherit from one abstract class. I decided to put choices variable to this abstract class:
class UsefullAbstractClass(models.Model):
NEW = 'new'
ACTUAL = 'actual'
DELETE = 'delete'
STATE_CHOICES = (
(NEW, 'new'),
(ACTUAL, 'actual'),
(DELETE, 'delete'))
class Meta:
abstract = True
Here my child class:
ChildClass(UsefullAbstractClass):
state = models.CharField(
max_length=100,
choices=STATE_CHOICES)
And I got
name 'STATE_CHOICES' is not defined
Then I use manage.py shell for check this variable exists. And all child class instanses has this variable
>>> c = ChildClass.objects.all()
>>> c[0].STATE_CHOICES
(('new', 'new'), ('actual', 'actual'), ('delete', 'delete'))
How can I solve this problem?
ChildClass(UsefullAbstractClass):
state = models.CharField(
max_length=100,
choices=UsefullAbstractClass.STATE_CHOICES)
Variables defined in the class definition are class variables; they are shared by all instances. To create instance variables, they can be set in a method with self.name = value. Both class and instance variables are accessible through the notation self.name.
You can access it in any methods of ChildClass like self.STATE_CHOICES.
That's why it worked for c[0].STATE_CHOICES
So it is only accessible with the instances (self.variable_name). If you want to access it outside the methods then you should do class.variable_name.
For more details please check the link http://docs.python.org/reference/compound_stmts.html#class-definitions
I'm developing Django application, and I have following error
'Sheep' object has no attribute _state
My models are constructed like this
class Animal(models.Model):
aul = models.ForeignKey(Aul)
weight = models.IntegerField()
quality = models.IntegerField()
age = models.IntegerField()
def __init__(self,aul):
self.aul=aul
self.weight=3
self.quality=10
self.age=0
def __str__(self):
return self.age
class Sheep(Animal):
wool = models.IntegerField()
def __init__(self,aul):
Animal.__init__(self,aul)
What I must do?
firstly, you must be very careful overriding __init__ to have non-optional arguments. remember it will be called every time you get an object from a queryset!
this is the correct code you want:
class Animal(models.Model):
#class Meta: #uncomment this for an abstract class
# abstract = True
aul = models.ForeignKey(Aul)
weight = models.IntegerField(default=3)
quality = models.IntegerField(default=10)
age = models.IntegerField(default=0)
def __unicode__(self):
return self.age
class Sheep(Animal):
wool = models.IntegerField()
I highly suggest setting the abstract option on Animal if you will only ever be using subclasses of this object. This ensures a table is not created for animal and only for Sheep (etc..). if abstract is not set, then an Animal table will be created and the Sheep class will be given it's own table and an automatic 'animal' field which will be a foreign key to the Animal model.
Django docs recommend against you to use __init__ method in models:
You may be tempted to customize the model by overriding the __init__ method. If you do so, however, take care not to change the calling signature as any change may prevent the model instance from being saved. Rather than overriding __init__, try using one of these approaches:
Add a classmethod on the model class
Add a method on a custom manager (usually preferred)
Let's say that I have a model Foo that inherits from SuperFoo:
class SuperFoo(models.Model):
name = models.CharField('name of SuperFoo instance', max_length=50)
...
class Foo(SuperFoo):
... # do something that changes verbose_name of name field of SuperFoo
In class Foo, I'd like to override the verbose_name of the name field of SuperFoo. Can I? If not, is the best option setting a label inside the model form definition to get it displayed in a template?
A simple hack I have used is:
class SuperFoo(models.Model):
name = models.CharField('name of SuperFoo instance', max_length=50)
...
class Meta:
abstract = True
class Foo(SuperFoo):
... # do something that changes verbose_name of name field of SuperFoo
Foo._meta.get_field('name').verbose_name = 'Whatever'
Bearing in mind the caveat that modifying Foo._meta.fields will affect the superclass too - and therefore is only really useful if the superclass is abstract, I've wrapped the answer #Gerry gave up as a reusable class decorator:
def modify_fields(**kwargs):
def wrap(cls):
for field, prop_dict in kwargs.items():
for prop, val in prop_dict.items():
setattr(cls._meta.get_field(field), prop, val)
return cls
return wrap
Use it like this:
#modify_fields(timestamp={
'verbose_name': 'Available From',
'help_text': 'Earliest date you can book this'})
class Purchase(BaseOrderItem):
pass
The example above changes the verbose_name and help_text for the inherited field 'timestamp'. You can pass in as many keyword args as there are fields you want to modify.
Your best bet would be setting/changing the label in the form itself. Referring to the name field of the Foo model (eg. by looking it up in Foo._meta.fields) will actually give you a reference to the name field of SuperFoo, so changing its verbose_name will change it in both models.
Also, adding a name field to the Foo class won't work either, because...
Overriding fields in a parent model
leads to difficulties in areas such as
initialising new instances (specifying
which field is being intialised in
Model.__init__) and serialization.
These are features which normal Python
class inheritance doesn't have to deal
with in quite the same way, so the
difference between Django model
inheritance and Python class
inheritance isn't merely arbitrary.
Have a look at how Django-CMS does this, they override the db_table field in the models inheriting from CMSPlugin. The basics (which I also use for my own stuff) boil down to:
class SuperFooMetaClass(ModelBase):
def __new__(cls, name, bases, attrs):
new_class = super(SuperFooMetaClass, cls).__new__(cls, name, bases, attrs)
new_class._meta.verbose_name = "...." # perhaps only if not customized
return new_class
class SuperFoo(models.Model):
__metaclass__ = SuperFooMetaClass
....
You can add some checks, e.g. only update for subclasses (not the direct type), or only update if the value is not customized.
I have different child classes deriving from the same base class. And I only care for the form view in the Django Admin interface.
The only solution that worked for me was to customize the Django Admin interface and set the verbose_name (or help_text) there:
class FooAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
form = super().get_form(request, obj, **kwargs)
form.base_fields['name'].verbose_name = "my verbose name"
return form
admin.site.register(models.Foo, FooAdmin)