The world changes over time, and so does your data. When you have a new object to store, you create it in a database for further use; if you don't need it anymore, you delete it. When the object's properties change, you make an update. These three actions along with reading are known as CRUD (Create, Read, Update, Delete) operations.
Even though Django methods use almost identical naming, let's look at them closer and learn how to use them right.
Create
Learning is hard, so we decide to take a break and make a truly entertaining computer game for ourselves. The galaxy is in danger, and our brave space team must prevent the alien invasion. The main weapons of our team are diplomacy and science. This time, we need only two models to start the game:
from django.db import models
class Alien(models.Model):
db_column = models.CharField(max_length=32)
distance_to_galaxy = models.IntegerField()
threat = models.IntegerField()
speed = models.IntegerField()
class Weapon(models.Model):
db_column = models.CharField(max_length=32)
quantity = models.IntegerField()
power = models.IntegerField()
coverage_distance = models.IntegerField()First, we should create some weapons for our heroes. Let's equip them with eloquence to convince aliens to go away and jammers to modify the scouting signals:
eloquence = Weapon.objects.create(
type='eloquence', power=100, coverage_distance=100, quantity=10
)
jammers = Weapon(type='jammer', power=10, coverage_distance=1000, quantity=50)
jammers.save()Two methods illustrate how you can create new objects in a database. We create eloquence with the help of Object Manager. It has the method create where you can pass all the parameters your object has. If the result of this operation is successful, by calling it, you save the object to the database and return the instance of the Weapon class.
The second method is to create an instance of a class and then save it manually. The two methods are pretty much the same, so you can use whichever you like most.
When you create an instance manually, the object will not be saved to a database until you call the save method explicitly.
Delete
The alien invasion is coming. Meanwhile, we hope that our game will attract gamers and won't be boring. For each session, we'll create hundreds or even thousands of aliens to make the battle hard. If we don't clear the database of all the defeated aliens, operations will become slower, and we will eventually run out of disk space.
The first enemy comes from a nearby galaxy, located only 23 solar years from ours:
et_alien = Alien.objects.create(type='ET', distance_to_galaxy=23, threat=70, speed=5)In five moves, it crosses the border, so the player can apply eloquence to deceive the opponent and make it back up. The power of eloquence is 100 and the threat is 70, so in one move the player can resist the first invasion!
We do not need the et_alien anymore, so let's delete it:
et_alien.delete()That's simple. We call the delete method of an instance of the Alien class, and the object is deleted from the database.
The delete method removes the object from a database but does not delete an instance of a class. Do not use the object after this operation to prevent ambiguity.
The game continues…
Update
We have studied two main powers of modification: create and delete. The third power is to change an existing object. When the properties of an object change, we should update them in the database.
The next enemy of the galaxy is the Predator, an opponent that can hardly be defeated in a single move since it comes from the deep space, and our weapons are not strong enough.
predator = Alien.objects.create(type='Predator', distance_to_galaxy=550, threat=40, speed=30)Our jammers create obstacles for signals in space, so the enemy loses direction. The player applies this weapon on the next move. The number of jammers decreased by one, and simultaneously the threat is diminished:
jammers.quantity -= 1
jammers.save()
predator.distance_to_galaxy -= predator.speed
predator.threat -= jammers.power
predator.save()Updating an object is a two-step operation. We change the attributes of an object and call the save method as for manual creation of an object.
In case of error, we used change only in one place. To avoid this, the best practice is to use atomic as a decorator:
from django.db import IntegrityError, transaction
@transaction.atomic
def changes():
jammers.quantity -= 1
jammers.save()
predator.distance_to_galaxy -= predator.speed
predator.threat -= jammers.power
predator.save()If the nested block succeeds, the changes it made to the database can be undone if an error occurs in the outer code block.
The instance attributes are saved only in a Python object until you call the save method. Your object is not an exact replica of a database row in a current moment, so remember to save it to synchronize the changes you made.
In three more moves, the player defeats the Predator with jammers, and it flies away in an unknown direction. We call predator.delete() and go forward to the next round.
Modification of QuerySet
We can modify each object as we like, but remember — aliens may come in a swarm. Can we apply our weapons to all of them simultaneously? With the approach we've just considered, the answer would be no. If only we could use a QuerySet for this task… The good news is we surely can!
Space bugs come in a pack of three:
Alien.objects.bulk_create([
Alien(type='Space Bug', distance_to_galaxy=30, threat=150, speed=12) for _ in range(3)
])On the first move, the player applies eloquence:
eloquence.quantity -= 1
eloquence.save()
space_bug = Alien.objects.filter(type='Space Bug').first()
Alien.objects.filter(type='Space Bug').update(
distance_to_galaxy=space_bug.distance_to_galaxy - space_bug.speed,
threat=space_bug.threat - eloquence.power
)The bugs are not defeated yet, but we update their position and threat in one call, not three. We get an Object Manager of a model, filter out objects that we want, and call update on the QuerySet. The syntax is the same: we pass parameters and their new values to the method, and Django does the rest of the work with the database.
Finally, the player applies eloquence again and the bugs are convinced to stop their invasion. Again, we call the method on a QuerySet:
Alien.objects.filter(type='Space Bug').delete()All space bugs are now removed from the database.
Although the delete method works for both an object and a QuerySet, the update method is defined only for a QuerySet. If you call an update method on an object, you'll get an AttributeError exception.
We have only three rounds, so the game is over. This time, the player wins and saves our galaxy from all space creatures out there. No aliens were harmed during the making of this topic.
Other methods in Django ORM
Now that the galaxy is saved, we can return to our planet and discuss other ORM methods, this time in a more peaceful way. In addition to the well-known methods such as filter(), get(), update(), all(), and delete(), Django ORM provides a lot of other methods that allow us to exploit the power of SQL queries. Often we want to spend some quality time watching movies. That is also the reason we decided to illustrate these additional methods by creating the movie table below.
class Movie(models.Model):
title = models.CharField(max_length=100)
genre = models.CharField(max_length=20)
release_year = models.IntegerField()
director = models.ForeignKey(Director, on_delete=models.CASCADE)
def __str__(self):
return self.titleAs you may well have noticed, the movie table has a foreign key relationship with the director table, so let us define the Director model as well:
class Director(models.Model):
name = models.CharField(max_length=100)
email = models.EmailField(null=True, blank=True)
address = models.TextField()
def __str__(self):
return self.nameAt first, we would like to mention the values() and values_list() methods. These methods are quite similar. Both return Python objects instead of QuerySet objects. The major difference between them is that the first one will return dictionaries while the second one returns tuples:
movies_dict = Movie.objects.values()
print(movies_dict)
# <QuerySet [{'id': 1, 'title': 'Bonnie and Clyde', 'genre': 'action', 'release_year': 1967, 'director': 'Arthur Penn'}, {'id': 2, 'title': 'The Seven Samurai', 'genre': 'action', 'release_year': 1954, 'director': 'Akira Kurosawa'}, {'id': 3, 'title': 'Doctor Zhivago', 'genre': 'drama', 'release_year': 1965, 'director': 'David Lean'}, {'id': 4, 'title': 'Rocky', 'genre': 'adventure', 'release_year': 1976, 'director': 'John G. Avildsen'}, {'id': 5, 'title': 'Braveheart', 'genre': 'action', 'release_year': 1995, 'director': 'Mel Gibson'}]>
movies_tuple = Movie.objects.values_list()
print(movies_tuple)
# <QuerySet [(1, 'Bonnie and Clyde', 'action', 1967, 'Arthur Penn'), (2, 'The Seven Samurai', 'action', 1954, 'Akira Kurosawa'), (3, 'Doctor Zhivago', 'drama', 1965, 'David Lean'), (4, 'Rocky', 'adventure', 1976, 'John G. Avildsen'), (5, 'Braveheart', 'action', 1995, 'Mel Gibson')]>The order_by() method changes the default ordering of the QuerySet. By default, the order is based on the primary key (in our case, the id field). If we want our QuerySet to be ordered by release_year, the order_by method is exactly what we need:
ordered_movies = Movie.objects.order_by('release_year')
print(ordered_movies)
# <QuerySet [<Movie: The Seven Samurai>, <Movie: Doctor Zhivago>, <Movie: Bonnie and Clyde>, <Movie: Rocky>, <Movie: Braveheart>]>You also can sort the data in descending order by adding a minus (-) before the value:
descend_ordered_movies = Movie.objects.order_by('-release_year')
print(descend_ordered_movies)
# <QuerySet [<Movie: Braveheart>, <Movie: Rocky>, <Movie: Bonnie and Clyde>, <Movie: Doctor Zhivago>, <Movie: The Seven Samurai>]>Lastly, we want to mention the select_related() method that can be used to improve database performance by retrieving all related data without performing multiple database searches. As you remember, our movie table has a foreign key relationship to the director table. To retrieve all related data, including the director at the first database lookup, we can use select_related() like so:
rocky = Movie.objects.select_related('director').filter(title='rocky')
print(rocky.director) # director has already been retrieved. Database is not hit again.
# <Director: John G. Avildsen>Each foreign key relationship requires an additional database lookup. In the example above, this is not a problem, but imagine you're working with huge databases that have many foreign key relationships. Well, in that case, the load on the database can be excessive.
If you want to discover more about the methods we presented and some other interesting Django ORM methods like latest(), earliest() or in_bulk(), head over to the Django's official documentation page.
Conclusion
Object modification is an important part of almost every application. Now you are able not only to get information from database, but also to change it: create objects in database, update them and, if you don't need them anymore, delete, and also know some other useful methods you can use to interact with databases. It's very important knowledge which you will use really often.