5

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

Community
  • 1
  • 1
Ohad
  • 1,719
  • 1
  • 16
  • 20
  • Please show some evidence that you are doing what you think you are doing. – Marcin Mar 19 '12 at 14:15
  • **If you've just started developing with Django, I'd recommend you to refrain from using [multi-table inheritance](https://docs.djangoproject.com/en/dev/topics/db/models/#multi-table-inheritance). Consider using simpler and less bug-prone approaches. [Abstract base classes](https://docs.djangoproject.com/en/dev/topics/db/models/#abstract-base-classes) will suit you well.** – Ivan Kharlamov Mar 19 '12 at 17:56

4 Answers4

5

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.

Chris Pratt
  • 232,153
  • 36
  • 385
  • 444
  • Cool. So at least I know it's not something I missed. What are possible workarounds for this behavior so I can access the correct sub-class methods for `some_parent.children.all()`? – Ohad Mar 19 '12 at 14:48
  • Nice ! You could also add an "assert False" statement at the bottom of the get_child() method, to get some evidence at run-time that some derived class is missing in the list – Mario Orlandi Jul 26 '17 at 11:08
0

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

  • I can't use an abstract class because I need the `related_name` functionality and I need to use it with a single name (`children` in my example). https://docs.djangoproject.com/en/dev/topics/db/models/#be-careful-with-related-name – Ohad Mar 19 '12 at 13:44
0

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.

Marcin
  • 48,559
  • 18
  • 128
  • 201
  • I am calling the method on a `Parent`. The `Parent.__unicode__()` method cycles through the `__unicode__()` methods of all the children but it's the original method and not the overridden version. – Ohad Mar 19 '12 at 13:48
  • http://pastebin.com/fjU62hMa It seems like the system refers to the inheriting models like a parent model. @Marcin – Ohad Mar 19 '12 at 14:05
  • 1
    @Ohad Please put the output and code in your question, edited down appropriately. In any case, I do not see that this shows that you are receiving the results of the sub-instance unicode methods. – Marcin Mar 19 '12 at 14:07
  • I'm not sure it is necessary in order to replicate the situation I'm describing, but I will try to make it more concise and edit it in later because maybe I'm missing something. Thanks. – Ohad Mar 19 '12 at 14:10
  • Well, there's a difference between lying and overlooking something that may be the key to the solution which is probably what you're implicating I'm doing. Thanks for the help anyways. – Ohad Mar 19 '12 at 14:13
  • @Ohad: I don't think you're lying, but I do think that you are incorrect, and there is no reason to believe otherwise. No-one can help you if you insist on ignoring what is actually happening. – Marcin Mar 19 '12 at 14:15
  • Turns out I was not incorrect. Thanks for motivating me to figure that out. – Ohad Mar 19 '12 at 15:20
  • @Ohad Where's the evidence? The answer you accepted (from Chris Pratt) agrees with my analysis. – Marcin Mar 19 '12 at 15:30
  • No, it doesn't. Chris said it is standard behavior, which is correct, and you said I was calling the method on the wrong instance, which is incorrect or at the very least incomplete. – Ohad Mar 19 '12 at 15:40
  • @Ohad Not really. You are calling it on a different type of object than you thought, which accounts for the entire issue. – Marcin Mar 19 '12 at 15:41
0

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.

Steve Mayne
  • 22,285
  • 4
  • 49
  • 49