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'>]
Every Python class has a Method Resolution Order, which determines the behavior of the class/method invocation. In the above example, the Bar class inherits the Foo class. The mro
method returns the order from child to patent class, Bar inherits Foo, Foo inherits object. As you can see, the Foo class has no inherited class in the code, but the mro
method says Foo inherits object. All the Python class inherits from the object; hence mro
method returns object
as the last parent in the list.
>>> 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
- The Django model inherits
model.Model
and all the class variables initialized withmodel.*Field
automatically behaves like a column in the table. - The interactions to the database happen through
ModelManager
viaobjects
attribute.
See also
- Profiling Django App
- Type Check Your Django Application
- Python Typing Koans
- Model Field - Django ORM Working - Part 2
- jut - render jupyter notebook in the terminal
This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.