-
Notifications
You must be signed in to change notification settings - Fork 7
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
Changes from 12 commits
073c271
07a82ac
f1560c9
005217f
9652563
48f877e
fab0362
7b36ded
4d0fdb3
90d24a8
72b93db
d1c6872
0d93cbb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 |
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 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:`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 | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
I think this is what this says There was a problem hiding this comment. Choose a reason for hiding this commentThe 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, Django does not revert any changes made | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
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. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -183,6 +183,8 @@ Query Support | |
the :ref:`raw_aggregate() method <django-raw-queries-search>`. | ||
- ✓ | ||
|
||
.. _django-feature-compat-db-coll: | ||
|
||
Database and Collection Support | ||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
||
|
@@ -248,8 +250,16 @@ Database and Collection Support | |
- ✓ | ||
|
||
* - Transactions | ||
- *Unsupported*. | ||
- ✓ | ||
- *Partially Supported*. You can use {+framework+}'s transactions API with the | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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". There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm I see what you mean, is "Supported with Limitations" more accurate? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. | ||
- Your MongoDB deployment must be a replica set or sharded cluster. | ||
- *Partially Supported*. | ||
|
||
Django Features | ||
--------------- | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.