Add coupon redemption with one-per-user limit
Python · Django · advanced · modification
Coupon redemption: the user POSTs a coupon code, the handler checks they haven't already redeemed it, and records the redemption. Tested by redeeming a few codes sequentially — the duplicate check fires correctly and redemption counts add up.
The coupon endpoint is public to any authenticated user. During a flash sale thousands of users may attempt to redeem the same popular code within seconds. The application runs on PostgreSQL behind a WSGI server with multiple worker processes, so requests are truly concurrent.
Requirements
- A user may redeem a given coupon at most once. A second redemption attempt for the same coupon by the same user must be rejected with HTTP 409 and a clear error message.
- The redeem endpoint accepts POST requests to `/api/coupons/<code>/redeem/` and requires authentication. An unauthenticated, CSRF-valid POST is redirected to the login page (302).
- Inactive coupons (`is_active=False`) must be rejected at redemption time with HTTP 404.
- The `coupons` app is already registered in `INSTALLED_APPS`; settings are not part of this PR.
Files touched
- coupons/models.py
- coupons/views.py
- coupons/migrations/0001_initial.py
- config/urls.py
--- coupons/models.py +from django.conf import settings +from django.db import models + + +class Coupon(models.Model): + code = models.CharField(max_length=50, unique=True) + description = models.TextField(blank=True) + discount_percent = models.PositiveSmallIntegerField(default=10) + is_active = models.BooleanField(default=True) + created_at = models.DateTimeField(auto_now_add=True) + + def __str__(self): + return self.code + + +class Redemption(models.Model): + user = models.ForeignKey( + settings.AUTH_USER_MODEL, + on_delete=models.CASCADE, + related_name='redemptions', + ) + coupon = models.ForeignKey( + Coupon, + on_delete=models.CASCADE, + related_name='redemptions', + ) + redeemed_at = models.DateTimeField(auto_now_add=True) + + class Meta: