Skip to content

Commit 9ad93a5

Browse files
committed
--amend
1 parent 00ee71d commit 9ad93a5

File tree

8 files changed

+54
-78
lines changed

8 files changed

+54
-78
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,4 @@ tests/coverage_html/
1616
tests/.coverage*
1717
build/
1818
tests/report/
19-
tests/screenshots/
19+
tests/screenshots/

django/contrib/admin/static/admin/css/base.css

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,10 +109,6 @@ body {
109109
background: var(--body-bg);
110110
}
111111

112-
label {
113-
font-size: 1rem;
114-
}
115-
116112
/* LINKS */
117113

118114
a:link, a:visited {

django/db/models/query.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
IntegrityError,
1919
NotSupportedError,
2020
connections,
21+
models,
2122
router,
2223
transaction,
2324
)
@@ -972,8 +973,6 @@ def get_or_create(self, defaults=None, **kwargs):
972973
Return a tuple of (object, created), where created is a boolean
973974
specifying whether an object was created.
974975
"""
975-
# The get() needs to be targeted at the write database in order
976-
# to avoid potential transaction consistency problems.
977976
self._for_write = True
978977
try:
979978
return self.get(**kwargs), False
@@ -985,11 +984,23 @@ def get_or_create(self, defaults=None, **kwargs):
985984
return self.create(**params), True
986985
except IntegrityError as e:
987986
try:
988-
return self.get(**kwargs), False
987+
obj = self.get(**kwargs)
989988
except self.model.DoesNotExist:
990989
# Re-raise if the object still doesn't exist.
991-
# this wasn't a race condition but an integrity error.
990+
# This wasn't a race condition but an integrity error.
992991
raise e
992+
else:
993+
# Clear stale reverse OneToOne cache on related object
994+
for field in self.model._meta.fields:
995+
if isinstance(field, models.OneToOneField):
996+
related_obj = kwargs.get(field.name)
997+
if related_obj is not None:
998+
related_cache_attr = field.remote_field.cache_name
999+
related_obj._state.fields_cache.pop(
1000+
related_cache_attr, None
1001+
)
1002+
1003+
return obj, False
9931004

9941005
get_or_create.alters_data = True
9951006

tests/get_or_create/tests.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22
import traceback
33
from datetime import date, datetime, timedelta
44
from threading import Event, Thread, Timer
5+
from unittest import mock
56
from unittest.mock import patch
67

78
from django.core.exceptions import FieldError
89
from django.db import DatabaseError, IntegrityError, connection
10+
from django.db.models import QuerySet
911
from django.test import TestCase, TransactionTestCase, skipUnlessDBFeature
1012
from django.test.utils import CaptureQueriesContext
1113
from django.utils.functional import lazy
@@ -812,3 +814,39 @@ def test_property_attribute_without_setter_kwargs(self):
812814
Thing.objects.update_or_create(
813815
name_in_all_caps="FRANK", defaults={"name": "Frank"}
814816
)
817+
818+
819+
class GetOrCreateOneToOneRaceConditionTest(TestCase):
820+
def test_race_condition_does_not_leave_stale_cache(self):
821+
person = Person.objects.create(
822+
first_name="Brian",
823+
last_name="Atkinson",
824+
birthday="1980-01-01",
825+
defaults="foo",
826+
create_defaults="bar",
827+
)
828+
829+
triggered = False
830+
831+
def effect(*args, **kwargs):
832+
nonlocal triggered
833+
if not triggered and kwargs.get("person") == person:
834+
triggered = True
835+
Profile.objects.update_or_create(
836+
person=Person.objects.get(pk=person.pk),
837+
defaults={},
838+
)
839+
return mock.DEFAULT
840+
841+
with mock.patch.object(
842+
QuerySet,
843+
"_extract_model_params",
844+
autospec=True,
845+
wraps=QuerySet._extract_model_params,
846+
side_effect=effect,
847+
):
848+
profile, created = Profile.objects.get_or_create(person=person, defaults={})
849+
self.assertFalse(created)
850+
851+
self.assertIsNotNone(profile.person_id)
852+
self.assertTrue(Profile.objects.filter(person=person).exists())

tests/get_or_create_race/__init__.py

Whitespace-only changes.

tests/get_or_create_race/models.py

Lines changed: 0 additions & 12 deletions
This file was deleted.

tests/get_or_create_race/tests.py

Lines changed: 0 additions & 31 deletions
This file was deleted.

tests/test_postgres.py

Lines changed: 0 additions & 26 deletions
This file was deleted.

0 commit comments

Comments
 (0)