Django admin interface does not use subclass __unicode __ ()

(Django 1.x, Python 2.6.x)

I have models for the melody:

class Animal(models.Model): pass class Cat(Animal): def __unicode__(self): return "This is a cat" class Dog(Animal): def __unicode__(self): return "This is a dog" class AnimalHome(models.Model): animal = models.ForeignKey(Animal) 

I did not create any animals, because it must be a virtual class. I created an instance of Cats and Dogs, but on the Admin page for AnimalHome my options for animals are displayed as "Animal Object" (by default __unicode __ (), I think), unlike __unicode__, which I defined for two subclasses. Reference.


The problem of the abstract base class is a red herring on this, I think. Even if Animal was not supposed to be abstract, I still have a problem, for some reason, since ForeignKey is defined in Animal, and not in one of its subclasses, the superclass method is called instead of the subclass. In OO programming, when you call object.method (), you must get the implementation of the lowest subclass, and you need to do extra work to get the implementation of the superclass. So, why is it not enough to specify __unicode__ in subclasses - in fact, the problem may be that __unicode__ is not called at all, because introspection in the Animal class shows that it is not defined. Therefore, perhaps, if I define __unicode__ for Animal and call its subclasses __unicode__, I could get the desired effect.


Ok, I think I understand the problems of ORM. Both of these answers helped me figure this out, thanks. During the experiments, I found that when Django saves the subclassed model, it performs two functions: (1) creates a row for the subclass object in the superclass table and (2) makes the PK in the subclass table identical to the PK assigned in the superclass table. This PK in the subclass table is called superclass_ptr. Based on this, I came up with the following. I will be grateful for the feedback.

 Class Animal(models.Model) def __unicode__(self): if Dog.objects.filter(pk=self.pk).count() > 0: return unicode(Dog.objects.get(pk=self.pk)) elif Cat.objects.filter(pk=self.pk).count() > 0: return unicode(Cat.objects.get(pk=self.pk)) else: return "An Animal!" 

It seems that Lawrence has most of all come up with this question. Cat and Dog will have disjoint PK sets (and any subclass of Animal will have a PK identical to that of its superclass), but unfortunately Django does not do any work behind the scenes a la: “I am Animal. Animals have subclasses of Dog and Cat. In particular, I am Animal number 3, and in addition, I just checked, and there is Cat No. 3. This means that I am actually Cat number 3. " Despite the fact that this seems quite possible and very reasonable (since Cat will not do anything that the animal could not do on its own) using Python introspection. Thanks to everyone.

+4
source share
6 answers

ForeignKey (Animal) is just a foreign key reference for a row in the Animal table. There is nothing in the basic SQL schema that indicates that the table is used as a superclass, so you are returning an Animal object.

To get around this:

First, you want the base class to be non-abstract. This is necessary for ForeignKey in any case, and also ensures that dogs and cats have spaced primary key sets.

Django now implements inheritance using OneToOneField. Because of this, an instance of a base class that has an instance of a subclass gets a reference to that instance, named accordingly. This means that you can do:

 class Animal(models.Model): def __unicode__(self): if hasattr(self, 'dog'): return self.dog.__unicode__() elif hasattr(self, 'cat'): return self.cat.__unicode__() else: return 'Animal' 

This also answers your Ber question about unicode (), which depends on the other attributes of the subclass. You are actually calling the appropriate method for the instance of the subclass.

Now this suggests that since Django is already looking for instances of the subclass behind the scenes, the code can just go all the way and return a Cat or Dog instance instead of Animal. You will need to discuss this issue with the developers. :)

+5
source

You want an abstract base class ("virtual" means nothing in Python.)

From the documentation:

 class CommonInfo(models.Model): name = models.CharField(max_length=100) age = models.PositiveIntegerField() class Meta: abstract = True 

Edit

"When programming OO when calling object.method (), you should get an implementation of the lowest subclass."

True But not the whole story.

This is not an OO problem. Or even a Python or Django problem. This is a problem with ORM.

Question: "What object was reconstructed at the end of the FK directory?" And the answer is that there is no standard, obvious answer on how to handle the conversion from the FK value to an object.

I have a line in AnimalHome with animals value 42. It refers to Animal.objects.get(pk=42) . What is the subclass of animals? Cat? Dog? How does the ORM level know if it should do Dog.objects.get(pk=42) or Cat.objects.get(pk=42) ?

"But wait, you say." It should get an Animal object, not a Dog or Cat object. "You can hope so, but that’s not how Django ORM works. Each class is a separate table. Cat and Dog are, by definition, separate tables with separate queries. You You don’t use object storage, you use ORM for relational tables.


Edit

Firstly, your request only works if Dog and Cat have a common key generator and do not have an overlapping PK set.

If you have a dog with a PC of 42 and a cat with a PC of 42, you have a problem. And since you cannot easily manage key generation, your solution may not work.

Runtime type identification is poor. This is not Object-Oriented in several ways. Almost everything you can do to avoid RTTI is better than an ever-expanding sequence of if statements to distinguish between subclasses.

However, the model you are trying to build is, in particular, a pathological problem for ORM systems. Indeed, it is so specifically pathological that I am almost ready to bet on homework. [There are also pathological problems for pure SQL systems. They often appear as homework.]

The problem is that ORM cannot do what you think should do. Thus, you have two options.

  • Stop using Django.
  • Do something Django directly.
  • Go to the OO design guide and resort to fragile things like RTTI, which makes it extremely difficult to add another subclass of animals.

Consider this method to make RTTI - it includes the class name as well as PK

 KIND_CHOICES = ( ( "DOG", "Dog" ), ( "CAT", "Cat" ), ) class Animal( models.Model ): kind = models.CharField( max_length= 1, choices=KIND_CHOICES ) fk = models.IntegerField() def get_kind( self ): if kind == "DOG": return Dog.objects.get( pk = fk ) elif kind == "CAT": return Cat.objects.get( pk = fk ) 
+6
source

Django (and relational databases in general) do not work this way. Even when using ORMs such as Django, you do not work with such class hierarchies.

There are two possible solutions to your problem:

(1) specify a “name” to define the Animal model, then add entities with names from ['Dog', 'Cat']. This will show the animal names in the foreign key selection field.

(2) If you really need to associate your foreign key with different models (which is really not the usual way to use an RDBMS), you should read about General Relationships in documents in the contenttypes structure.

My advice: (1).

+3
source

This is similar to what S. Lott suggested, but without if / elif / ..., which can become increasingly inconvenient and difficult to maintain, as the number of subclasses that need to be supported is growing.

 class Cat(models.Model): def __unicode__(self): return u'A Cat!' class Dog(models.Model): def __unicode__(self): return u'A Dog!' class Eel(models.Model): def __unicode__(self): return u'An Eel!' ANIMALS = { 'CAT': {'model': Cat, 'name': 'Cat'}, 'DOG': {'model': Dog, 'name': 'Dog'}, 'EEL': {'model': Eel, 'name': 'Eel'}, } KIND_CHOICES = tuple((key, ANIMALS[key]['name']) for key in ANIMALS) class Animal(models.Model): kind = models.CharField(max_length=3, choices=KIND_CHOICES) fk = models.IntegerField() def get_kind(self): return ANIMALS[self.kind]['model'].objects.get(pk=self.fk) def __unicode__(self): return unicode(self.get_kind()) 

Something very similar can also be done using inheritance across multiple Django tables (to do this, search for Django documents). For instance:

 ANIMALS = { 'CAT': {'model_name': 'Cat', 'name': 'Cat'}, 'DOG': {'model_name': 'Dog', 'name': 'Dog'}, 'EEL': {'model_name': 'Eel', 'name': 'Eel'}, } KIND_CHOICES = tuple((key, ANIMALS[key]['name']) for key in ANIMALS) class Animal(models.Model): kind = models.CharField(max_length=3, choices=KIND_CHOICES) def get_kind(self): return getattr(self, ANIMALS[self.kind]['model_name'].lower()) def __unicode__(self): return unicode(self.get_kind()) class Cat(Animal): def __unicode__(self): return u'A Cat!' class Dog(Animal): def __unicode__(self): return u'A Dog!' class Eel(Animal): def __unicode__(self): return u'An Eel!' 

I personally prefer the second option, since instances of the subclass will have all the fields defined in the parent class automatically-magically, which allows for a clearer and more concise code. (For instace, if the Animal class had a "gender" field, then Cat.objects.filter (gender = 'MALE') will work).

+2
source

Regarding general relationships, note that regular Django requests cannot cover GenerecForeignKey relationships. Using inheritance with multiple tables avoids this problem because it is a less general solution.

From the docs:

Due to the way GenericForeignKey is implemented, you cannot use such fields directly with filters (e.g. filter () and exclude ()) through the database API. They are not normal field objects. These examples will not work:

 # This will fail >>> TaggedItem.objects.filter(content_object=guido) # This will also fail >>> TaggedItem.objects.get(content_object=guido) 
+1
source

All Articles