0% completed
Introduction to Django Models and Migrations
On This Page
Defining a Model
From Model to Table: makemigrations and migrate
Checking Which Migrations Are Applied
Adding a Field
Renaming a Field
Removing a Field and Undoing a Migration
Key Takeaways
A blog has to remember its articles after the server restarts, so every article needs a row in a database table. You could write the SQL for that table by hand, but Django lets you describe the table as a Python class instead. That class is called a model, and this chapter is about models: how to define them, how Django turns them into tables, how to read and write their rows with Python, and how to connect one model to another.
This lesson covers the first two steps. You define a model for the blog's articles, and then you use migrations, which are Django's record of every change to your tables, to create the table and to change it later.
Defining a Model
A model is a Python class that inherits from models.Model, and it lives in the models.py file of an app. Each class attribute that is a field becomes one column of the table. The examples in this chapter use an app called blog, so the model goes in blog/models.py:
# blog/models.py from django.db import models from django.utils import timezone class Article(models.Model): title = models.CharField(max_length=200) content = models.TextField() author = models.CharField(max_length=100) published_date = models.DateTimeField(default=timezone.now) def __str__(self): return self.title
The field class decides what kind of data the column holds and how Django checks it. A CharField is short text, and it requires max_length, which becomes the size of the column. A TextField is text of any length, which suits the body of an article. A DateTimeField stores a date and a time.
The default=timezone.now argument passes the function itself, without parentheses. Django calls it each time it creates an article, so a new article gets the current date and time, but you can still set a different publication date when you need to. (If you wrote timezone.now() with parentheses, every article would get the moment the server started.)
The __str__ method is not a field. It tells Django how to show an article as text, so the shell and the admin site print the title instead of Article object (1).
Other common field types are IntegerField and PositiveIntegerField for whole numbers, DecimalField for exact amounts such as prices, BooleanField for true or false values, DateField for a date without a time, and EmailField for email addresses. The ForeignKey field links one model to another, and the third lesson of this chapter covers it.
You did not declare a primary key, but the table still gets one. Django adds a field called id to every model that does not define its own primary key. Its type is BigAutoField, a 64-bit integer that the database increases by one for each new row.
The table also gets a name that you did not choose. Django names it <app label>_<model name in lowercase>, so the Article model in the blog app is stored in a table called blog_article. You can check the name in the shell with Article._meta.db_table.
From Model to Table: makemigrations and migrate
Writing the class does not create anything in the database. Django changes the database in two separate steps, and each step has its own command.
The first step writes a migration file. Run this command from the folder that contains manage.py:
python manage.py makemigrations
Migrations for 'blog': blog/migrations/0001_initial.py + Create model Article
Django compared your models with the migration files that already exist, found a new model, and wrote a new file in blog/migrations/. The file name starts with a number, so the files of an app always run in order. At this point the database has not changed at all.
The migration file is ordinary Python. It describes the change as a list of operations, not as SQL:
# blog/migrations/0001_initial.py (generated, header comment removed) import django.utils.timezone from django.db import migrations, models class Migration(migrations.Migration): initial = True dependencies = [ ] operations = [ migrations.CreateModel( name='Article', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('title', models.CharField(max_length=200)), ('content', models.TextField()), ('author', models.CharField(max_length=100)), ('published_date', models.DateTimeField(default=django.utils.timezone.now)), ], ), ]
Because the file describes operations rather than SQL, the same migration works on SQLite, PostgreSQL, MySQL or any other database that Django supports. Django produces the SQL for your database only when it applies the migration. You can see that SQL without running it:
python manage.py sqlmigrate blog 0001
BEGIN; -- -- Create model Article -- CREATE TABLE "blog_article" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "title" varchar(200) NOT NULL, "content" text NOT NULL, "author" varchar(100) NOT NULL, "published_date" datetime NOT NULL); COMMIT;
This is the SQL for SQLite, the database a new project uses. Notice that the default for published_date does not appear in the SQL. Django fills in that value in Python when it saves an article, not in the database.
The second step applies the migration to the database:
python manage.py migrate
Operations to perform: Apply all migrations: accounts, admin, auth, blog, contenttypes, sessions Running migrations: Applying blog.0001_initial... OK
The migrate command runs every migration that has not been applied yet, in dependency order. The first line lists every app that has migrations, but only one migration ran. The project's first migrate, in Chapter 1, already applied the migrations of the apps that Django installs for you, such as auth for permissions and sessions for logged-in visitors, together with the accounts app and its user model. Only the new blog.0001_initial was left to apply.
Django also records each applied migration in a table called django_migrations. The next time you run migrate, it reads that table and skips everything already done.
The picture shows why there are two commands. makemigrations only reads your models and writes a file, while migrate is the only step that touches the database. The migration files belong in version control next to your models, so everyone on the team and every server applies exactly the same changes in the same order.
Checking Which Migrations Are Applied
The showmigrations command lists the migrations of each app and marks the applied ones with [X]:
python manage.py showmigrations blog
blog [X] 0001_initial
A migration shown as [ ] exists as a file but has not been applied to this database yet. That is the normal state right after makemigrations and before migrate.
Adding a Field
Suppose the blog should count how many times each article is read. You add a field to the class:
# blog/models.py class Article(models.Model): title = models.CharField(max_length=200) content = models.TextField() author = models.CharField(max_length=100) published_date = models.DateTimeField(default=timezone.now) views = models.PositiveIntegerField(default=0) def __str__(self): return self.title
Then you run the same two commands:
python manage.py makemigrations python manage.py migrate
Migrations for 'blog': blog/migrations/0002_article_views.py + Add field views to article
The new migration is numbered 0002, and Django names it after the change it found. When migrate applies it, every article already in the table gets 0 in the new column, because that is the default.
The default matters because the table may already hold rows, and each of those rows needs a value in the new column. If you add a field such as summary = models.CharField(max_length=300) with no default, makemigrations stops and asks what to put in the existing rows:
It is impossible to add a non-nullable field 'summary' to article without specifying a default. This is because the database needs something to populate existing rows. Please select a fix: 1) Provide a one-off default now (will be set on all existing rows with a null value for this column) 2) Quit and manually define a default value in models.py. Select an option:
You can type a value for the existing rows, or quit and add a default to the field in models.py.
A change to an ordinary method needs no migration. Adding or editing the __str__ method, for example, changes only Python code, so makemigrations answers No changes detected. A change to a field's options is different, even when the column stays the same. Adding help_text="Number of reads" to a field, for example, makes makemigrations write an AlterField migration, because Django records every field option in the migration history.
Renaming a Field
Now suppose the team prefers the name view_count to views. You change the name in the class:
# blog/models.py view_count = models.PositiveIntegerField(default=0)
Django cannot be sure from the code alone whether you renamed the field or deleted one field and added another. When the old and the new field have the same type, makemigrations asks you:
Was article.views renamed to article.view_count (a PositiveIntegerField)? [y/N]
If you answer y, Django writes 0003_rename_views_article_view_count.py, and migrate renames the column, which keeps the data:
ALTER TABLE "blog_article" RENAME COLUMN "views" TO "view_count";
If you answer N, or press Enter, Django writes a migration that removes the views column and adds an empty view_count column. Every stored view count is lost when that migration runs. Read the question carefully, and read the summary that makemigrations prints before you run migrate.
Removing a Field and Undoing a Migration
Removing a field works the same way. Delete the view_count line from the class and run makemigrations. Django writes 0004_remove_article_view_count.py with a RemoveField operation, and when migrate applies it, the column is dropped together with all the values it held:
ALTER TABLE "blog_article" DROP COLUMN "view_count";
Migrations can also run backward. To return the blog app to the state it had after an earlier migration, name that migration:
python manage.py migrate blog 0003
Operations to perform: Target specific migration: 0003_rename_views_article_view_count, from blog Running migrations: Rendering model states... DONE Unapplying blog.0004_remove_article_view_count... OK
Undoing a migration changes the structure of the table back, but it does not bring back data that the migration deleted. The view_count column returns, and every article has 0 in it, which is the field's default. Treat a migration that removes a field as permanent once it has run on a database with real data.
Unapplying a migration does not edit your code. To finish the undo, put the view_count line back in the class and delete the file 0004_remove_article_view_count.py. After that, makemigrations reports No changes detected, because the models and the applied migrations agree again.
The rest of this chapter uses the model with the view_count field, as it was after the rename:
# blog/models.py from django.db import models from django.utils import timezone class Article(models.Model): title = models.CharField(max_length=200) content = models.TextField() author = models.CharField(max_length=100) published_date = models.DateTimeField(default=timezone.now) view_count = models.PositiveIntegerField(default=0) def __str__(self): return self.title
Key Takeaways
- A model is a class in
models.py. Each field is a column, Django adds anidprimary key, and the table is named<app label>_<model name>, such asblog_article. makemigrationswrites a migration file and never touches the database.migrateapplies the files and records them indjango_migrations.- A new field needs a default, or a decision about existing rows, because those rows need a value.
- Answer the rename question with
yto keep data. A removed field loses its data, and undoing the migration does not restore it. - Keep migration files in version control with the models they describe.
Reading Progress
0%
On This Page
Defining a Model
From Model to Table: makemigrations and migrate
Checking Which Migrations Are Applied
Adding a Field
Renaming a Field
Removing a Field and Undoing a Migration
Key Takeaways