A Beginner’s Guide to Django Models: Understanding Each Option with Clear Examples

Satya Repala
13 min readAug 7, 2023

--

Introduction:

Django, a popular Python web framework, simplifies the process of building web applications by providing robust tools and functionalities. One fundamental aspect of Django is the models.py file, which defines the structure of the application's database. In this article, we will explore the various options available in Django's models and provide detailed explanations with easy-to-understand examples. By the end of this guide, you will have a solid understanding of Django models and how to harness their power to create efficient web applications.

Fields:

In Django models, fields are used to define the attributes of a model, representing the columns in the database table. Each field represents a specific data type and defines the kind of data that can be stored in the corresponding column. Django provides a variety of field types to handle different data types and constraints. Let’s delve into the most commonly used fields and explore examples for each:

from django.db import models

# CharField
class Book(models.Model):
# A field to store the title of the book (max length: 100 characters)
title = models.CharField(max_length=100)

# IntegerField
class Student(models.Model):
# A field to store the age of the student
age = models.IntegerField()

# FloatField
class Product(models.Model):
# A field to store the price of the product
price = models.FloatField()

# DateField
class Event(models.Model):
# A field to store the date of the event
event_date = models.DateField()

# DateTimeField
class Post(models.Model):
# A field to store the publication date and time of the post
pub_date = models.DateTimeField()

# BooleanField
class Task(models.Model):
# A field to store the completion status of the task
is_completed = models.BooleanField()

# ForeignKey
class Author(models.Model):
# A field to store the name of the author
name = models.CharField(max_length=100)

class Book(models.Model):
# A field to store the title of the book
title = models.CharField(max_length=100)
# A foreign key field to establish a one-to-many relationship with the Author model
author = models.ForeignKey(Author, on_delete=models.CASCADE)

# ManyToManyField
class Course(models.Model):
# A field to store the name of the course
name = models.CharField(max_length=100)

class Student(models.Model):
# A field to store the name of the student
name = models.CharField(max_length=100)
# A ManyToManyField to establish a many-to-many relationship with the Course model
courses = models.ManyToManyField(Course)

# EmailField
class Contact(models.Model):
# A field to store the email address of the contact
email = models.EmailField()

# ImageField
class UserProfile(models.Model):
# A field to store the profile picture of the user (uploading to 'profile_pics/' directory)
profile_picture = models.ImageField(upload_to='profile_pics/')

1. CharField: This field is used for short text fields, such as names, titles, or short descriptions. It is limited in length and requires the `max_length` parameter to be defined. In the `Book` model, we use a `CharField` to store the title of the book with a maximum length of 100 characters.

2. IntegerField: The `IntegerField` is used for storing integer values. It can be used for fields like age, quantity, or any other whole number. In the `Student` model, we use an `IntegerField` to store the age of the student.

3. FloatField: The `FloatField` is used for storing floating-point numbers with decimal places. It is suitable for fields like price or any other numeric value with decimals. In the `Product` model, we use a `FloatField` to store the price of the product.

4. DateField: This field is used for storing date values without time. It allows you to input dates in the format “YYYY-MM-DD.” In the `Event` model, we use a `DateField` to store the date of the event.

5. DateTimeField: The `DateTimeField` is used for storing date and time values. It includes both the date and time components and is used for fields that require precise timestamp information. In the `Post` model, we use a `DateTimeField` to store the publication date and time of the post.

6. BooleanField: This field is used for storing boolean values (True/False). It is suitable for fields like status or flags. In the `Task` model, we use a `BooleanField` to store the completion status of the task.

7. ForeignKey: The `ForeignKey` is used to create a one-to-many relationship between two models. It represents a many-to-one relationship from the current model to another model. In the `Book` model, we use a `ForeignKey` to establish a one-to-many relationship with the `Author` model, as each book can have only one author but an author can have multiple books.

8. ManyToManyField: This field is used to create a many-to-many relationship between two models. It allows a single instance of one model to be related to multiple instances of another model. In the `Student` model, we use a `ManyToManyField` to establish a many-to-many relationship with the `Course` model, as a student can enroll in multiple courses, and each course can have multiple students.

9. EmailField: This field is used to store email addresses and enforces validation for a valid email format. In the `Contact` model, we use an `EmailField` to store the email address of the contact.

10. ImageField: The `ImageField` is used to store image files. It handles the file upload and stores the path to the image file in the database. In the `UserProfile` model, we use an `ImageField` to store the profile picture of the user, and it will be uploaded to the ‘profile_pics/’ directory.

Advanced Fields:

from django.db import models
from django.contrib.postgres.fields import ArrayField

# CommaSeparatedIntegerField
class Item(models.Model):
# A field to store a comma-separated list of item codes (max length: 100 characters)
item_codes = models.CommaSeparatedIntegerField(max_length=100)

# JSONField
class Product(models.Model):
# A field to store JSON-formatted data for product properties
properties = models.JSONField()

# ArrayField (with PostgreSQL)
class Order(models.Model):
# An array field to store a list of item names
item_names = ArrayField(models.CharField(max_length=100))
  1. CommaSeparatedIntegerField: This field is used to store a comma-separated list of integers. You can use this field if you need to store an array of integers.
  2. JSONField: Starting from Django 3.1, you can use the JSONField to store JSON-formatted data, which can include arrays or lists.
  3. ArrayField (with PostgreSQL): If you are using PostgreSQL as your database, you can use the ArrayField provided by the django.contrib.postgres.fields module to store arrays of any data type, including integers, strings, or even nested arrays.

By understanding each field and its purpose, developers can efficiently define the structure of their database tables and handle various data types in their Django web applications.

Field Arguments:

When initializing fields in Django models, there are various arguments that can be used to customize the behavior of the fields. Each argument serves a specific purpose and allows you to define the characteristics and constraints of the field. Here are some of the common arguments used when initializing fields:

from django.db import models

# max_length: Specifies the maximum length of the field
class Book(models.Model):
title = models.CharField(max_length=100)

# null: Determines whether the field can store a NULL value
class Student(models.Model):
name = models.CharField(max_length=100)
age = models.IntegerField(null=True)

# blank: Indicates whether the field is allowed to be blank
class Article(models.Model):
title = models.CharField(max_length=100, blank=True)

# default: Sets a default value for the field
class Order(models.Model):
order_number = models.CharField(max_length=10, default="0000")

# choices: Provides a list of choices for the field
GENDER_CHOICES = [
('M', 'Male'),
('F', 'Female'),
('O', 'Other'),
]

class Person(models.Model):
name = models.CharField(max_length=100)
gender = models.CharField(max_length=1, choices=GENDER_CHOICES)

# primary_key: Indicates whether the field is the primary key for the model
class UserProfile(models.Model):
user_id = models.AutoField(primary_key=True)
username = models.CharField(max_length=100)

# unique: Specifies whether the field's value must be unique
class Product(models.Model):
product_code = models.CharField(max_length=10, unique=True)

# verbose_name: Sets a human-readable name for the field
class Car(models.Model):
make = models.CharField(max_length=100, verbose_name="Car Make")

# help_text: Provides additional descriptive text for the field
class Employee(models.Model):
name = models.CharField(max_length=100, help_text="Employee's full name")

# validators: Allows you to specify a list of validators for the field
from django.core.validators import MinValueValidator, MaxValueValidator

class Score(models.Model):
score = models.IntegerField(validators=[MinValueValidator(0), MaxValueValidator(100)])

# upload_to: Used with ImageField to specify the directory where uploaded files should be stored
class UserProfile(models.Model):
name = models.CharField(max_length=100)
profile_pic = models.ImageField(upload_to='profile_pics/')

# db_index: Indicates whether the field should have a database index for faster lookups
class Person(models.Model):
name = models.CharField(max_length=100, db_index=True)

# editable: Determines whether the field is editable in forms and the admin interface
class Task(models.Model):
task_name = models.CharField(max_length=100, editable=False)

1. max_length: Specifies the maximum length of the field. This argument is applicable for fields like `CharField` and `CommaSeparatedIntegerField`.

2. null: Determines whether the field can store a `NULL` value in the database. If set to `True`, the field can be `NULL`, and if set to `False`, the field will have a required value.

3. blank: Indicates whether the field is allowed to be blank (i.e., contain an empty value). If set to `True`, the field can be blank, and if set to `False`, the field must have a value.

4. default: Sets a default value for the field. If no value is provided when creating an object, the default value will be used.

5. choices: Provides a list of choices for the field. This argument is used with fields like `CharField` and `IntegerField` to restrict the possible values.

6. primary_key: Indicates whether the field is the primary key for the model. Only one field can be set as the primary key.

7. unique: Specifies whether the field’s value must be unique across all instances of the model.

8. verbose_name: Sets a human-readable name for the field. This is used as a label when displaying the field in forms or the admin interface.

9. help_text: Provides additional descriptive text for the field. This text is displayed as a tooltip or hint when users interact with the field.

10. validators: Allows you to specify a list of validators for the field. Validators are functions that validate the value of the field.

11. upload_to: Used with `ImageField` to specify the directory where uploaded files should be stored.

12. db_index: Indicates whether the field should have a database index for faster lookups.

13. editable: Determines whether the field is editable in forms and the admin interface.

These arguments provide flexibility and control over the behavior of fields in your Django models. By using these arguments appropriately, you can tailor the fields to suit your specific requirements and constraints.

Model Relations:

Model relations in Django allow you to establish relationships between different models, enabling you to represent complex data structures and associations in your database. There are three main types of model relations in Django: One-to-One, One-to-Many (or Many-to-One), and Many-to-Many.

Examples:

from django.db import models

# One-to-One Relationship
# Example: UserProfile and User models
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)
bio = models.TextField()
location = models.CharField(max_length=100)

# One-to-Many (Many-to-One) Relationship
# Example: Author and Book models
class Author(models.Model):
name = models.CharField(max_length=100)
nationality = models.CharField(max_length=50)

class Book(models.Model):
title = models.CharField(max_length=200)
author = models.ForeignKey(Author, on_delete=models.CASCADE)

# Many-to-Many Relationship
# Example: Student and Course models
class Course(models.Model):
name = models.CharField(max_length=100)

class Student(models.Model):
name = models.CharField(max_length=100)
courses = models.ManyToManyField(Course)

# Self-referential Relationship
# Example: Category model with parent-child relationship
class Category(models.Model):
name = models.CharField(max_length=100)
parent_category = models.ForeignKey('self', on_delete=models.SET_NULL, null=True, blank=True)

# Reverse Relation (Related Name)
# Example: Comment and Article models
class Article(models.Model):
title = models.CharField(max_length=200)

class Comment(models.Model):
article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='comments')
text = models.TextField()

# Many-to-Many with Through Model
# Example: Actor, Movie, and Role models
class Actor(models.Model):
name = models.CharField(max_length=100)

class Movie(models.Model):
title = models.CharField(max_length=200)
actors = models.ManyToManyField(Actor, through='Role')

class Role(models.Model):
actor = models.ForeignKey(Actor, on_delete=models.CASCADE)
movie = models.ForeignKey(Movie, on_delete=models.CASCADE)
role_name = models.CharField(max_length=100)

Model Methods:

Model methods in Django are functions defined within a model that perform various operations on the model’s data. These methods allow you to add custom functionality to your models and encapsulate related logic within the model itself. Model methods can be used to perform calculations, return formatted data, or execute specific tasks related to the model.

To define a model method, you simply create a Python function inside the model class. Model methods do not receive any special arguments by default, but they always have access to the instance of the model on which they are called (i.e., self).

Let’s explore some examples of model methods to understand their usage and benefits:

from django.db import models

class Product(models.Model):
name = models.CharField(max_length=100)
price = models.DecimalField(max_digits=8, decimal_places=2)
stock = models.IntegerField()

def calculate_total_value(self):
# Calculate the total value of the product (price * stock)
return self.price * self.stock

def is_in_stock(self):
# Check if the product is in stock
return self.stock > 0

def get_formatted_price(self):
# Return the price of the product formatted as a string
return f"${self.price:.2f}"

In this example, we have a model called Product with three fields: name, price, and stock. We've defined three model methods:

  1. calculate_total_value: This method calculates the total value of the product by multiplying the price with the stock count. It does not take any arguments other than self, which represents the instance of the product being accessed.
  2. is_in_stock: This method checks whether the product is in stock by verifying if the stock count is greater than zero. It returns a boolean value indicating the product's availability.
  3. get_formatted_price: This method returns the price of the product as a formatted string with two decimal places. It leverages string formatting to achieve the desired presentation.

Model methods are useful for performing specific operations on model instances and can be called directly on any instance of the model. They provide a clean and organized way to encapsulate related logic within the model class, making the code more maintainable and readable.

Model Managers:

Model Managers in Django are a powerful tool that allows you to customize the default database queries and manage model instances more efficiently. By defining custom managers, you can add custom methods, filters, and querysets to your models, making it easier to retrieve, create, update, and delete data from the database.

Django provides a default manager for every model, which is automatically created if you don’t define any custom managers. However, you can create your own manager to override the default one or add additional functionality.

To define a custom manager, you create a class that inherits from models.Manager or its subclasses (models.QuerySet or models.BaseManager). You can then add methods to this manager class to perform custom queries or operations on the model's data.

Let’s look at an example of a custom manager for the Product model:

from django.db import models

class ProductManager(models.Manager):
def get_available_products(self):
# Custom method to retrieve only products that are in stock
return self.filter(stock__gt=0)

def get_expensive_products(self):
# Custom method to retrieve products with a price greater than $100
return self.filter(price__gt=100)

class Product(models.Model):
name = models.CharField(max_length=100)
price = models.DecimalField(max_digits=8, decimal_places=2)
stock = models.IntegerField()

# Assign the custom manager to the model
objects = ProductManager()

In this example, we created a custom manager called ProductManager, which inherits from models.Manager. The ProductManager class contains two custom methods: get_available_products and get_expensive_products. These methods add custom filtering functionality to the Product model.

To use the custom manager, we set the objects attribute of the Product model to an instance of the ProductManager class. This allows us to call the custom methods directly on the model class.

By using model managers, you can encapsulate complex or frequently used queries within the manager class, making your code more organized and easier to maintain. It also allows you to reuse these custom queries across different parts of your application, promoting code reusability and efficiency.

Model Inheritance:

# models.py

from django.db import models

# Abstract Base Classes (Abstract Models)
class AbstractProduct(models.Model):
name = models.CharField(max_length=100)
price = models.DecimalField(max_digits=8, decimal_places=2)

class Meta:
abstract = True

# Multi-table Inheritance
class Book(AbstractProduct):
author = models.CharField(max_length=100)

# Proxy Models
class DiscountedProduct(AbstractProduct):
class Meta:
proxy = True

def discounted_price(self):
return self.price * 0.9

Abstract Base Classes (Abstract Models)

An abstract model is a base model that cannot be instantiated on its own. It is designed to be used as a base for other models and provides common fields and methods. Abstract models are defined by setting the abstract attribute of the Meta class to True. In this example, we have an AbstractProduct model representing common fields shared by various products.

Multi-table Inheritance

Multi-table inheritance allows you to create a new model that includes all the fields from the base model along with additional fields specific to the new model. This creates a one-to-one relationship between the base model and the new model. In this example, the Book model uses multi-table inheritance to include the fields from AbstractProduct along with its specific field author.

Proxy Models

Proxy models allow you to create a new model that has the same fields as the base model but does not create a new database table. Proxy models are useful when you want to change the behavior of a model or add new methods without modifying the original model. In this example, the DiscountedProduct model is a proxy model that inherits from AbstractProduct and adds a custom method discounted_price.

By using model inheritance in Django, you can create a hierarchy of related models, enabling code reuse, better organization, and more flexibility in managing different types of data in your application. It promotes the “Don’t Repeat Yourself” (DRY) principle and encourages efficient and maintainable code development.

Models Queries:

In Django, queries play a fundamental role in interacting with the database and retrieving data from models. Django’s powerful QuerySet API allows you to perform a wide range of database operations, including creating, reading, updating, and deleting objects. With QuerySets, you can filter, aggregate, order, and limit results, making it easy to extract the data you need from the database. Whether you want to fetch all records, filter based on certain conditions, perform complex lookups, or update and delete objects, Django provides a rich set of query tools to meet your needs. In this article, we will explore various types of queries and their practical applications in Django models, demonstrating how to harness the full potential of QuerySets to efficiently retrieve and manipulate data from the database.

Examples:

# models.py

from django.db import models

class Product(models.Model):
name = models.CharField(max_length=100)
price = models.DecimalField(max_digits=8, decimal_places=2)
stock = models.IntegerField()

# Create and Save
product1 = Product(name='Product A', price=25.99, stock=100)
product1.save()

# Retrieve all objects
all_products = Product.objects.all()

# Retrieve specific objects using filters
cheap_products = Product.objects.filter(price__lt=30)
in_stock_products = Product.objects.filter(stock__gt=0)

# Retrieve a single object using get() (raises DoesNotExist or MultipleObjectsReturned if not found)
try:
product2 = Product.objects.get(name='Product B')
except Product.DoesNotExist:
product2 = None

# Update objects
product1.price = 22.99
product1.save()

# Delete objects
product1.delete()

# Aggregation
total_price = Product.objects.aggregate(total=models.Sum('price'))

# Ordering
sorted_products = Product.objects.order_by('price') # Ascending order
reverse_sorted_products = Product.objects.order_by('-price') # Descending order

# Chaining queries
cheap_in_stock_products = Product.objects.filter(price__lt=30, stock__gt=0)

# Complex lookups using Q objects (OR and AND operations)
from django.db.models import Q
discounted_or_in_stock = Product.objects.filter(Q(price__lt=25) | Q(stock__gt=0))

# Complex lookups using F objects (performing operations on fields)
from django.db.models import F
Product.objects.filter(price__lt=F('stock'))

# Limiting results
limited_products = Product.objects.all()[:5]

# Counting
product_count = Product.objects.count()

# Exists
exists = Product.objects.filter(name='Product C').exists()

# Related objects (assuming a ForeignKey relation)
class Category(models.Model):
name = models.CharField(max_length=50)

class Product(models.Model):
name = models.CharField(max_length=100)
category = models.ForeignKey(Category, on_delete=models.CASCADE)

# Retrieve related objects
category_products = Category.objects.get(name='Electronics').product_set.all()

Conclusion:

Django models serve as the backbone of database interactions in Django projects. They enable developers to define data structure and manipulate the database using Python code. With powerful QuerySets, we can perform a wide range of operations like filtering, aggregating, and manipulating data efficiently. Django models’ flexibility and features, such as model relationships, inheritance, and managers, empower us to build scalable and data-driven web applications effortlessly. By utilizing Django’s ORM, we can abstract away raw SQL complexities, making development more efficient and maintainable. Overall, Django models are a powerful tool for crafting sophisticated and data-centric web applications.

--

--

Satya Repala

Data Sorcerer 🧙‍♂️ | Unveiling insights through data spells 📊 | Spreading joy and harmony 🌟 | Enchanting data science with a happy touch 😊✨