Skip to content

Commit

Permalink
[2.2.x] Fixed #30199 -- Adjusted QuerySet.get_or_create() docs to hig…
Browse files Browse the repository at this point in the history
…hlight atomicity warning.

Backport of 1686dce from master
  • Loading branch information
alexbecker authored and carltongibson committed May 17, 2019
1 parent 34a357d commit 4ef96cc
Show file tree
Hide file tree
Showing 3 changed files with 19 additions and 18 deletions.
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ answer newbie questions, and generally made Django that much better:
Alexander Dutton <dev@alexdutton.co.uk>
Alexander Myodov <alex@myodov.com>
Alexandr Tatarinov <tatarinov1997@gmail.com>
Alex Becker <https://alexcbecker.net/>
Alex Couper <http://alexcouper.com/>
Alex Dedul
Alex Gaynor <alex.gaynor@gmail.com>
Expand Down
5 changes: 4 additions & 1 deletion docs/ref/databases.txt
Original file line number Diff line number Diff line change
Expand Up @@ -488,7 +488,10 @@ this entry are the four standard isolation levels:

or ``None`` to use the server's configured isolation level. However, Django
works best with and defaults to read committed rather than MySQL's default,
repeatable read. Data loss is possible with repeatable read.
repeatable read. Data loss is possible with repeatable read. In particular,
you may see cases where :meth:`~django.db.models.query.QuerySet.get_or_create`
will raise an :exc:`~django.db.IntegrityError` but the object won't appear in
a subsequent :meth:`~django.db.models.query.QuerySet.get` call.

.. _transaction isolation level: https://dev.mysql.com/doc/refman/en/innodb-transaction-isolation-levels.html

Expand Down
31 changes: 14 additions & 17 deletions docs/ref/models/querysets.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1882,16 +1882,18 @@ Returns a tuple of ``(object, created)``, where ``object`` is the retrieved or
created object and ``created`` is a boolean specifying whether a new object was
created.

This is meant as a shortcut to boilerplatish code. For example::
This is meant to prevent duplicate objects from being created when requests are
made in parallel, and as a shortcut to boilerplatish code. For example::

try:
obj = Person.objects.get(first_name='John', last_name='Lennon')
except Person.DoesNotExist:
obj = Person(first_name='John', last_name='Lennon', birthday=date(1940, 10, 9))
obj.save()

This pattern gets quite unwieldy as the number of fields in a model goes up.
The above example can be rewritten using ``get_or_create()`` like so::
Here, with concurrent requests, multiple attempts to save a ``Person`` with
the same parameters may be made. To avoid this race condition, the above
example can be rewritten using ``get_or_create()`` like so::

obj, created = Person.objects.get_or_create(
first_name='John',
Expand All @@ -1903,6 +1905,15 @@ Any keyword arguments passed to ``get_or_create()`` — *except* an optional one
called ``defaults`` — will be used in a :meth:`get()` call. If an object is
found, ``get_or_create()`` returns a tuple of that object and ``False``.

.. warning::

This method is atomic assuming that the database enforces uniqueness of the
keyword arguments (see :attr:`~django.db.models.Field.unique` or
:attr:`~django.db.models.Options.unique_together`). If the fields used in the
keyword arguments do not have a uniqueness constraint, concurrent calls to
this method may result in multiple rows with the same parameters being
inserted.

You can specify more complex conditions for the retrieved object by chaining
``get_or_create()`` with ``filter()`` and using :class:`Q objects
<django.db.models.Q>`. For example, to retrieve Robert or Bob Marley if either
Expand Down Expand Up @@ -1944,20 +1955,6 @@ when you're using manually specified primary keys. If an object needs to be
created and the key already exists in the database, an
:exc:`~django.db.IntegrityError` will be raised.

This method is atomic assuming correct usage, correct database configuration,
and correct behavior of the underlying database. However, if uniqueness is not
enforced at the database level for the ``kwargs`` used in a ``get_or_create``
call (see :attr:`~django.db.models.Field.unique` or
:attr:`~django.db.models.Options.unique_together`), this method is prone to a
race-condition which can result in multiple rows with the same parameters being
inserted simultaneously.

If you are using MySQL, be sure to use the ``READ COMMITTED`` isolation level
rather than ``REPEATABLE READ`` (the default), otherwise you may see cases
where ``get_or_create`` will raise an :exc:`~django.db.IntegrityError` but the
object won't appear in a subsequent :meth:`~django.db.models.query.QuerySet.get`
call.

Finally, a word on using ``get_or_create()`` in Django views. Please make sure
to use it only in ``POST`` requests unless you have a good reason not to.
``GET`` requests shouldn't have any effect on data. Instead, use ``POST``
Expand Down

0 comments on commit 4ef96cc

Please sign in to comment.