The storage you work with is probably a file system. It works well for HTML pages and templates, but how do you keep small objects like login, age, or favorite color for each person? Relational databases can help you organize and handle such data.
Let's start from scratch and learn how to use only Python to work with databases.
Relational databases
If your first thought is I need to keep the data with a typical structure, then your second thought should surely be databases.
A relational database is a collection of multiple data sets organized by tables, records, and columns. It works fine for most types of data. Each implementation provides the universal language called Structured Query Language (SQL).
The most popular databases include PostgreSQL, Oracle SQL, MS SQL, and MySQL. There is also a simple database that works on your smartphone in many applications: SQLite. It's perfect for one-client use and trying out Django models for the first time. SQLite is included in the list of libraries for Django by default, so you probably already have it. Check whether you have it on your computer:
sqlite3 --versionIf you don't, install it with your package manager or download it from the official site.
Object-relational mapping
With summer almost over and clouds getting heavier, the new season of Quidditch is almost here. As you know, wizards lack computer science classes, even though programming is a kind of magic. They want to store the teams, their results, and the rosters on the website, and they wonder if there is a way to do it with Django. Well, there is! For this, let's create the quidditch project with the tournament app in it. Let's meet and greet Django Models!
Django Models are classes that map objects from the real world to the database records. We will work with databases using Python classes and methods. Our strong side is the programming language, and we will make the most of it. It will also make the code independent of the specific database used by our application (and defined in the settings.py file). The objects are similar to the database records, and their methods resemble SQL commands. There's no need to know SQL directly as we apply the instruments that imitate it. We have teams, so we call our model the Team.
To tell Django that it's a class that maps its structure to the database table, we inherit the Team from django.models.Model. Also, we have players and game tables. Suppose we have the quidditch project with the tournament application inside. Let's make the stubs for our classes in the tournament/models.py module:
from django.db import models
class Team(models.Model):
name = ...
class Player(models.Model):
height= ...
name = ...
team = ...
class Game(models.Model):
date = ...
home_team = ...
home_team_points = ...
rival_team = ...
rival_team_points = ...We gave names to our classes and described their content. The restriction of all relational databases is that we should define the types for all the fields in the model. So how can we match the types with the fields?
Fields
To get most of the database's features, we use the Fields classes. They map the class attribute to a particular column in the database table. Does it mean we need the instance of a class for each field? Yes, but don't worry; it's easier than it may seem.
To build the whole schema, we start from the core element, Team:
class Team(models.Model):
name = models.CharField(max_length=64)CharField is similar to Python string but has one restriction: the length limit. Wigtown Wanderers is the longest team name in the league now, but the league is still open to new teams, so we ensure max_length with 64 symbols.
Each team has players. Let's define a model for a player:
class Player(models.Model):
height = models.FloatField()
name = models.CharField(max_length=64)
team = models.ForeignKey(Team, on_delete=models.CASCADE)We already know what the CharField means, so FloatField should sound familiar to you, too. It's the same as Python's float type. What's more interesting is the ForeignKey field. It means that the player is bound to a specific Team and the on_delete=models.CASCADE restriction means that if the team is deleted from the database, it will be erased with all the players. That sounds unfair, but you should try harder to stay in the league!
There are other deleting strategies besides CASCADE: PROTECT, RESTRICT, SET_NULL, SET_DEFAULT, SET(), and DO_NOTHING. Feel free to read more about them on the corresponding documentation page.
Finally, there's the Game model:
class Game(models.Model):
home_team = models.ForeignKey(Team, related_name='game_at_home', on_delete=models.CASCADE)
home_team_points = models.IntegerField()
rival_team = models.ForeignKey(Team, related_name='rival_game', on_delete=models.CASCADE)
rival_team_points = models.IntegerField()
date = models.DateField()There are no games without teams, so again, we set on_delete=models.CASCADE for each ForeignKey. Also, we add the related_name for the Game model, by which we can access it from the Team model, in other words, follow the relationship backward. If related_name wasn't defined, the special game_set field will show you the games in which a team participated; more can be found in Django's documentation.
Points is an int type, so we make it IntegerField, and the date is a DateField.
You can think of Fields as expansions of Python's primitive types for simple cases like IntegerField, CharField, and FloatField. They also have special cases like ForeignKey and other relations between objects.
Migrations
We've described the mappings between Python classes and database tables, but we don't have any tables. Let's correct this. Add tournament to INSTALLED_APPS in the quidditch/settings.py module:
INSTALLED_APPS = [
# other installed apps
'tournament',
]We have the league layout in our code; we are ready to migrate it to the database. It takes two steps:
python manage.py makemigrations
python manage.py migrateThe first command creates migrations. A migration is a piece of code that describes what actions should be done in the database to synchronize the models with the tables. You can find the created code in the tournament/migrations/0001_initial.py file. So, don't forget to check whether the migrations directory exists; otherwise, migration will not be completed.
In the second step, we apply the changes and run the generated commands. Preceding manage.py <command> with python is the platform-independent way to launch any Django command. It's a valid syntax for both Unix and Windows systems.
In PyCharm IDE, you can open the terminal tab and add the commands. If you want to make and then apply migrations to a particular application in your project, add the application name after each command:
python manage.py makemigrations tournament
python manage.py migrate tournamentWhen you run these commands, your database will finally have the tables to work with.
It's important to note that when you run the makemigrations command in Django, it generates a set of migration files based on the changes you have made to your models. However, it's crucial to ensure the accuracy of your migrations before applying them to the database.
To mitigate any risks, you can use the --dry-run option as an optional argument with the makemigrations command. This allows you to perform a dry run of the migration process without actually creating or modifying any tables in the database. Instead, it prints the potential migration SQL commands that would be executed. By reviewing these commands, you can verify the correctness of your migrations before applying them to the database.
To perform a dry run of migrations, you can use the following command:
python manage.py makemigrations --dry-runThe --dry-run option allows you to preview the changes without applying them to the database. This can help test migrations and verify that the changes run as expected.
Conclusion
Now you are familiar with models and ORM. There are many types of fields in Django. You can read about the different fields in the documentation. Try and create your models!