Overview
In this guide, you can learn how to use Django MongoDB Backend to perform transactions. Transactions allow you to run a series of operations that change data only if the entire transaction is committed. If any operation in the transaction does not succeed, Django MongoDB Backend stops the transaction and discards all changes to the data before they ever become visible. This feature is called atomicity.
In MongoDB, transactions run within logical sessions. A session is a grouping of related read or write operations that you want to run sequentially. Sessions enable causal consistency for a group of operations and allow you to run operations in an ACID-compliant transaction, which is a transaction that meets an expectation of atomicity, consistency, isolation, and durability.
You can use Django's transaction API to perform database transactions. To run operations within a transaction, define them inside an atomic block of code. Django manages session logic internally, so you do not need to manually start a session before running a transaction.
Important
Transaction Limitations
Django MongoDB Backend's support for the Django transaction API has several limitations. To view a list of limitations, see Database and Collection Support in the Django and MongoDB Feature Compatibility guide.
Sample Data
The examples in this guide use the Movie
model, which represents
the sample_mflix.movies
collection from the Atlas sample datasets.
The Movie
model class has the following definition:
from django.db import models from django_mongodb_backend.fields import ArrayField class Movie(models.Model): title = models.CharField(max_length=200) plot = models.TextField(blank=True) runtime = models.IntegerField(default=0) released = models.DateTimeField("release date", null=True, blank=True) genres = ArrayField(models.CharField(max_length=100), null=True, blank=True) class Meta: db_table = "movies" managed = False def __str__(self): return self.title
The Movie
model includes an inner Meta
class, which specifies
model metadata, and a __str__()
method, which defines the
model's string representation. To learn about these
model features, see Define a Model in the
Create Models guide.
Run Code Examples
You can use the Python interactive shell to run the code examples. To enter the shell, run the following command from your project's root directory:
python manage.py shell
After entering the Python shell, ensure that you import the following models and modules:
from <your application name>.models import Movie from django.utils import timezone from datetime import datetime
To learn how to create a Django application that uses the Movie
model and the Python interactive shell to interact with MongoDB documents,
visit the Get Started with Django MongoDB Backend tutorial.
Start a Transaction
To start a database transaction, define an atomic block of code
by adding the @transaction.atomic
decorator above your function.
This decorator guarantees the atomicity of any database operations
within the function. If the function successfully completes, the
changes are committed to MongoDB.
The following example calls the create()
method within a transaction,
which inserts a document into the sample_mflix.movies
collection if the
transaction succeeds:
def insert_movie_transaction(): Movie.objects.create( title="Poor Things", runtime=141, genres=["Comedy", "Romance"] )
Alternatively, you can use the transaction.atomic()
context manager
to create an atomic block. This example runs the same operation as the
preceding example but uses a context manager to start a transaction:
def insert_movie_transaction(): with transaction.atomic(): Movie.objects.create( title="Poor Things", runtime=141, genres=["Comedy", "Romance"] )
Run Callbacks After a Transaction
To perform certain actions only if a transaction successfully completes,
you can use the transaction.on_commit()
function. This function allows you to
register callbacks that run after a transaction is committed to the
database. Pass a function, or any callable object, as an argument to
on_commit()
.
The following example queries for movies that have a genre
value of
["Horror", "Comedy"]
only after a related database transaction completes:
def get_horror_comedies(): movies = Movie.objects.filter(genres=["Horror", "Comedy"]) for m in movies: print(f"Title: {m.title}, runtime: {m.runtime}") def insert_movie_with_callback(): with transaction.atomic(): Movie.objects.create( title="The Substance", runtime=140, genres=["Horror", "Comedy"] ) transaction.on_commit(get_horror_comedies)
Handle Transaction Errors
To handle exceptions that occur during a transaction, add error handling logic around your atomic code block. If you handle errors inside the atomic block, you might obscure these errors from Django. Since Django uses errors to determine whether to commit or roll back a transaction, this can cause unexpected behavior.
If a transaction does not succeed, Django does not revert any changes made to a model's fields. To avoid inconsistencies between your models and database documents, you might need to manually restore the original field values.
Example
The following example includes error handling logic that reverts the modified
title
value of the retrieved document if the database transaction fails:
movie = Movie.objects.get(title="Jurassic Park") movie.title = "Jurassic Park I" try: with transaction.atomic(): movie.save() except DatabaseError: movie.title = "Jurassic Park" if movie.title == "Jurassic Park I": movie.plot = "An industrialist invites experts to visit his theme park of cloned dinosaurs. After a power failure," \ " the creatures run loose, putting everyone's lives, including his grandchildren's, in danger." movie.save()
Since the code performs a second database operation based on the
model's title
value, reverting the change if the transaction errors
prevents further data inconsistencies.
Additional Information
To learn more about the Django transaction API, see Database Transactions in the Django documentation.