How to access a child models or classes in Django, or how to cast models in Django.

https://django-polymorphic.readthedocs.org/en/latest/



""""""
The bellow text will be deleted
""""""

Let's think by giving a actual example...

models.py

from django.db import models

class Person(models.Model):
  name = models.CharField(max_length=200)

class Student(Person):
  student_num = models.IntegerField(default=100)

class Teacher(Person):
  teacher_num = models.IntegerField(default=100)

 

1. a most easy way...

Let think about one object which you get by Model.objects.get method. And if you know which child class this object is belong to. Then, you can access child class object by just writting object.lower_case_child_class_name.

>>> Student(name="Tom", student_num=99).save()
>>> person = Person.objects.get(name="Tom")
>>> # person.student_num <= ObjectDoesNotExist Error would occur...
>>> student = person.student # object.lower_case_child_model_name
>>> student.student_num
99

 

2. get_child_object method

I've wrote this paragraph by referring at djangosnippets: Get child model
 

Let think about one object which you get by Model.objects.get method. And if you don't know which child class this object is belong to. Then, get_child_object method which I have wrote might be helpful.


usage

>>> from polls.models import Person
>>> from polls.models import Student
>>> from polls.models import Teacher
>>> 
>>> Student(name="Tom",   student_num="1").save()
>>> Teacher(name="Malco", teacher_num="1").save()
>>> 
>>> person_list = Person.objects.all()
>>> 
>>> person_list[0]
<Person: Person object>
>>> person_list[0].student
<Student: Student object>
>>> person_list[0].teacher
Traceback (most recent call last):
...
polls.models.DoesNotExist: Person has no teacher.
>>> 
>>> get_child_object(person_list[0])
<Student: Student object>
>>> get_child_object(person_list[1])
<Teacher: Teacher object>
>>> 

 

get_child_object method

from django.core.exceptions import ObjectDoesNotExist

class MultipleChildObjectsReturned(Exception):
  def __init__(self, len_child_objects):
    self.len_child_objects = len_child_objects
  def __str__(self):
    return repr(self.len_child_objects)


def get_child_object(parental_object):
  child_objects = []
  
  all_field_names = parental_object._meta.get_all_field_names()
  
  for field_name in all_field_names:
    field = None
    child_object = None
    
    
    try:
      field = parental_object.__getattribute__(field_name)
    except ObjectDoesNotExist:
      continue
    
    if issubclass(field.__class__, parental_object.__class__):
      child_object = field
    else:
      continue
    
    if field_name == child_object.__class__.__name__.lower():
      child_objects.append(child_object)
    else:
      continue
  
  if len(child_objects) == 1:
    return child_objects[0]
  else:
    raise MultipleChildObjectsReturned(len(child_objects))

 
Q. Is it safe? or well tested?
A. I'm considering now...
 


Some description...

Model._meta.get_all_field_names method

>>> person0 = person_list[0]
>>> person1 = person_list[1]
>>> 
>>> person0._meta.get_all_field_names()
['id', 'name', 'student', 'teacher']
>>> 
>>> person0.__dict__
{'_state': <django.db.models.base.ModelState object at 0x10cda9ad0>, 'name': 'Tom', 'id': 1, '_teacher_cache': None, '_student_cache': <Student: Student object>}

 

Model.__getattribute__ method

>>> person0.__getattribute__('id')
1
>>> 
>>> person0.__getattribute__('name')
'Tom'
>>> 
>>> person0.__getattribute__('student')
<Student: Student object>
>>> 
>>> person0.__getattribute__('teacher')
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  ...
    self.related.get_accessor_name()))
myapp.models.DoesNotExist: Person has no teacher.
>>> 

 

And, if you want to use the child model's field in the parent,
just use self.child_model_field

class Person(models.Model):
  name = models.CharField(max_length=200)
  
  def print_student_num(self):
   try:
     self.student_num
   except NameError:
     print(self.__class__.__name__ + " " + "should be inherited")
     print("by a model which has the field named student_num."):
     raise
   else:
    return self.student_num

(`・ω・´)