Demystifying  Django ORM

Demystifying Django ORM

A simple and precise Blog on the basics of Django ORM.


Hello folks !! Happy to see y'all here. Here in this blog, I will share a simple overview of Django ORM and how it makes a great impact on the backend development in the platform. Django is a very powerful, robust framework that is being used for building large, scalable web apps, made rapidly and in a completely modular fashion. Started being a full stack platform, to a modular strong backend, Django has traveled so many ups and downs in the web development field. Django works in MVT architecture, where M stands for Model, V stands for Views, and T stands for Templates. Here Model shows the schematic representation of database relations (tabular relations as we use relational databases mostly). So to handle all the database-works, without letting developers write raw queries (mostly SQL), django provides its own ORM!!

ORM

ORM stands for Object Relational Mapper. What is Object? - In the general idea of OOPS, we know its an entity filled with some raw data, in a prototype called class. Yeah! the answer is correct, but in backend frameworks like Django, the object is also the instance or entity of a particular table. Now, we know that the table's schema is being created in the Models section. Models are typically class-based arrangements that creates the schematic format of a certain table by applying its fields inside variables (column names mostly). But how we will get to see the raw database table from this format - that's why we need to migrate the models. So now you can see, that by both ways, object is the correct name for the entities of a certain table in a certain database. What is Relational? - In DBMS, we know that there can be 3 types of relations among tables in a certain database (1-1, 1-M or M-1, M-M). Here also we can establish all these relationships inside the Models section and thus the Mapping portion is also done.

# A demo Django Model (under APP/models.py)
from django.db import models
class Customer(models.Model): # Table name - Customer
    cid= models.CharField() # Field for taking chr i/p in 'id' col.
    email= models.EmailField() # Field for emails
    username = models.TextField()
    password = models.CharField(max_length=100) #can provide arguments
    items = models.ManytoManyField(prods) # Making a M-M relation between two tables

Components of ORM

Before starting to discuss the components, we need to know that components of ORM are nothing but the visible or hidden parts of any model. So basically the components to create a model and components to manipulate a model's object's data (searching,sorting,filtering,arithmatic functions etc) comes under ORM.

For the Creation of a model, we just use the fields of the models.Model class. I have told some of the most common ones , you can search for more in the docs also (https://docs.djangoproject.com/en/4.2/ref/models/fields/)
(Do change the version, if this docs got obsolete.)

But what about the Manipulation part of models ? At this point, django provides a very beautiful tool which maybe cover 60-70% of the ORM, which is - Querysets.

What is Queryset?

what do you mean by query ? A question right? In DBMS , the query is a straight-up descriptive row of small instructions, that's why SQL is a DDL (also it is DCL, DML etc..). So that's how we mean this term here also - A set of different queries which used to do CRUD, searching and all other SQL operations on objects. In django , Queryset provides developers , some database-abstraction APIs, which makes the operations easier than writing raw SQL. So, what is the structure of a query in django ? See in the demo code below

# For searching all existing objects
from APP.models import Customer
obj=Customer.Objects.all() # Used to show all the Objs of Customer model
return obj

Here, I have shown the command for retrieving all objects from a relation. If we analyze the structure of the query , It can be broken into 3 parts (separated by period). Customer - is the Model-name / table-name / relation-name upon which we will fire the queries. Objects - this part is typically called Manager in Django. Similar to the manager in the company, who instructs his/her employees to do some tasks, here the 'Objects' manager also holds the APIs and when we mention the manager, we get the access to the APIs also to do our task. Django , by default provided the 'Objects' manager , although we can manipulate the manager , create a new manager and do various operations on it also , will come to this topic later on this blog. So till now, we know that Django provides a easier structure to perform queries over a model which is governed by a manager who provides the APIs. so what are the apis - Based on the raw SQL operations , django have apis based on every single operations. In previous code, '.all()' is an API. I am showing some few in code below , you can search others in the docs.

# For Creating a new object
from APP.models import Customer
obj=Customer.Objects.create(cid="////",email="////".....) 
# OR -------------------
obj=Customer(cid="////",email="////"......)
obj.save() # this api resides within create() api but have to call manually in this way
return obj
# For filtering or getting some specified objects
from APP.models import Customer
obj=Customer.Objects.filter(username="///") # we got the objects by specifing an attribute
obj1=obj.all() # Showing their details ( Here I could nest the two apis but for simplicity I broke them)
return obj1
# If there is only one object then use this query, otherwise use filter
from APP.models import Customer
obj=Customer.Objects.get(username="///")
return obj
# nested query (similar to the mnested queries in SQL)
from APP.models import Customer
obj=Customer.Objects.filter(username="//").update(email="////")
return obj

So, I think, you all got a brief understanding of the queryset and how it works, up to this point. Apart from thes most basic queries there are more queries which you can find in the django docs like (bulk_create(),get_or_create() etc). So now lets move our focus on the Manager portion.

What is a Manager?

From our previous part, we know manager is the heart of the query structure, rather Queryset. Manager holds the APIs which we use. Typically manager is also a class-based interface. Now we will discuss how to play with it.

  • Renaming default 'Objects' manager

      from django.db import models
      class Person(models.Model):
          '''
          Here we are storing all the APIs from the default manager
          to new manager
          '''
          people = models.Manager() # Now the manager-name is 'people'
      # e.g : obj=Person.people.all()
      # Person.Objects.all() will throw an AttributeError
    
  • Creating New manager (by extending the given/base manager)

    We can create our custom managers by taking help from the base manager . According to django docs, if developer wants to add more methods/APIs to the manager or modify the initial APIs working process.

      # Purpose-1(new api add)
      from django.db import models
      from django.db.models.functions import Coalesce
    
      class PollManager(models.Manager): # Creating a new Manager class
          def with_counts(self): # New API/method
              return self.annotate(num_responses=Coalesce(models.Count("response"), 0))
    
      class OpinionPoll(models.Model):
          question = models.CharField(max_length=200)
          objects = PollManager() # changing the objects manager by new manager class
    
      # Now OpinionPoll.objects.all() may give same result but the manager's internals have been changed
    

    If we want to change the existing api's working process, then we have to redefine the get_queryset of manager. see the code below-

      # Purpose-2
      # First, define the Manager subclass.
      class DahlBookManager(models.Manager):
          def get_queryset(self): # changing the 'get_queryset'
              return super().get_queryset().filter(author="Roald Dahl")
    
      # Then hook it into the Book model explicitly.
      class Book(models.Model):
          title = models.CharField(max_length=100)
          author = models.CharField(max_length=50)
          objects = models.Manager()  # The default manager.
          dahl_objects = DahlBookManager()  # The Dahl-specific manager.
    
      # Book.objects.all() return all the books
      # Book.dahl_objects.all() return only the books written by Roald Dahl
    

    These are the basic ways to manipulate managers. Based on these two purpose of changing manager, there are many ways we can apply. Now I want to put some light on few other related subtopics related to ORM.

Model Instance Reference

This topic opens about some methods/APIs provided by Model . The first one is itself - 'Model' , it opens up to the fields of it. But it do not touches the database , Untill we use save() method. Else we have For More Information see from_db() method. When we put some validations on raw data and want to get the correct new datas , we use Model.clean() or Model.validate_unique() etc. Mostly we use these apis with ModelForm or Serializers (for REST API creations). For more informations, check-
(https://docs.djangoproject.com/en/4.2/ref/models/instances/#django.db.models.Model) (check the version and change if got obsolete) .

Migrations

Migration is process of executing the Models. There are few commands related to this process -

  • python manage.py makemigrations - push all the new models or changes made in old models to a staged (pushed but not applied in db) area.

  • python manage.py migrate - Adding all the new models/changes to the database.

  • python manage.py sqlmigrate - Displays all the SQL statements for a migration.

    (you can write python manage.py to the terminal to see all commands)

If you have reached upto this line, that means you have got a brief overview of Django ORM . I am not kidding!! there is more in the docs, explore if you are interested!! Hope you have liked the pace of my narrative and I will meet you guys very soon in my next blog. Till then, Keep coding and keep exploring!!