Unlocking Django ORM's Potential: A Technical Dive into Database Wizardry

Unlocking Django ORM's Potential: A Technical Dive into Database Wizardry

ยท

8 min read

Django's ORM (Object Relation Mapping) system is considered to be one of the most powerful and robust features of Django. It is a bridge between the application's Object Oriented Code and the database.

If we dive into the technical lexicon, ORM is a programming paradigm that allows developers to interact with databases using an object-oriented approach. Django ORM simplifies the database interactions by allowing developers to work with Python objects instead of writing complex SQL queries. This not only enhances code readability but also promotes rapid development. Putting it in quite simple words, Django ORM allows us to setup database tables using Python classes and do all the CRUD (Create, Retrieve, Update and Delete) operation using Python objects only.

Let's try to create a simple schematic diagram to understand how it works.

We can consider a scenario. Django knows only one language named Python and the database knows only SQL, maybe. Now, Django has to ask the database about creating a Post Table, which has some fields like id, title and description etc. Django has two kind of translators to approach. One is a RAW SQL QUERY, which isn't a trustworthy translator and anybody can tamper the information with it. The second one is Django ORM, which is a trustworthy one and it's easy for Django to ask it to send some information to the database or get some information from the database. Let's try and understand with the help of one more simple schematic diagram.

In the diagram, Django attempts to create a Post table in the database, for which it has got two ways to espouse.

  1. Using a RAW SQL Query. Let's see the code.

     from django.db import connection
    
     def create_posts_table():
         with connection.cursor() as cursor:
             cursor.execute("""
                 CREATE TABLE post (
                     id SERIAL PRIMARY KEY,
                     title VARCHAR(255),
                     content TEXT
                 )
             """)
     # The CREATE TABLE SQL query is used to define the structure of the "posts" table.
     # id is a serial primary key, commonly used for unique identification.
     # title is a VARCHAR field for the post title.
     # content is a TEXT field for the post content.
    
  2. Using Django ORM

     from django.db import models
    
     class Post(models.Model):
         title = models.CharField(max_length=255)
         content = models.TextField()
    
         def __str__(self):
             return self.title
    

So, the first way was to write a raw SQL query, but that's insecure because that's very prone to SQL injection attacks. Let's understand how. See the code below

# Unsafe code without proper use of Django ORM parameterization

user_input = request.GET.get('username')
query = f"SELECT * FROM users WHERE username = '{user_input}'"
results = MyModel.objects.raw(query)

In this example, the user_input variable is directly interpolated into the SQL query string. If an attacker provides a malicious input, they can manipulate the query to execute arbitrary SQL code. For instance, an attacker might input something like:

' OR 1=1; -- malicious input

The resulting query would be:

SELECT * FROM users WHERE username = '' OR 1=1; --'

This query always evaluates to true (1=1), essentially fetching all records from the "users" table. This is a classic SQL injection attack.

To prevent SQL injection attacks, it's crucial to use parameterized queries provided by Django ORM. Here's a safer version of the code using Django ORM:

user_input = request.GET.get('username')
results = MyModel.objects.filter(username=user_input)

Django ORM automatically handles parameterization, making it much more resistant to SQL injection attacks.

This is one of the major benefits of the Django ORM. Security, right? When we are handling sensitive user data, security is a necessity, so, the Django ORM provides that without you putting in much effort.

Now, let's dive into the details of Django ORM and see how we can use it for different CRUD operations and make our application seamless and secure. We'll simply start by creating a model and then utilising in our view function for CRUD ops.

#home/models.py
'''Assuming you already have a Django project and a home app installed 
   and some basic database setup'''

from django.db import models
from django.contrib.auth.models import User

class Post(models.Model):
    title = models.CharField(max_length = 200)
    description = models.TextField()
    timestamp = models.DateTimeField(auto_now_add = True)
    author = models.ForeignKey(User, on_delete = models.CASCADE)

Let me break it into simple steps.

  1. Import Statements:

    • from django.db import models: This imports the necessary functionality for defining database models in Django.

    • from django.contrib.auth.models import User: This imports the built-in User model that Django provides for handling user authentication.

  2. Post Model:

    • class Post(models.Model):: This line defines a new model named Post, which is a type of database table in Django.

Fields in the Post Model:

  • title = models.CharField(max_length=200): This creates a field named title for the Post model, which is a character field (text) with a maximum length of 200 characters.

  • description = models.TextField(): This creates a field named description for the Post model, which is a larger text field without a specified maximum length.

  • timestamp = models.DateTimeField(auto_now_add=True): This creates a field named timestamp for the Post model, representing the date and time when the post is created. auto_now_add=True means that this field will be automatically set to the current date and time when a new Post instance is created.

  • author = models.ForeignKey(User, on_delete=models.CASCADE): This creates a field named author for the Post model, establishing a many-to-one relationship with the built-in User model. This field represents the author of the post. The on_delete=models.CASCADE parameter specifies that if a User is deleted, then all the posts associated with that user should also be deleted.

๐Ÿ’ก
We will discuss the on_delete parameter in detail in the next article.

Now, once we have created this model, we have to migrate our changes to the database. How do we do that? Simple. Just run the following commands in your terminal.

python manage.py makemigrations
python manage.py migrate

The first command python manage.py makemigrations creates a migrations file, which specifies what all changes need to be mapped to the database and the second command python manage.py migrate migrates all those changes to the database. Once you run those commands, if you've followed the blog correctly, you'll see some satisfying things running in the terminal saying something like applying migrations . Once done, you can see that the Post table created in your database.

Now, how do I see it in the admin panel? Simple, we'll register our model in our admin.py file as follows:

#home/admin.py
from django.contrib import admin
from .models import Post

# Register the Post model with the admin site
admin.site.register(Post)

This helps me view the data of the Post model in the Django Admin Panel and also perform any CRUD operations on it in the admin panel.

Now, we'll see that how we'll use the Django ORM to perform CRUD operations on this Post model.

First, let us see, how we can create a post. Since, we're discussing the Django ORM, we'll focus on that only. You can complete the view function to render the response or set a template for creating a post.

from .models import Post

def create_post(request):
    user = request.user
    title = "Unlocking Django ORM's Potential"
    description = "Django's ORM (Object Relation Mapping) system is considered to be one of the most powerful and robust features of Django. It is a bridge between the application's Object Oriented Code and the database."
    post = Post.objects.create(title=title, description=description, author=user)

So, instead of using a raw SQL query, we simply used the following line of code

post = Post.objects.create(title=title, description=description, author=user) . It will create an instance of the Post model in our database with the details as passed in the parameters. The timestamp will be added automatically as we have set auto_now_add=True

Now, let's see how can we fetch all posts and a specific user's posts from the database.

from .models import Post

# This is how we can get all posts
def get_all_posts(request):
    allPosts = Post.objects.all()

# This is how we can get user specific posts
def get_users_posts(request):
    user = request.user
    userPosts = Post.objects.filter(author = user)

So, we used allPosts = Post.objects.all() to get all the posts from the database. It simply fetches all the posts from the database in the form of a QuerySet. Similarly, we used userPosts = Post.objects.filter(author = user) to filter from the database all the posts written by the user making the request.

๐Ÿ’ก
A QuerySet serves as a container for database queries, enabling the extraction of specific data from a database. It provides an efficient means of accessing data without executing any database actions until explicitly triggered. QuerySets are versatile and can be chained to perform various operations such as filtering, ordering, and manipulating data before retrieval from the database. When working with models in Django, QuerySets offer a convenient way to interact with the database using Python code. In the above mentioned code, both allPosts and userPosts are quersets. The best part is that the database isn't queried until and unless we use the queryset somewhere.

In conclusion, we've discussed the fundamentals of Django ORM using quite simple schematic diagrams and explored how it simplifies database interactions within a Django web application using simple code snippets. By leveraging the power of models, migrations, querysets, and relationships, we can create a robust and maintainable data layer for our projects.

As we wrap up this discussion, it's worth noting that Django ORM not only excels in creating and retrieving data but also extends its capabilities to handle updates and deletions seamlessly. To further enhance your understanding and mastery of Django ORM, I encourage you to explore the use of HTTP methods like PUT, PATCH, and DELETE.

These HTTP methods align with the standard CRUD operations (Create, Read, Update, Delete) and can be harnessed to manipulate data in our Django models. By incorporating these methods into your views or API endpoints, you'll unlock the full potential of Django ORM for creating, updating, and deleting records in a clean and efficient manner.

Keep Learning, Keep Sharing, Keep Djangoing!

Peace, Dot!

ย