Skip to content

Commit 7c81ccc

Browse files
committed
--amend
1 parent 00ee71d commit 7c81ccc

File tree

8 files changed

+55
-76
lines changed

8 files changed

+55
-76
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: 16 additions & 2 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
)
@@ -979,17 +980,30 @@ def get_or_create(self, defaults=None, **kwargs):
979980
return self.get(**kwargs), False
980981
except self.model.DoesNotExist:
981982
params = self._extract_model_params(defaults, **kwargs)
983+
# Try to create an object using passed params.
982984
try:
983985
with transaction.atomic(using=self.db):
984986
params = dict(resolve_callables(params))
985987
return self.create(**params), True
986988
except IntegrityError as e:
987989
try:
988-
return self.get(**kwargs), False
990+
obj = self.get(**kwargs)
989991
except self.model.DoesNotExist:
990992
# Re-raise if the object still doesn't exist.
991-
# this wasn't a race condition but an integrity error.
993+
# This wasn't a race condition but an integrity error.
992994
raise e
995+
else:
996+
# Clear stale reverse OneToOne cache on related object
997+
for field in self.model._meta.fields:
998+
if isinstance(field, models.OneToOneField):
999+
related_obj = kwargs.get(field.name)
1000+
if related_obj is not None:
1001+
related_cache_attr = field.remote_field.cache_name
1002+
related_obj._state.fields_cache.pop(
1003+
related_cache_attr, None
1004+
)
1005+
1006+
return obj, False
9931007

9941008
get_or_create.alters_data = True
9951009

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)