Models
As mentioned earlier, Django provides an abstraction layer (the "Model") for structuring and manipulating the data of your web application.
What is a Model
In Django, a model is a Python class that defines the structure of a database table and represents a data entity in your application.
- Generally, each model maps to a single database table (except for abstract models that are covered below).
- Each object (or instance) of a model class represents a record (or row) in the corresponding database table.
- Each attribute of the model class corresponds to a column/field.
- Models can also include methods and optional metadata.
- Django's ORM allows you to perform CRUD operations (create, read, update, delete) without writing raw SQL.
Every Django model inherits from the base class django.db.models.Model. The following example defines a Person model:
from django.db.models import Model, CharField, DateTimeField
class Person(Model):
first_name = CharField(max_length=30)
last_name = CharField(max_length=30)
created_at = DateTimeField(auto_now_add=True)
updated_at = DateTimeField(auto_now=True)The above Person model would create a database table like this:
CREATE TABLE myapp_person (
"id" bigint NOT NULL PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
"first_name" varchar(30) NOT NULL,
"last_name" varchar(30) NOT NULL,
"created_at" timestamp with time zone NOT NULL,
"updated_at" timestamp with time zone NOT NULL
);Key points about this model:
- Django automatically adds an
idprimary key column. - The
first_nameandlast_namefields store the person's name. - The CREATE TABLE SQL is shown in PostgreSQL syntax, but Django generates SQL specific to the database backend configured in your
settings.pyfile. - The timestamp fields track when a record is created or updated:
auto_now_add: Automatically sets the field to the current timestamp when the record is first created; it does not change on updates.auto_now: Automatically updates the field to the current timestamp every time the record is saved; useful for "last modified" tracking.
Using models
After defining the models, you must register them with Django. To do so, update the INSTALLED_APPS setting in the settings file, to include the name of the module that contains your models.py.
For example, if your application is named myapp, it should look like this:
INSTALLED_APPS = [
# ...
"myapp",
# ...
]Meta Options
You can define the metadata of your model using the Meta inner class. Metadata is "anything that’s not a field", such as ordering options, database table names, or human-readable singular and plural names. It is completely optional.
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=200)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ['-created_at']
verbose_name = 'Blog Post'
verbose_name_plural = 'Blog Posts'In the above example, ordering ensures posts are returned newest first. verbose_name and verbose_name_plural define human-readable names used in places like the Django admin.
Here are the most commonly used Meta options:
NOTE
If verbose_name is not provided, Django automatically generates one from the model’s class name: CamelCase is converted to camel case, and an s is appended for the plural form.
WARNING
Ordering is not a free operation. Each field added to ordering increases database query cost. Additionally, ordering on a foreign key implicitly includes that related model’s default ordering, which can further impact performance.
| Option | Description |
|---|---|
verbose_name | A human-readable singular name for the model, used in the Django admin and other display contexts. |
verbose_name_plural | A human-readable plural name for the model. |
app_label | Explicitly sets the application label for the model, useful when the model is defined outside its app. |
ordering | Specifies the default ordering for querysets returned by the model. Use a leading - for descending order. |
get_latest_by | Specifies the field(s) used by Model.objects.latest() and earliest(). |
db_table | Sets a custom database table name instead of Django’s default naming convention. |
unique_together | Enforces multi-field uniqueness (recommended to use UniqueConstraint with constraints option, as it provides more functionality). |
constraints | Defines database-level constraints such as uniqueness or conditional constraints. Refer this doc for full list of supported constraints. |
abstract | If set to True, the model becomes abstract and no database table is created. |
proxy | If True, creates a proxy model that uses the same database table as the original model. |
managed | If False, Django will not create, modify, or delete the database table for this model. Useful when mapping to pre-existing tables or database views. Defaults to True. |
For an exhaustive and up-to-date list of all available options, refer to this official documentation.
Model Classes and Instances
A model class, or simply model, is a Python class that subclasses django.db.models.Model. It serves as the schema and behavior definition for a database table. A model instance, on the other hand, represents one row in the database table defined by the model class.
# This is a model class
class Post(models.Model):
title = models.CharField(max_length=200)
published_at = models.DateTimeField()
# This is a model instance
post = Post(title="Hello Django", published_at=timezone.now())
post.save()Managers
A manager is the interface through which Django performs database queries for a model. Every model has at least one manager, and it is used to retrieve model instances from the database.
By default, Django adds a manager named objects to every model.
Person.objects.all()
Person.objects.filter(last_name="Doe")In the above example:
Personis the model class.objectsis the manager.- The methods (
all(),filter()) return QuerySets (covered later) of model instances.
Managers are intended to handle table-wide operations, such as:
- Fetching multiple rows from the database.
- Applying common filters.
- Encapsulating query logic that does not belong to a single instance.
Custom Managers
You can define a custom manager by sub-classing django.db.models.Manager. This is useful when you want to add reusable, high-level query logic.
from django.db import models
class PublishedManager(models.Manager):
def published(self):
return self.filter(is_published=True)
class Post(models.Model):
title = models.CharField(max_length=200)
is_published = models.BooleanField(default=False)
objects = models.Manager() # default manager
published_posts = PublishedManager()Model Attributes
Django exposes useful attributes at both the model class level and the model instance level. These attributes help you interact with the ORM and inspect object state.
Model Class Attributes
These attributes are accessed on the model class itself, not on individual instances.
objects
As explained above, objects is the default manager attached to the model class. You can override it with a custom manager as shown below:
from django.db import models
class PublishedManager(models.Manager):
def published(self):
return self.filter(is_published=True)
class Post(models.Model):
title = models.CharField(max_length=200)
is_published = models.BooleanField(default=False)
objects = PublishedManager() # overrides the default managerModel Instance Attributes
These attributes are available on individual model instances.
pk
pk refers to the instance’s primary key, regardless of the actual field name.
id vs pk
In Django, id refers to the specific integer field created automatically when you do not define a custom primary key. In contrast, pk is a property on the model instance, that always points to the primary key, regardless of its actual field name (such as user_id or email). If you use the default setup, they both behave identically.
Using .pk is generally recommended because it improves code stability. If you later rename the primary key field or switch to UUIDs, your existing queries and logic will remain functional without requiring updates.
_state
_state is an internal attribute Django uses to track the lifecycle of the instance, such as whether it has been saved. It’s mainly used internally to decide whether the model needs an INSERT or an UPDATE when .save() is called.
Field attributes
You can access individual fields defined on the model, as an attribute of model instance.
person.first_name
person.last_nameModel Methods
Django models support two broad categories of methods: built-in methods provided by Django and custom methods defined by you.
Built-in Methods
Django provides several built-in model methods that define how model instances behave. Some of these are commonly overridden to customize behavior.
__str__()
Returns a human-readable string representation of the model instance. Django uses this in the admin, shell, templates, and error messages. Overriding __str__() is strongly recommended, as the default representation is rarely useful.
__eq__()
Defines how two model instances are compared for equality. For Django models, two instances are considered equal if:
- They are instances of the same model.
- They have the same primary key.
- The primary key is not
None.
If either instance has no primary key (i.e., it hasn’t been saved yet), the comparison falls back to object identity, meaning two unsaved instances are only equal if they are the same Python object.
__hash__()
Defines whether a model instance can be used in hash-based collections such as sets or as dictionary keys. Django’s behavior is:
- Model instances with a primary key are hashable.
- Model instances without a primary key are not hashable.
get_absolute_url()
Returns the canonical URL for a model instance. A canonical URL is a single, preferred location used to uniquely and consistently identify an object.
Django uses this method in places like the admin and generic views when it needs a standard way to refer to a specific model instance. If a model represents something that users can view individually (such as a blog post, product, or profile), it should define get_absolute_url().
save()
Called when a model instance is saved to the database. Overriding this method allows you to run custom logic before or after saving (for example, modifying fields or triggering side effects).
When overriding save(), always call super().save(**kwargs) to ensure the instance is actually saved, and pass *args and **kwargs through your method to super() to maintain compatibility with Django’s API.
If you wish to update a field value in the save() method, you may also want to have this field added to the update_fields keyword argument. This will ensure the field is saved when update_fields is specified. For example:
class Blog(models.Model):
name = models.CharField(max_length=100)
name_upper = models.CharField(max_length=100)
def save(self, **kwargs):
self.name_upper = self.name.upper()
if "update_fields" in kwargs and "name" in kwargs["update_fields"]:
kwargs["update_fields"] = set(kwargs["update_fields"]) | {"name_upper"}
super().save(**kwargs)delete()
Called when a single model instance is deleted. Overriding this method allows custom cleanup or validation logic to run before deletion.
Similar to save(), when overriding delete(), always call super().delete(**kwargs) to ensure the instance is actually saved, and pass *args and **kwargs through your method to super() to maintain compatibility with Django’s API.
_is_pk_set()
_is_pk_set() is an internal Django model instance method that indicates whether the model instance’s pk is set. It’s mainly used internally to decide whether the model needs an INSERT or an UPDATE when .save() is called.
Note that _is_pk_set() returns True even if you manually assign a primary key to a new object. In such cases, Django relies on the instance’s _state attribute to decide whether to perform INSERT or UPDATE.
post = Post(id=42, title="Manual PK")
print(post._is_pk_set()) # True
print(post._state.adding) # TrueCustom Methods
You can define custom methods on a model to add custom “row-level” functionality to your objects. Unlike Manager methods, these model methods operate on individual model instances.
Custom methods are commonly used to:
- Compute derived or formatted values.
- Encapsulate domain logic.
- Provide convenience accessors.
They may be regular instance methods or exposed as attributes using @property.
class Post(models.Model):
title = models.CharField(max_length=200)
is_published = models.BooleanField(default=False)
def publish(self):
"""Mark the post as published."""
self.is_published = True
self.save()
@property
def status(self):
"""Return a human-readable publication status."""
return "Published" if self.is_published else "Draft"The last method in the above example is a property.
Organizing models in a package
As your project grows, keeping all models in a single models.py file can become unmanageable. Django allows you to split your models into separate files within a package.
To do this, replace your models.py file with a directory named models. Inside this directory, you must create a file named __init__.py and as many separate files as you need for your models. The directory structure looks like this:
myapp/
models/
__init__.py
users.py
products.pyThe __init__.py file makes the directory a Python package. Crucially, you must import your models into this file. If you do not import them here, Django will not "see" your models and will not create database tables for them. Example:
# __init__.py
from .users import User, Profile
from .products import Product, Category