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 10 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
51 changes: 51 additions & 0 deletions source/includes/interact-data/transactions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
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"]
)

Copy link
Collaborator

Choose a reason for hiding this comment

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

chop the blank line?

transaction.on_commit(get_horror_comedies)
# end-callback

# start-handle-errors
movie = Movie.objects.get(
title="Jurassic Park",
released=timezone.make_aware(datetime(1993, 6, 11))
Copy link
Collaborator

Choose a reason for hiding this comment

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

It's probably enough to retrieve a move by name (omit released=...).

)
try:
with transaction.atomic():
movie.update(title="Jurassic Park I")
Copy link
Collaborator

Choose a reason for hiding this comment

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

models don't have an update() method

except DatabaseError:
movie.update(title="Jurassic Park")
Copy link
Collaborator

Choose a reason for hiding this comment

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

You might copy from Django's example and use the value of movie.title in a line that follows (like if obj.active: in Django's example) to make it clear why it could be important to revert the value.

# 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
151 changes: 151 additions & 0 deletions source/interact-data/transactions.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
.. _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 data changes before they ever become

Choose a reason for hiding this comment

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

Suggested change
transaction and discards all data changes before they ever become
transaction and discards all changes to the data before they ever become

Consider this for clarity

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 Django and MongoDB Feature Compatibility guide.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
in the Django and MongoDB Feature Compatibility guide.
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 MongoDB.

The following example calls the ``create()`` method within a transaction,
inserting a document into the ``sample_mflix.movies`` collection if the

Choose a reason for hiding this comment

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

Suggested change
inserting a document into the ``sample_mflix.movies`` collection if the
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
---------------------------------

If you want to perform certain actions only if a transaction successfully

Choose a reason for hiding this comment

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

Suggested change
If you want to perform certain actions only if a transaction successfully
To perform certain actions only if a transaction successfully

completes, you can use the ``transaction.on_commit()`` method. This method allows you to

Choose a reason for hiding this comment

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

Suggested change
completes, you can use the ``transaction.on_commit()`` method. This method allows you to
completes, use the ``transaction.on_commit()`` method. This method allows you to

Copy link
Collaborator

Choose a reason for hiding this comment

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

on_commit() is a function, not a method.

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 include error handing logic inside
the atomic block, {+framework+} might ignore these errors, resulting in unexpected
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think the meaning of "{+framework+} might ignore these errors," is probably unclear.

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 added some more detail here

behavior.

If a transaction does not succeed, your application does not revert any changes made
Copy link
Collaborator

Choose a reason for hiding this comment

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

"your application" -> Django

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:

Additional Information
----------------------

To learn more about the {+framework+} transaction API, see `Database Transactions
<{+django-docs+}/topics/db/transactions>`__ in the {+framework+} documentation.
15 changes: 13 additions & 2 deletions 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,8 +250,17 @@ Database and Collection Support
- ✓

* - Transactions
- *Unsupported*.
- ✓
- *Partially Supported*. You can use {+framework+}'s transactions API with the
Copy link
Collaborator

Choose a reason for hiding this comment

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

It's not an issue for this PR to tackle, but I think it's a strange choice of words to call it "Partial support" when there are any limitations. To me, "partial support" makes me think "this feature is partially completed and more work will be done later".

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Hm I see what you mean, is "Supported with Limitations" more accurate?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes, but I think it would be enough to say, "✓ You use... with the following limitations:" rather than place a double emphasis on limitations.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yeah I like ✓ with limitations noted. Partial support has more of a "we may get further on this soon" which I don't think is the case with transactions support.

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, which differs from {+framework+}'s
default migration behavior.
Copy link
Collaborator

Choose a reason for hiding this comment

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

which differs from {+framework+}'s default migration behavior.

This isn't quite accurate. It depends on the database. SQLite & PostgreSQL run operations inside a transaction, but MySQL and Oracle don't. (The relevant feature flag is DatabaseFeatures.can_rollback_ddl.)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Removed this part

- Your MongoDB deployment must be a replica set or sharded cluster.
- *Partially Supported*.

Django Features
---------------
Expand Down
Loading