Mastering Django models and ORM

Written by Fabien Schlegel

Fabien Schlegel

5 min

published: 12/3/2025

Mastering Django models and ORM

Writing SQL queries is a task that can quickly become repetitive, complex, and a source of security errors.

This is where Django’s ORM (Object-Relational Mapper) comes in. It is one of the most powerful features of the framework, designed to increase your productivity and peace of mind.

The Philosophy of Django ORM: What exactly is it?

An ORM is a programming technique that creates an “abstraction layer” between your code (object-oriented, in Python) and the database (relational, in SQL).

Instead of writing: SELECT * FROM blog_post WHERE status = 'published';

You write Python: Post.objects.filter(status='published')

The magic of ORM is that it translates this Python code into an optimized and secure SQL query for the database you have chosen (PostgreSQL, MySQL, SQLite, etc.).

The advantages are immense:

  • Productivity: You write Python, not SQL. It’s faster, more intuitive, and better integrated with the rest of your application.
  • Portability: Change databases (for example, from SQLite in development to PostgreSQL in production) without modifying your code. The ORM takes care of the translation.
  • Security: Django’s ORM automatically escapes parameters, protecting you from SQL injection attacks, one of the most common security vulnerabilities.
  • Readability: Your code is clearer and easier to maintain because the data logic is expressed in Python objects.

Defining Structure with Django Models

In Django, a model is the single, definitive source of information about your data. It contains the essential fields and behaviors of the data you store. Each model typically corresponds to a single database table.

To create a model, we define a class that inherits from django.db.models.Model.

Let’s imagine we are building a blog. A good place to start would be a Post model:

# in your application's models.py file
from django.db import models

class Post(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    published_date = models.DateTimeField(auto_now_add=True)
    is_published = models.BooleanField(default=True)

    def __str__(self):
        return self.title

Each attribute of the class represents a field in the database. Django will use this definition to create the blog_post table (assuming our application is called blog).

Essential Field Types

Django offers a wide range of field types to cover all your needs. Here are the most common ones:

  • CharField: For small to medium-sized character strings. The max_length argument is required. Ideal for titles, names, etc.
  • TextField: For long texts, such as the content of a blog post. No size limit (at the Django level).
  • IntegerField, FloatField, DecimalField: For storing numbers (integers, floating point, fixed precision decimals).
  • BooleanField: For True/False values. It is stored as true/false in the database. default=False is often a good practice.
  • DateTimeField, DateField, TimeField: For dates and times.
  • auto_now=True: Updates the field each time the model is saved. Perfect for a last_updated field.
    • auto_now_add=True: Only saves the date and time when the object is created. Ideal for a creation_date field.
  • EmailField, URLField: CharFields with built-in validation for email and URL formats.

Relationships Between Models

This is where the power of ORM really shines. Real-world applications have interconnected data. An article has an author, a product has a category, and so on.

ForeignKey (One-to-Many Relationship)

Use ForeignKey when an object in one model is linked to a single object in another model. This is the most common relationship.

Example: An author can write multiple articles, but an article has only one author.

from django.contrib.auth.models import User

class Author(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    bio = models.TextField()

    def __str__(self):
        return self.user.username

class Post(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    author = models.ForeignKey(Author, on_delete=models.CASCADE) # The relationship!

    def __str__(self):
        return self.title

The on_delete=models.CASCADE argument is crucial: it tells Django that if an author is deleted, all of their articles must also be deleted.

ManyToManyField (Many-to-Many Relationship)

Use ManyToManyField when an object can be linked to multiple objects of another model, and vice versa.

Example: An article can have multiple tags (categories), and a tag can be associated with multiple articles.

class Tag(models.Model):
    name = models.CharField(max_length=50, unique=True)

    def __str__(self):
        return self.name

class Post(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    author = models. ForeignKey(Author, on_delete=models.CASCADE)
    tags = models.ManyToManyField(Tag) # The relationship!

def __str__(self):
return self.title

Django will automatically create an intermediate table in the database to manage this complex relationship. You don’t have to worry about it!

OneToOneField (One-to-One Relationship)

Less common, but very useful. Use OneToOneField when an object in a model is linked to one and only one other object. It’s like a ForeignKey with a uniqueness constraint.

Example: Extend Django’s User model with a specific user profile. Each User has only one UserProfile.

from django.contrib.auth.models import User

class UserProfile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE) # The relationship!
    avatar = models.ImageField(upload_to='avatars/')
    website = models.URLField(blank=True)

    def __str__(self):
        return self.user.username

Apply Changes: Django Migrations

You’ve modified your models.py, but how does the database know about these changes? Thanks to the Django migrations system.

It’s a version control system for your database schema.

  1. Generate migration files: Every time you modify your models (add a field, delete a model, etc.), you must run this command:

    python manage.py makemigrations

    Django compares your current models to their previous state (stored in the latest migration files) and generates a new migration file in your application’s migrations/ folder. This file contains the Python code to apply your changes.

  2. Apply migrations to the database: This command reads the migration files that have not yet been applied and executes the corresponding changes on your database.

python manage.py migrate

This two-step process is incredibly robust. It allows you to develop your data schema iteratively and securely, and deploy it reliably to other environments.

Conclusion

ORM and models are at the heart of every Django application. They provide a powerful, secure, and productive way to interact with your database using only Python code.

By mastering model definition, different field types, and, most importantly, ForeignKey, ManyToManyField, and OneToOneField relationships, you have the fundamentals to build the data structure of any web application.

Related Articles