Skip to content

Commit 95d5f57

Browse files
refactor(likes): Make like/unlike operations atomic
Wrap the like and unlike logic in `django.db.transaction.atomic()` to ensure that the creation/deletion of the like and the update of the promo's `like_count` are performed as a single, indivisible operation. This prevents potential race conditions and data inconsistency. Add `promo.refresh_from_db()` after the `F()` expression update to ensure the promo object reflects the latest database state.
1 parent d32e0b8 commit 95d5f57

File tree

1 file changed

+33
-29
lines changed

1 file changed

+33
-29
lines changed

promo_code/user/views.py

Lines changed: 33 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -246,42 +246,46 @@ def get_promo_object(self, promo_id):
246246

247247
def post(self, request, id):
248248
"""Add a like to the promo code."""
249-
promo = self.get_promo_object(id)
249+
with django.db.transaction.atomic():
250+
promo = self.get_promo_object(id)
250251

251-
created = user.models.PromoLike.objects.get_or_create(
252-
user=request.user,
253-
promo=promo,
254-
)
252+
like_obj, created = user.models.PromoLike.objects.get_or_create(
253+
user=request.user,
254+
promo=promo,
255+
)
255256

256-
if created:
257-
promo.like_count = django.db.models.F('like_count') + 1
258-
promo.save(update_fields=['like_count'])
257+
if created:
258+
promo.like_count = django.db.models.F('like_count') + 1
259+
promo.save(update_fields=['like_count'])
260+
promo.refresh_from_db()
259261

260-
return rest_framework.response.Response(
261-
{'status': 'ok'},
262-
status=rest_framework.status.HTTP_200_OK,
263-
)
262+
return rest_framework.response.Response(
263+
{'status': 'ok'},
264+
status=rest_framework.status.HTTP_200_OK,
265+
)
264266

265267
def delete(self, request, id):
266268
"""Remove a like from the promo code."""
267-
promo = self.get_promo_object(id)
269+
with django.db.transaction.atomic():
270+
promo = self.get_promo_object(id)
271+
272+
# Idempotency: if the like doesn't exist,
273+
# do nothing and still return 200 OK.
274+
like_instance = user.models.PromoLike.objects.filter(
275+
user=request.user,
276+
promo=promo,
277+
).first()
278+
279+
if like_instance:
280+
like_instance.delete()
281+
promo.like_count = django.db.models.F('like_count') - 1
282+
promo.save(update_fields=['like_count'])
283+
promo.refresh_from_db()
268284

269-
# Idempotency: if the like doesn't exist,
270-
# do nothing and still return 200 OK.
271-
like_instance = user.models.PromoLike.objects.filter(
272-
user=request.user,
273-
promo=promo,
274-
).first()
275-
276-
if like_instance:
277-
like_instance.delete()
278-
promo.like_count = django.db.models.F('like_count') - 1
279-
promo.save(update_fields=['like_count'])
280-
281-
return rest_framework.response.Response(
282-
{'status': 'ok'},
283-
status=rest_framework.status.HTTP_200_OK,
284-
)
285+
return rest_framework.response.Response(
286+
{'status': 'ok'},
287+
status=rest_framework.status.HTTP_200_OK,
288+
)
285289

286290

287291
class PromoCommentListCreateView(rest_framework.generics.ListCreateAPIView):

0 commit comments

Comments
 (0)