I have the following code:
# apps/models.py :
class Parent(models.Model):
name = models.CharField(max_length=80)
def __unicode__(self):
clist = ", ".join([x.__unicode__() for x in self.children.all()])
return self.name + clist
class Child(models.Model):
unit = models.ForeignKey(Parent, related_name='children')
desc = models.CharField(max_length=80)
def __unicode__(self):
return self.desc
class ChildA(Child):
text = models.TextField()
def __unicode__(self):
return self.text[:40]
I have several items of type ChildA. Why when I ask for a __unicode__() of the relevant Parent, the string I get in return is the one generated by the __unicode__() method of Child and not the __unicode__() method of ChildA ?
Updates:
This is standard behavior. Another possible solutions in addition to answers below is an inheritance cast
This is standard behavior for inheritance. Parent is related directly with Child, not ChildA. When you call some_parent.children.all(), you get back a queryset of Child instances, so obviously, when you call unicode on one of those, it's calling Child.__unicode__.
UPDATE
There's not really a good way to get from a parent to the child. If you're using MTI (Multiple Table Inheritance), you can take advantage of the way Django implements it, namely as a OneToOneField with the parent. Because of that, there's also a reverse relationship on the parent to the child, but you must specifically test for it. For example:
class Child(models.Model):
...
def get_child(self):
if hasattr(self, 'childa'):
return self.childa
if hasattr(self, 'childb'):
return self.childb
...
It's not ideal by any means, and you need to be careful to always update this method whenever you subclass Child, which pretty much totally violates abstraction in OOP.
You probably want an abstract base class.
Read up on the difference here...
https://docs.djangoproject.com/en/dev/topics/db/models/#abstract-base-classes
Why when I ask for a unicode() of the relevant parent the string I
get in return is the one generated by the unicode() method of
Child ?
It follows that you are not calling the method on a Parent instance. That is why you are seeing this behaviour.
You can access the parent class's method by using the super() keyword:
a = ChildA()
#This will give you the normal method:
print unicode(a)
#super() will give you the parent's method:
super(ChildA, a).__unicode__()
You cannot simply use the unicode() function with the latter call, as super returns a proxy to the object, rather than an object in its own right.
This is not a good way to code. Leave it up to the class to override behaviour as it sees fit.
Related
Let me explain. I have 2 tables which are child classes of another abstract table. The abstract table has a relationship to a model called Foo. The related_name is set dynamically. The code looks like this:
class Foo(models.Model):
...
class Parent(models.Model):
foo = models.ForeignKey(
Foo,
on_delete=models.CASCADE,
related_name='%(app_label)s_%(class)s_related'
)
...
def bar(self):
print('bar')
class Meta:
abstract = True
class ChildOne(Parent):
...
class ChildTwo(Parent):
...
Therefore, the related names become 'myapp_childone_related', and 'myapp_childtwo_related'.
Now, lets say I want to call the bar() method of all the objects from the ChildOne and ChildTwo model that is related to a Foo object. There is a catch though, I want to it from with a class method of the Foo model. Currently, I'm doing it like this:
class Foo(models.Model):
...
def call_bar(self):
references = ('childone', 'childtwo')
for ref in references:
children = getattr(self, f'myapp_{ref}_related').all()
for child in children:
child.bar()
This works fine, but honestly feels a bit hack-y, especially when dealing with more than two children classes. Is there a nicer, more Pythonic solution to this problem?
Edit: I decided not to mention previously that I wanted to call the bar() method from within a class method of the Foo model because I thought that it was unnecessary for this question. However, Daneil Roseman's answer suggested making a list of classes, which is a good solution, but it would not work within the class method, as the classes have not yet been defined at that point in the module. So mentioning that in this edit.
A related_name is only syntactic sugar for performing a query from the related class itself. So you should just do this explicitly:
child_classes = [ChildOne, ChildTwo]
for child_class in child_classes:
children = child_class.objects.filter(foo=foo)
Anybody knows how to create a foreignkey field and make it always point to same model, so far I got these.
class PanMachineTimeUnitField(models.ForeignKey):
def __init__(self, **kwargs):
to = 'panbas.PanBasTimeUnit'
kwargs['verbose_name'] = _('Machine Unit')
kwargs['related_name'] = 'machine_unit'
super(PanMachineTimeUnitField, self).__init__(to, **kwargs)
But I got errors when on start.
I aim to use it like,
machine_unit = PanMachineTimeUnitField()
No further declarations needed.
Edit:
I want this because, I will have this foreignkey in quiet a few places. If I want to change the verbose_name of field, I want all of my fields to be affected by this change. Verbose name was an example, it may be an another attribute.
I dont want to use settings py to declare the defaults, either.
I recommend that you use only a simple function to create a similarly pre-configured instance of ForeignKey: (not an instance of subclass of ForeignKey)
def pan_machine_time_unit_field(**kwargs):
othermodel = 'panbas.PanBasTimeUnit'
on_delete = models.DO_NOTHING # or what you need
kwargs['verbose_name'] = 'Machine Unit'
kwargs.setdefault('related_name', '+')
# or: kwargs.setdefault('related_name', "%(app_label)s_%(class)s_related",
return models.ForeignKey(othermodel, on_delete, **kwargs)
class C(models.Model):
machine_unit = pan_machine_time_unit_field()
# or:
# machine_unit = pan_machine_time_unit_field(related_name='klass_c_children')
The related_name attribute is a name used for backward relation from the target object of othermodel to all objects that reference it. That name must be unique on othermodel ('panbas.PanBasTimeUnit', usually something with app and class name that is unique enough) or that name can be '+' if you don't want to create a backward relationship query set. Both variants are implied in the example. Also remember on_delete.
If you would really need to create a subclass (which makes sense if more methods need be customized), you must also define a deconstruct method for migrations. It would be complicated if you need to modify such subclass later. It can be never removed, renamed etc. due to migrations on a custom field. On the other hand, if you create a simple instance of ForeignKey directly by a function, all about migrations can be ignored.
EDIT
Alternatively you can create an abstract base model with that field and create new models by inheritance or multiple inheritance:
class WithPanBasTimeUnit(models.Model):
machine_unit = models.ForeignKey(
'panbas.PanBasTimeUnit',
models.DO_NOTHING,
verbose_name=_('Machine Unit'),
related_name='%(app_label)s_%(class)s_related'
)
class Meta:
abstract = True
class ExampleModel(WithPanBasTimeUnit, ...or more possible base models...):
... other fields
This solution (inspired by an invalid soution Ykh) useful if you want to add a method to models with that field or to add more fields together, otherwise the original solution is easier.
class PanBasTimeUnit(models.Model):
machine_unit = models.ForeignKey('self', blank=True, null=True,
verbose_name=u'parent')
use 'self' or 'panbas.PanBasTimeUnit' will fine.
You can not have several Foreign Keys to a model with same related_name.
Indeed, on a PanBasTimeUnit instance, which manager should Django return when calling <instance>.machine_unit? This is why you have to be carefull on related models and abstract classes.
It should work fine if you remove kwargs['related_name'] = 'machine_unit' in your code, and replace it with kwargs['related_name'] = "%(app_label)s_%(class)s_related" or something similar.
A slight modification in your attempt should do your work.
class PanMachineTimeUnitField(models.ForeignKey):
def __init__(self, **kwargs):
kwargs["to"] = 'panbas.PanBasTimeUnit'
kwargs['verbose_name'] = _('Machine Unit')
kwargs['related_name'] = 'machine_unit'
super(PanMachineTimeUnitField, self).__init__(**kwargs)
why not use directly machine_unit = models.ForeignKey(panbas.PanBasTimeUnit, verbose_name=_('Machine Unit'), related_name='machine_unit')) ?
I have a super class like this:
class Superclass(models.Model):
number = models.PositiveIntegerField()
class Meta:
abstract = True
def get_next(self):
return Superclass.objects.get(number=self.number+1)
Now, I have a child class that inherits from the superclass.
What's the problem?
I can't do this: Superclass.objects because the superclass doesn't refer to any database table.
I don't want to query all Superclass childs, only the one of the current child class, like this: When I do instance_of_child1.get_next I don't want to get an instance of Child2.
How to solve this?
My first idea was to add a static constant to any child class that contains the class (So I could do self.myclass.objects) But this seems to be not a good way.
Make the method get_next being part of the child class. Problem: there will be duplicates.
This should work:
def get_next(self):
return self.__class__.objects.get(number=self.number+1)
I am adding a slug to all my models for serialization purposes, so I have defined an abstract base class which uses the AutoSlugField from django_autoslug.
class SluggerModel(models.Model):
slug = AutoSlugField(unique=True, db_index=False)
class Meta:
abstract=True
I also have a custom manager and a natural_key method defined, and at this point I have about 20 child classes, so there are several things that make using an abstract base model worthwhile besides just the single line that defines the field.
However, I want to be able to switch a few of the default arguments for initializing the AutoSlugField for some of the child models, while still being able to utilize the abstract base class. For example, I'd like some of them to utilize the populate_from option, specifiying fields from their specific model, and others to have db_index=True instead of my default (False).
I started trying to do this with a custom Metaclass, utilizing custom options defined in each child Model's inner Meta class, but thats become a rat's nest. I'm open to guidance on that approach, or any other suggestions.
One solution would be to dynamically construct your abstract base class. For example:
def get_slugger_model(**slug_kwargs):
defaults = {
'unique': True,
'db_index': False
}
defaults.update(slug_kwargs)
class MySluggerModel(models.Model):
slug = AutoSlugField(**defaults)
class Meta:
abstract = True
return MySluggerModel
class MyModel(get_slugger_model()):
pass
class MyModel2(get_slugger_model(populate_from='name')):
name = models.CharField(max_length=20)
Update: I started out with the following solution, which was ugly, and switched to Daniel's solution, which is not. I'm leaving mine here for reference.
Here's my Metaclass rat trap that seems to be working (without extensive testing yet).
class SluggerMetaclass(ModelBase):
"""
Metaclass hack that provides for being able to define 'slug_from' and
'slug_db_index' in the Meta inner class of children of SluggerModel in order to set
those properties on the AutoSlugField
"""
def __new__(cls, name, bases, attrs):
# We don't want to add this to the SluggerModel class itself, only its children
if name != 'SluggerModel' and SluggerModel in bases:
_Meta = attrs.get('Meta', None)
if _Meta and hasattr(_Meta, 'slug_from') or hasattr(_Meta, 'slug_db_index'):
attrs['slug'] = AutoSlugField(
populate_from=getattr(_Meta, 'slug_from', None),
db_index=getattr(_Meta, 'slug_db_index', False),
unique=True
)
try:
# ModelBase will reject unknown stuff in Meta, so clear it out before calling super
delattr(_Meta, 'slug_from')
except AttributeError:
pass
try:
delattr(_Meta, 'slug_db_index')
except AttributeError:
pass
else:
attrs['slug'] = AutoSlugField(unique=True, db_index = False) # default
return super(SlugSerializableMetaclass, cls).__new__(cls, name, bases, attrs)
The SlugModel looks basically like this now:
class SluggerModel(models.Model):
__metaclass__ = SluggerMetaclass
objects = SluggerManager()
# I don't define the AutoSlugField here because the metaclass will add it to the child class.
class Meta:
abstract = True
And I can acheive the desired effect with:
class SomeModel(SluggerModel, BaseModel):
name = CharField(...)
class Meta:
slug_from = 'name'
slug_db_index = True
I have to put SluggerModel first in the inheritance list for models having more than one abstract parent model, or else the fields aren't picked up by the other parent models and validation fails; however, I couldn't decipher why.
I guess I could put this an answer to my own question, since it works, but I'm hoping for a better way since its a bit on the ugly side. Then again, hax is hax so what can you do, so maybe this is the answer.
I am trying to design an abstract model that contains a field. Subclassed models will have this field, but they will be of various field types.
Example
class AbsModel(models.Model):
data = models.??? #I want subclasses to choose this
def __unicode__(self):
return data.__str__()
class Meta:
abstract = True
class TimeModel(AbsModel):
data = models.TimeField()
...
class CharModel(AbsModel):
data = models.CharField(...)
...
I am looking for a way to enforce the existence of the data field so I can write unicode once for all objects.
If this isn't possible, how can I refer to the "data" field of the subclass when calling the super class's unicode
I have a feeling this second question has an obvious answer I am missing.
It's not possible to override a superclass field where the field is of type models.Field.
https://docs.djangoproject.com/en/1.4/topics/db/models/#field-name-hiding-is-not-permitted
You can get round this by defining a field of another type in the superclass, and then overriding it in the child (perhaps include a __str__() method just in case the data field isn't overriden).
from django.db import models
class AbsDataField:
def __str__(self):
return "undefined"
class AbsModel(models.Model):
data = AbsDataField
def __unicode__(self):
return self.data.__str__()
class Meta:
abstract = True
class TimeModel(AbsModel):
data = models.TimeField()
#...
class CharModel(AbsModel):
data = models.CharField(max_length=32)
#...
You can write something like that:
class AbsModel(models.Model):
def __unicode__(self):
if hasattr(self, "data") and isinstance(self.data, models.Field):
return data.__str__()
return u"Unknown"
You cannot do that in Django:
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.
Overriding fields in a parent model leads to difficulties in areas
such as initializing new instances (specifying which field is being
initialized 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 arbitrary.
[...]
Django will raise a FieldError if you override any model field in any
ancestor model.