diff --git a/source/includes/interact-data/transactions.py b/source/includes/interact-data/transactions.py new file mode 100644 index 0000000..916f5a6 --- /dev/null +++ b/source/includes/interact-data/transactions.py @@ -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 \ No newline at end of file diff --git a/source/interact-data.txt b/source/interact-data.txt index c2bc78f..72f1e21 100644 --- a/source/interact-data.txt +++ b/source/interact-data.txt @@ -24,6 +24,7 @@ Interact with Data CRUD Operations Specify a Query Perform Raw Queries + Transactions In this section, you can learn how to use {+django-odm+} to interact with your MongoDB data. @@ -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. diff --git a/source/interact-data/raw-queries.txt b/source/interact-data/raw-queries.txt index 7498340..d481b90 100644 --- a/source/interact-data/raw-queries.txt +++ b/source/interact-data/raw-queries.txt @@ -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 diff --git a/source/interact-data/transactions.txt b/source/interact-data/transactions.txt new file mode 100644 index 0000000..0123a96 --- /dev/null +++ b/source/interact-data/transactions.txt @@ -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 ` + 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 `. +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 .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 +``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. \ No newline at end of file diff --git a/source/limitations-upcoming.txt b/source/limitations-upcoming.txt index 2b3510a..d09ab74 100644 --- a/source/limitations-upcoming.txt +++ b/source/limitations-upcoming.txt @@ -183,6 +183,8 @@ Query Support the :ref:`raw_aggregate() method `. - ✓ +.. _django-feature-compat-db-coll: + Database and Collection Support ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -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