Skip to content

DOCSP-51036: Transactions #40

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Jul 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions source/includes/interact-data/transactions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
from django.db import transaction, DatabaseError
from sample_mflix.models import Movie

# start-transaction-decorator
@transaction.atomic
def insert_movie_transaction():
Movie.objects.create(
title="Poor Things",
runtime=141,
genres=["Comedy", "Romance"]
)
# end-transaction-decorator

# start-transaction-manager
def insert_movie_transaction():
with transaction.atomic():
Movie.objects.create(
title="Poor Things",
runtime=141,
genres=["Comedy", "Romance"]
)
# end-transaction-manager

# start-callback
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)
# end-callback

# start-handle-errors
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()

# end-handle-errors
4 changes: 4 additions & 0 deletions source/interact-data.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ Interact with Data
CRUD Operations </interact-data/crud>
Specify a Query </interact-data/specify-a-query>
Perform Raw Queries </interact-data/raw-queries>
Transactions </interact-data/transactions>

In this section, you can learn how to use {+django-odm+} to interact with your
MongoDB data.
Expand All @@ -36,3 +37,6 @@ MongoDB data.

- :ref:`django-raw-queries`: Learn how to use MongoDB's aggregation pipeline syntax
or the PyMongo driver to query your data.

- :ref:`django-transactions`: Learn how to use {+framework+}'s transaction API
to run data operations within a transaction.
15 changes: 3 additions & 12 deletions source/interact-data/raw-queries.txt
Original file line number Diff line number Diff line change
Expand Up @@ -164,19 +164,10 @@ that covers the fields you want to query. Then, pass a ``$search``
or ``$searchMeta`` stage in an aggregation pipeline parameter to
the ``raw_aggregate()`` method.

.. important::
.. tip::

You cannot use the ``QuerySet`` API to create Atlas Search indexes.
However, you can create an index by exposing your ``MongoClient``
object directly, on which you can call the PyMongo driver's
``create_search_index()`` method. To learn how to expose the
``MongoClient``, see the :ref:`django-client-operations` section
of this guide.

For instructions on using the PyMongo driver to create an Atlas
Search index, see `Atlas Search and Vector Search Indexes
<{+pymongo-docs+}/indexes/atlas-search-index/>`__
in the PyMongo documentation.
To learn how to create Atlas Search indexes, see :ref:`django-indexes-atlas-search`
in the Create Indexes guide.

This example runs an Atlas Search query by passing the ``$search`` pipeline
stage to the ``raw_aggregate()`` method. The code performs the following
Expand Down
156 changes: 156 additions & 0 deletions source/interact-data/transactions.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
.. _django-transactions:

=========================
Transactions and Sessions
=========================

.. facet::
:name: genre
:values: reference

.. meta::
:keywords: code example, ACID compliance, multi-document

.. contents:: On this page
:local:
:backlinks: none
:depth: 2
:class: singlecol

Overview
--------

In this guide, you can learn how to use {+django-odm+} 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-odm+} 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 {+framework+}'s transaction API to perform database transactions.
To run operations within a transaction, define them inside an atomic block of
code. {+framework+} manages session logic internally, so you do not need to
manually start a session before running a transaction.

.. important:: Transaction Limitations

{+django-odm+}'s support for the {+framework+} transaction API
has several limitations. To view a list of limitations, see
:ref:`Database and Collection Support <django-feature-compat-db-coll>`
in the {+framework+} 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:`Atlas sample datasets </sample-data>`.
The ``Movie`` model class has the following definition:

.. literalinclude:: /includes/interact-data/crud.py
:start-after: start-models
:end-before: end-models
:language: python
:copyable:

.. include:: /includes/use-sample-data.rst

.. replacement:: model-classes

``Movie`` model includes

.. replacement:: model-imports

.. code-block:: python

from <your application name>.models import Movie
from django.utils import timezone
from datetime import datetime

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:

.. literalinclude:: /includes/interact-data/transactions.py
:start-after: start-transaction-decorator
:end-before: end-transaction-decorator
:language: python
:copyable:

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:

.. literalinclude:: /includes/interact-data/transactions.py
:start-after: start-transaction-manager
:end-before: end-transaction-manager
:language: python
:copyable:

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
database. Pass a function, or any callable object, as an argument to
database. To use this method, pass a function, or any callable object, as an argument to

I think this is what this says

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll keep as is to avoid using too many commas, since I think the "to use..." is implied

``on_commit()``.

The following example queries for movies that have a ``genre`` value of
``["Horror", "Comedy"]`` only after a related database transaction completes:

.. literalinclude:: /includes/interact-data/transactions.py
:start-after: start-callback
:end-before: end-callback
:language: python
:copyable:

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 {+framework+}. Since
{+framework+} uses errors to determine whether to commit or roll
back a transaction, this can cause unexpected behavior.

If a transaction does not succeed, {+framework+} 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:

.. literalinclude:: /includes/interact-data/transactions.py
:start-after: start-handle-errors
:end-before: end-handle-errors
:language: python
:copyable:

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 {+framework+} transaction API, see `Database Transactions
<{+django-docs+}/topics/db/transactions>`__ in the {+framework+} documentation.
12 changes: 11 additions & 1 deletion source/limitations-upcoming.txt
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,8 @@ Query Support
the :ref:`raw_aggregate() method <django-raw-queries-search>`.
- ✓

.. _django-feature-compat-db-coll:

Database and Collection Support
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down Expand Up @@ -248,7 +250,15 @@ Database and Collection Support
- ✓

* - Transactions
- *Unsupported*.
- ✓ You can use {+framework+}'s transactions API with the
following limitations:

- ``QuerySet.union()`` is not supported within a transaction.
- If a transaction generates an error, the transaction is no longer usable.
- Savepoints, or nested atomic blocks, are not supported. The outermost atomic block starts
a transaction, and any subsequent atomic blocks have no effect.
- Migration operations do not run inside a transaction.
- Your MongoDB deployment must be a replica set or sharded cluster.
- ✓

Django Features
Expand Down
Loading