Structure - Django ORM Working - Part 1

Django ORM hides a lot of complexity while developing the web application. The data model declaration and querying pattern are simplified, whereas it's structured differently behind the scenes. The series of blog posts will explain Django ORM working(not just converting Python code to SQL), model declaration, querying (manager, queryset), supporting multiple drivers, writing custom queries, migrations etc...

Consider a model definition from the Django tutorial.

from django.db import models


class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')


class Choice(models.Model):
    question = models.ForeignKey(Question, on_delete=models.CASCADE)
    choice_text = models.CharField(max_length=200)
    votes = models.IntegerField(default=0)

The Question and Choice model class derives from models.Model. Inheriting Model signals Django at run-time the class is a database model. Question model(later converted to a table) contains two extra class variables, question_text and pub_date, which will be two columns in the table. Their type is indicated by creating an instance of the respective type of fields, here models.CharField, models.DateTimeField. A similar work applies to the Choice model.

Querying

Let's display all the instances of Question (all the rows in the table - polls_question).

>>> Question.objects.all()
<QuerySet []>

As expected, the table is empty, and the method call returns empty result. The two things to notice are objects.all() and QuerySet. All the data entry in and out of the database through Django ORM happens through the object's interface. All the results are wrapped inside the QuerySet, even empty ones.

From where does the objects instance come?

>>> class Foo:
...     pass
...
>>> class Bar(Foo):
...     pass
...
>>> Bar.mro()
[<class 'Bar'>, <class 'Foo'>, <class 'object'>]
>>> Question.mro()
[<class 'polls.models.Question'>, 
<class 'django.db.models.base.Model'>, 
<class 'object'>]

MRO for Question is clear and returns result as expected.

>>> Bar
<class 'Bar'>
>>> Question
<class 'polls.models.Question'>
>>> Question.objects
<django.db.models.manager.Manager object at 0x10bd7f1c0>
>>> Question.objects.mro()
Traceback (most recent call last):
  File "<console>", line 1, in <module>
AttributeError: 'Manager' object has no attribute 'mro'
>>> Question.objects.__class__.mro()
[<class 'django.db.models.manager.Manager'>, <class 'django.db.models.manager.BaseManagerFromQuerySet'>, <class 'django.db.models.manager.BaseManager'>, <class 'object'>]

The representation of Question.objects is different from the representation of Bar and Question classes. As the name indicates, objects is an instance of the Manager. The objects, the instance of Manager class, inherits BaseManagerFromQuerySet and BaseManager.

>>> Choice
<class 'polls.models.Choice'>
>>> Choice.objects
<django.db.models.manager.Manager object at 0x10bd7f0a0>
>>> Question.objects
<django.db.models.manager.Manager object at 0x10bd7f1c0>
>>> Choice.objects is Question.objects
False
>>> Choice.objects == Question.objects
True

What? Even though the instance uses different id but equality test returns True.

# It's decalared in manager class

def __eq__(self, other):
        return (
            isinstance(other, self.__class__) and
            self._constructor_args == other._constructor_args
        )

The logic for checking the equality has two pieces - both operands should be of the same type and their constructor args are same. In this case(behind the scenes), both the managers were called with empty arguments.

>>> Question.objects._constructor_args
((), {})
>>> Choice.objects._constructor_args
((), {})

In the next post, I'll cover how models.*Field works.

Summary

  1. The Django model inherits model.Model and all the class variables initialized with model.*Field automatically behaves like a column in the table.
  2. The interactions to the database happen through ModelManager via objects attribute.

Notes:

23