Skip to content

Commit d2d0128

Browse files
authored
fix(Sentry:PYDOTORG-PROD-1MQ): duplicate key crash on sponsor appliction form submission (#2962)
1 parent a18368a commit d2d0128

File tree

2 files changed

+35
-6
lines changed

2 files changed

+35
-6
lines changed

apps/sponsors/models/benefits.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Benefit feature and configuration models for the sponsors app."""
22

33
from django import forms
4-
from django.db import models
4+
from django.db import IntegrityError, models, transaction
55
from django.db.models import UniqueConstraint
66
from django.urls import reverse
77
from polymorphic.models import PolymorphicModel
@@ -155,11 +155,16 @@ def create_benefit_feature(self, sponsor_benefit, **kwargs):
155155

156156
asset_qs = content_object.assets.filter(internal_name=self.internal_name)
157157
if not asset_qs.exists():
158-
asset = self.ASSET_CLASS(
159-
content_object=content_object,
160-
internal_name=self.internal_name,
161-
)
162-
asset.save()
158+
try:
159+
with transaction.atomic():
160+
asset = self.ASSET_CLASS(
161+
content_object=content_object,
162+
internal_name=self.internal_name,
163+
)
164+
asset.save()
165+
except IntegrityError:
166+
if not content_object.assets.filter(internal_name=self.internal_name).exists():
167+
raise
163168

164169
return benefit_feature
165170

apps/sponsors/tests/test_models.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1127,6 +1127,30 @@ def test_cant_create_same_asset_twice(self):
11271127
self.config.create_benefit_feature(self.sponsor_benefit)
11281128
self.assertEqual(1, TextAsset.objects.count())
11291129

1130+
def test_create_benefit_feature_with_preexisting_asset_no_crash(self):
1131+
"""Regression: submitting a new sponsorship when the sponsor already
1132+
has an asset with the same internal_name (from a prior application)
1133+
should not raise an IntegrityError."""
1134+
sponsor = self.sponsor_benefit.sponsorship.sponsor
1135+
TextAsset.objects.create(
1136+
content_object=sponsor,
1137+
internal_name=self.config.internal_name,
1138+
)
1139+
# should not raise IntegrityError
1140+
self.config.create_benefit_feature(self.sponsor_benefit)
1141+
self.assertEqual(1, TextAsset.objects.count())
1142+
1143+
def test_integrity_error_reraised_when_asset_missing(self):
1144+
"""An IntegrityError that is NOT a duplicate-asset collision must
1145+
propagate so it doesn't get silently swallowed."""
1146+
from unittest.mock import patch
1147+
1148+
with (
1149+
patch.object(TextAsset, "save", side_effect=IntegrityError("unrelated")),
1150+
self.assertRaises(IntegrityError),
1151+
):
1152+
self.config.create_benefit_feature(self.sponsor_benefit)
1153+
11301154
def test_clone_configuration_for_new_sponsorship_benefit_with_new_due_date(self):
11311155
sp_benefit = baker.make(SponsorshipBenefit, year=2023)
11321156

0 commit comments

Comments
 (0)