ORM¶
Theory¶
ORM stands for Object - Relational Mapping (object relational mapping). A model is a Python object that abstracts database relationships. Django models provide an interface for reading and manipulating data from a database. Thanks to them, we do not have to build raw SQL queries in strings, we have objects representing entries in tables, we do not have to worry about the SQL dialect and the specificity of the database we use (ORM takes responsibility for this) and we do not have to worry about securing user data entered into queries (using ORM we are automatically protected against SQL-injection attacks).
Model class¶
Database relationships represent classes that inherit from Model
. The columns are represented by objects with the suffix Field
(e.g. CharField
). We place the models in the models.py
file.
from django.db.models import CharField, Model
class Genre(Model):
name = CharField(max_length=128)
Migrations¶
During the launch of the project, we saw the following inscription:
Okay, red message ... it would be worth reading ...
To get rid of it, run the manage.py script with the migrate
argument.
The above migrations were migrations of the part models built into Django. There were no tables in the database, so they had to be used. After their application, the database creates tables similar to the models built into Django.
Migrations describe changes taking place in models, so we started to need them in our project as well, because we added the model. The manage.py script when run with the makemigrations
argument will create migration files for the models in our project.
The created migrations (files from the migrations folder) are stored in the code repository. We do not store the database in the repository, so if we want to refresh it we have to do python manage.py migrate
even after checkout.
Shell¶
For "quick access" to the application from the developer side, Django provides the shell
command: python manage.py
shell
This command is best used primarily for interacting with models. With it, you can, for example, test prepared queries:
We now have access to a Python shell with the models loaded.
Model instances¶
Let's import the model:
from viewer.models import Genre
Let's check the model entries in the database:
Genre.objects.all()
The base is obviously empty. Let's add the first entries:
Genre.objects.create(name='Horror')
lub:
genre = Genre(name='Thriller')
genre.save()
Now let's check the contents of the table again:
Genre.objects.all()
Let's take the horror genre entry from the database:
horror = Genre.objects.get(name='Horror')
Let's check if it's a horror movie:
horror.name
Each entry in the database also has a primary id:
horror.id
Model fields¶
Django's ORM has many classes that represent columns of database relationships. These fields can be found in the django.db.models
module and are classes with the suffix Field
.
from django.db.models import (
DO_NOTHING, CharField, DateField, DateTimeField, ForeignKey, IntegerField,
Model, TextField
)
class Movie(Model):
title = CharField(max_length=128)
genre = ForeignKey(Genre, on_delete=DO_NOTHING)
rating = IntegerField()
released = DateField()
description = TextField()
created = DateTimeField(auto_now_add=True)
CharField
is a column typeCHAR
; as in SQL, we must specify its sizeTextField
is a column of the typeVARCHAR
orTEXT
(ORM will use the appropriate type depending on the database used, we don't have to worry about it)ForeignKey
it represents a foreign key (the argument describes the table to which the key should refer); the convention is not followed because it is not a regular column - in the database it is a column of the primary key type of the second table and a foreign key constraint is createdIntegerField
is a column typeINT
DateField
is a column of the typeDATE
(similarly with the columnDATETIME
orTIMESTAMP
)
After deploying changes to the models, we should again properly handle the migration flow:
python manage.py
makemigrations
python manage.py
migrate
Administrator panel¶
Django generates an admin panel by default. We have already seen the url registered at urls.py :
from django.contrib import admin
from django.urls import path
from viewer.views import hello
urlpatterns = [
path('admin/', admin.site.urls),
path('hello/<s>', hello)
]
Let's visit the address http://localhost:8000/admin/. Let's create an administrator from the command line:
python manage.py
createsuperuser
Now let's try to log in to the admin panel using the login details of the newly created user. Let's register our models in the admin panel:
from django.contrib import admin
from django.urls import path
from viewer.models import Genre, Movie
from viewer.views import hello
admin.site.register(Genre)
admin.site.register(Movie)
urlpatterns = [
path('admin/', admin.site.urls),
path('hello/<s>', hello)
]
After registration, the models should appear in the admin panel, from where we can view, add, modify and delete their instances. The changes made will be immediately entered into the database.
DUMP/LOAD¶
Data can be exported from the database:
python manage.py
dumpdata viewer --output fixtures.json
After exporting, the data appears in the fixtures.json
JSON file.
Data can be imported into the database:
python manage.py
loaddata fixtures.json
Of course, we should clean the database before loading the data. Otherwise we see the unique value violation error.
The exported data is called fixtures. Fixture is a good idea to use for automated tests or to manually test the application in our local development environments.
Queries¶
Thanks to the models, we can construct safe queries to the database. Here are some Python equivalents of the most popular SQL operators:
WHERE
→.filter
.filter
returns multiple records- The usual comparison - by kwargsa:
Movie.objects.filter(
rating
=8) # films rated 8
horror = Genre.objects.get(
name
='Horror')
Movie.objects.filter(
genre
=horror) # horrors
- Comparison of nested fields - the so-called „lookup”:
Movie.objects.filter(
genre__name
='Horror') # horrors (second option)
- Other comparisons - also „lookup”
Movie.objects.filter(
rating__gt
=8) # rates greater than 8
Movie.objects.filter(
title__contains
='Godfather') # all parts of the Godfather (i.e. SQL LIKE; similarly `__icontains` is ILIKE)
- NOTE: In SQLite, LIKE works like ILIKE in other databases, so we won't notice differences between
__contains
and__icontains
!
- NOTE: In SQLite, LIKE works like ILIKE in other databases, so we won't notice differences between
Movie.objects.filter(
title__in
=['Se7en', 'Fight Club']) # „Se7en” i „Fight Club”
.filter
can be "chained"Movie.objects
.filter(title__icontains=’godfather’) .filter(released__year__gt=1973)
# parts of "The Godfather" published after 1973
WHERE NOT
→.exclude
- It works exactly like
.filter
, but returns records not matching the lookup
- It works exactly like
- Existence test:
Movie.objects.filter(title='Avatar')
.exists()
# is there a movie called "Avatar"
COUNT
→.count
Movie.objects.filter(title__contains=’Godfather’)
.count()
# how many parts of the "Godfather"
ORDER BY
→.order_by
:Movie.objects.filter(title__contains=’Godfather’)
.order_by('released')
# "The Godfather" parts sorted by oldest
Movie.objects.filter(title__contains=’Godfather’)
.order_by('-released')
# parts of "The Godfather" sorted by the newest
Data manipulation¶
We can manipulate data similarly:
CREATE
→.create
Genre.objects
.create(name='Documentary')
# no access to the object before write
- creation of an object +
.save
genre =
Genre(name='Comedy')
genre
.save()
UPDATE
→.update
- Updates the query results
Movie.objects.filter(released__year=2000)
.update(rating=5)
# sets movie ratings from year 2000 to 5
- changing the value of a field +
.save
pulp_fiction = Movie.objects
.get(title='Pulp Fiction')
pulp_fiction
.rating =
7
pulp_fiction
.save()
# here is the entry into the database
DELETE
→.delete
Movie.objects.filter(title__contains='Godfather')
.delete()
# remove all parts of the "Godfather" from the base