Skip to content

Commit 5fcd185

Browse files
authored
DOCSP-51036: Transactions (#40)
* DOCSP-51036: Transactions * work in progress * edits * atlas search idx note * edits * add limitation * edit * add example * wording * fix * feedback * edit * feedback 2
1 parent 8bea877 commit 5fcd185

File tree

5 files changed

+228
-13
lines changed

5 files changed

+228
-13
lines changed
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
from django.db import transaction, DatabaseError
2+
from sample_mflix.models import Movie
3+
4+
# start-transaction-decorator
5+
@transaction.atomic
6+
def insert_movie_transaction():
7+
Movie.objects.create(
8+
title="Poor Things",
9+
runtime=141,
10+
genres=["Comedy", "Romance"]
11+
)
12+
# end-transaction-decorator
13+
14+
# start-transaction-manager
15+
def insert_movie_transaction():
16+
with transaction.atomic():
17+
Movie.objects.create(
18+
title="Poor Things",
19+
runtime=141,
20+
genres=["Comedy", "Romance"]
21+
)
22+
# end-transaction-manager
23+
24+
# start-callback
25+
def get_horror_comedies():
26+
movies = Movie.objects.filter(genres=["Horror", "Comedy"])
27+
for m in movies:
28+
print(f"Title: {m.title}, runtime: {m.runtime}")
29+
30+
def insert_movie_with_callback():
31+
with transaction.atomic():
32+
Movie.objects.create(
33+
title="The Substance",
34+
runtime=140,
35+
genres=["Horror", "Comedy"]
36+
)
37+
transaction.on_commit(get_horror_comedies)
38+
# end-callback
39+
40+
# start-handle-errors
41+
movie = Movie.objects.get(title="Jurassic Park")
42+
movie.title = "Jurassic Park I"
43+
try:
44+
with transaction.atomic():
45+
movie.save()
46+
except DatabaseError:
47+
movie.title = "Jurassic Park"
48+
49+
if movie.title == "Jurassic Park I":
50+
movie.plot = "An industrialist invites experts to visit his theme park of cloned dinosaurs. After a power failure," \
51+
" the creatures run loose, putting everyone's lives, including his grandchildren's, in danger."
52+
movie.save()
53+
54+
# end-handle-errors

source/interact-data.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ Interact with Data
2424
CRUD Operations </interact-data/crud>
2525
Specify a Query </interact-data/specify-a-query>
2626
Perform Raw Queries </interact-data/raw-queries>
27+
Transactions </interact-data/transactions>
2728

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

3738
- :ref:`django-raw-queries`: Learn how to use MongoDB's aggregation pipeline syntax
3839
or the PyMongo driver to query your data.
40+
41+
- :ref:`django-transactions`: Learn how to use {+framework+}'s transaction API
42+
to run data operations within a transaction.

source/interact-data/raw-queries.txt

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -164,19 +164,10 @@ that covers the fields you want to query. Then, pass a ``$search``
164164
or ``$searchMeta`` stage in an aggregation pipeline parameter to
165165
the ``raw_aggregate()`` method.
166166

167-
.. important::
167+
.. tip::
168168

169-
You cannot use the ``QuerySet`` API to create Atlas Search indexes.
170-
However, you can create an index by exposing your ``MongoClient``
171-
object directly, on which you can call the PyMongo driver's
172-
``create_search_index()`` method. To learn how to expose the
173-
``MongoClient``, see the :ref:`django-client-operations` section
174-
of this guide.
175-
176-
For instructions on using the PyMongo driver to create an Atlas
177-
Search index, see `Atlas Search and Vector Search Indexes
178-
<{+pymongo-docs+}/indexes/atlas-search-index/>`__
179-
in the PyMongo documentation.
169+
To learn how to create Atlas Search indexes, see :ref:`django-indexes-atlas-search`
170+
in the Create Indexes guide.
180171

181172
This example runs an Atlas Search query by passing the ``$search`` pipeline
182173
stage to the ``raw_aggregate()`` method. The code performs the following

source/interact-data/transactions.txt

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
.. _django-transactions:
2+
3+
=========================
4+
Transactions and Sessions
5+
=========================
6+
7+
.. facet::
8+
:name: genre
9+
:values: reference
10+
11+
.. meta::
12+
:keywords: code example, ACID compliance, multi-document
13+
14+
.. contents:: On this page
15+
:local:
16+
:backlinks: none
17+
:depth: 2
18+
:class: singlecol
19+
20+
Overview
21+
--------
22+
23+
In this guide, you can learn how to use {+django-odm+} to perform
24+
**transactions**. Transactions allow you to run a series of operations
25+
that change data only if the entire transaction is committed.
26+
If any operation in the transaction does not succeed, {+django-odm+} stops the
27+
transaction and discards all changes to the data before they ever become
28+
visible. This feature is called **atomicity**.
29+
30+
In MongoDB, transactions run within logical sessions. A
31+
session is a grouping of related read or write operations that you
32+
want to run sequentially. Sessions enable causal consistency for a group
33+
of operations and allow you to run operations in an **ACID-compliant**
34+
transaction, which is a transaction that meets an expectation of
35+
atomicity, consistency, isolation, and durability.
36+
37+
You can use {+framework+}'s transaction API to perform database transactions.
38+
To run operations within a transaction, define them inside an atomic block of
39+
code. {+framework+} manages session logic internally, so you do not need to
40+
manually start a session before running a transaction.
41+
42+
.. important:: Transaction Limitations
43+
44+
{+django-odm+}'s support for the {+framework+} transaction API
45+
has several limitations. To view a list of limitations, see
46+
:ref:`Database and Collection Support <django-feature-compat-db-coll>`
47+
in the {+framework+} and MongoDB Feature Compatibility guide.
48+
49+
Sample Data
50+
~~~~~~~~~~~
51+
52+
The examples in this guide use the ``Movie`` model, which represents
53+
the ``sample_mflix.movies`` collection from the :atlas:`Atlas sample datasets </sample-data>`.
54+
The ``Movie`` model class has the following definition:
55+
56+
.. literalinclude:: /includes/interact-data/crud.py
57+
:start-after: start-models
58+
:end-before: end-models
59+
:language: python
60+
:copyable:
61+
62+
.. include:: /includes/use-sample-data.rst
63+
64+
.. replacement:: model-classes
65+
66+
``Movie`` model includes
67+
68+
.. replacement:: model-imports
69+
70+
.. code-block:: python
71+
72+
from <your application name>.models import Movie
73+
from django.utils import timezone
74+
from datetime import datetime
75+
76+
Start a Transaction
77+
-------------------
78+
79+
To start a database transaction, define an atomic block of code
80+
by adding the ``@transaction.atomic`` decorator above your function.
81+
This decorator guarantees the atomicity of any database operations
82+
within the function. If the function successfully completes, the
83+
changes are committed to MongoDB.
84+
85+
The following example calls the ``create()`` method within a transaction,
86+
which inserts a document into the ``sample_mflix.movies`` collection if the
87+
transaction succeeds:
88+
89+
.. literalinclude:: /includes/interact-data/transactions.py
90+
:start-after: start-transaction-decorator
91+
:end-before: end-transaction-decorator
92+
:language: python
93+
:copyable:
94+
95+
Alternatively, you can use the ``transaction.atomic()`` context manager
96+
to create an atomic block. This example runs the same operation as the
97+
preceding example but uses a context manager to start a transaction:
98+
99+
.. literalinclude:: /includes/interact-data/transactions.py
100+
:start-after: start-transaction-manager
101+
:end-before: end-transaction-manager
102+
:language: python
103+
:copyable:
104+
105+
Run Callbacks After a Transaction
106+
---------------------------------
107+
108+
To perform certain actions only if a transaction successfully completes,
109+
you can use the ``transaction.on_commit()`` function. This function allows you to
110+
register callbacks that run after a transaction is committed to the
111+
database. Pass a function, or any callable object, as an argument to
112+
``on_commit()``.
113+
114+
The following example queries for movies that have a ``genre`` value of
115+
``["Horror", "Comedy"]`` only after a related database transaction completes:
116+
117+
.. literalinclude:: /includes/interact-data/transactions.py
118+
:start-after: start-callback
119+
:end-before: end-callback
120+
:language: python
121+
:copyable:
122+
123+
Handle Transaction Errors
124+
-------------------------
125+
126+
To handle exceptions that occur during a transaction, add error handling
127+
logic around your atomic code block. If you handle errors inside
128+
the atomic block, you might obscure these errors from {+framework+}. Since
129+
{+framework+} uses errors to determine whether to commit or roll
130+
back a transaction, this can cause unexpected behavior.
131+
132+
If a transaction does not succeed, {+framework+} does not revert any changes made
133+
to a model's fields. To avoid inconsistencies between your models and database documents,
134+
you might need to manually restore the original field values.
135+
136+
Example
137+
~~~~~~~
138+
139+
The following example includes error handling logic that reverts the modified
140+
``title`` value of the retrieved document if the database transaction fails:
141+
142+
.. literalinclude:: /includes/interact-data/transactions.py
143+
:start-after: start-handle-errors
144+
:end-before: end-handle-errors
145+
:language: python
146+
:copyable:
147+
148+
Since the code performs a second database operation based on the
149+
model's ``title`` value, reverting the change if the transaction errors
150+
prevents further data inconsistencies.
151+
152+
Additional Information
153+
----------------------
154+
155+
To learn more about the {+framework+} transaction API, see `Database Transactions
156+
<{+django-docs+}/topics/db/transactions>`__ in the {+framework+} documentation.

source/limitations-upcoming.txt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,8 @@ Query Support
187187
the :ref:`raw_aggregate() method <django-raw-queries-search>`.
188188
- ✓
189189

190+
.. _django-feature-compat-db-coll:
191+
190192
Database and Collection Support
191193
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
192194

@@ -252,7 +254,15 @@ Database and Collection Support
252254
- ✓
253255

254256
* - Transactions
255-
- *Unsupported*.
257+
- ✓ You can use {+framework+}'s transactions API with the
258+
following limitations:
259+
260+
- ``QuerySet.union()`` is not supported within a transaction.
261+
- If a transaction generates an error, the transaction is no longer usable.
262+
- Savepoints, or nested atomic blocks, are not supported. The outermost atomic block starts
263+
a transaction, and any subsequent atomic blocks have no effect.
264+
- Migration operations do not run inside a transaction.
265+
- Your MongoDB deployment must be a replica set or sharded cluster.
256266
- ✓
257267

258268
Django Features

0 commit comments

Comments
 (0)