How Django Models Work - A Beginner's Guide
The database is the core of any Django application. However, the language of databases (well... most of them) is SQL, not Python.
Something that makes Django very powerful is it allows you to build very complex data-driven web applications without having to write a line of SQL. To do that, Django needs a way of converting the developer's Python code into SQL commands that the database will understand.
We need models. This post will go through how Django models work, so you can start coding data-driven apps.
What are models?
Models can be thought of as a blueprint that sets out what a table of the database should look like.
Developers use models to define the database structure. Django uses models to interface with the database.
A developer should be able to look at the model class and answer these questions:
What columns does the table have?
What kind of data (string, integer, float, boolean etc...) goes in each column?
What data is valid input? (e.g. length limits on strings)
Do any of the columns allow blank values?
If a user doesn't input a value, is there a default?
There will be one model for each table. It is the model that defines the name of each column and the rules about the data that can go inside them.
Why do we need models?
It is possible to build web-apps without models. Developers can technically write their own SQL to create and manage tables in the database.
However, the web-app will have no record of the database structure, the names of each table and column and the rules about each column. This makes it harder to keep track of the database structure, creating more work for the developer.
Models provide an easy interface to Create, Read, Update and Delete (CRUD) records from the database.
What can models do?
The basic use-cases for models are:
- Create, Read, Update and Delete records from the database
- Define the structure of the database table.
- Impose rules on what kind of data can be stored in a particular column e.g. positive integers only, strings up to 255 characters long.
- Set default values
- Define relationships between tables e.g. you have a table of blog posts and a table of authors, you can link the tables to get the blog post’s author.
- Define useful methods. For example, the URL of a blog post can be calculated from the post’s slug, so it’s pointless to store the URL in the database. Just set a method
get_url()
instead. I've written a separate post on the different ways adding methods can enhance your classes, which you can read here.
Where are models stored?
Models are typically stored in a file called models.py
. A model will always be a class that inherits from django.db.models.Model
.
A mistake I made when learning Django was thinking that models had to exist inside models.py
for Django to pick them up. They can go into any file you like, as long as they inherit from models.Model
and the file exists inside an app that has been registered in settings.py
.
How do Django models work?
Models work by inheriting the functionality of Django's base model in django.db.models
.
This class contains lots of useful methods that will apply your schema to the database. Therefore, it’s critical your model classes inherit Model
, so they can also access these methods.
Django can then detect your model by calling Model.__subclasses__()
behind the scenes which returns all classes that inherit from it.
Columns
django.db.models
provides a number of field types. Every column will have a field type.Columns are defined by setting these fields as attributes of the class.
Django’s field types will allow you to set rules. In the example below, the title is limited to 250 characters. The
publish_date
field has a default applied. If the user does not provide a value, then it will use the current date and time.
Migrations
A migration is a file that will perform operations on the database
When an update to your model means the structure of the database will need to change, you will need to run a migration.
python manage.py makemigrations
is a command that detects changes in your models and writes the migration files for you.python manage.py migrate
is the command to run those migrations. They only need to be run once.Typically, changes to model attributes will result in the need for a new migration but changes to model methods won’t.
Migrations can be confusing when you're a beginner and can also throw some weird errors. I believe understanding how migrations work is the best way to prevent and deal with these errors. You can read my guide here.
How to use models
1. Create your table in the database
Create your model in models.py
. Remember to:
- Define your model as a class inheriting from
django.db.models.model
- Set fields as attributes of the class
2. Register the model in Admin
Go to admin.py
(it will be in the same folder as models.py
and register the model)
3. Define the string representation (optional)
Assuming your migrations ran successfully, you can go into localhost:8000/admin
and view the model. You may need to create a superuser and log in to do that.
Post object(1): not very helpful
To create the image above, I created a few posts in Admin. Post object(1)
isn’t particularly insightful. It would be nicer if we could tell the posts apart.
The str dunder method can be added to the model to define how instances of models are represented in string format.
class Blog(models.Model):
...
def __str__(self):
return f"{self.title}, by {self.author.username}"
As soon as you set the __str__
method, you can refresh admin and see the result in admin:
Each row has the post title and the author, taken from the __str__ method defined on the model... much better.
4. Define ordering (optional)
By default, rows in the database are ordered by their primary key (ID) but you can override this.
For example, you can change to ordering to their published date in descending order by defining the Meta
sub-class. (If you’re not familiar with tuples, don’t forget the command after "-published"
.)
class Post(models.Model):
...
class Meta:
ordering = ("-published",)
Define Properties / Methods (optional)
If there are attributes that can be derived from other attributes, then it would be inefficient to store them both on the model.
Instead, you can define a method with the @property
decorator to define the other attribute.
One use-case is defining the absolute url of a blog post:
class Post(models.Model):
...
@property
def absolute_url(self):
return f"<https://myurl.com/posts{self.slug}>"
Afterwards (you won’t need a migration, the value is computed as required), you can instantiate a post and calling post.absolute_url
with return the URL.
The following will also work (using post.get_absolute_url()
, properties don’t need parentheses but methods do).
class Post(models.Model):
...
def get_absolute_url(self):
return f"<https://myurl.com/posts{self.slug}>"
Troubleshooting
No Changes Detected
You run python manage.py makemigrations
having added a model but Django responds with “No Changes Detected”.
Possible Solution: Go to settings.py
and make sure the name of your app is included in INSTALLED_APPS
Possible Solution: Check your model class inherits from models.Model
(imported from django.db
)
You’ve ran the migration but there are attributes that don’t appear as columns in the database.
Only attributes that use a field from the models module will become columns e.g. models.CharField
, models.ForeignKey
See Django’s model field reference for the full list of allowable fields.
Model is Missing from Admin
You created your model, the migration ran successfully but you can’t see the model in admin localhost:8000/admin
.
Solution:
Go to admin.py
(it will be in the same folder as models.py
and register the model
from django.contrib import admin
from . import models
# Register your models here.
admin.site.register(models.YourModelName)
Conclusion
This post has covered how Django models work. We have covered:
Declaring models by defining a class that inherits from
django.db.models.Model
Defining columns of a table in your database by setting Fields as attributes of the class
Syncing your model with the database by creating and running migrations
Registering the model, so it appears in
/admin