Skip to content

Commit 3e13383

Browse files
Intermediate commit
1 parent 4ff5ea9 commit 3e13383

File tree

17 files changed

+249
-11
lines changed

17 files changed

+249
-11
lines changed

cart/cart.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from django.conf import settings
44

5+
from coupons.models import Coupon
56
from shop.models import Product
67

78

@@ -17,6 +18,9 @@ def __init__(self, request):
1718
cart = self.session[settings.CART_SESSION_ID] = {}
1819
self.cart = cart
1920

21+
# store current applied coupon
22+
self.coupon_id = self.session.get('coupon_id')
23+
2024
def __iter__(self):
2125
"""
2226
Iterate over the items in the cart and get the products
@@ -82,3 +86,22 @@ def remove(self, product):
8286
def save(self):
8387
# mark the session as "modified" to make sure it gets saved
8488
self.session.modified = True
89+
90+
@property
91+
def coupon(self):
92+
if self.coupon_id:
93+
try:
94+
return Coupon.objects.get(id=self.coupon_id)
95+
except Coupon.DoesNotExist:
96+
pass
97+
return None
98+
99+
def get_discount(self):
100+
if self.coupon:
101+
return (
102+
self.coupon.discount / Decimal('100')
103+
) * self.get_total_price()
104+
return Decimal(0)
105+
106+
def get_total_price_after_discount(self):
107+
return self.get_total_price() - self.get_discount()

cart/templates/cart/detail.html

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ <h1>Your shopping cart</h1>
2626
2727
2828
29+
30+
2931
{% if product.image %}{{ product.image.url }}{% else %}{% static "img/no_image.png" %}{% endif %}">
3032
</a>
3133
</td>
@@ -50,13 +52,40 @@ <h1>Your shopping cart</h1>
5052
</tr>
5153
{% endwith %}
5254
{% endfor %}
55+
<tr class="total">
56+
{% if cart.coupon %}
57+
<tr class="subtotal">
58+
<td>Subtotal</td>
59+
<td colspan="4"></td>
60+
<td class="num">${{ cart.get_total_price|floatformat:2 }}</td>
61+
</tr>
62+
<tr>
63+
<td>
64+
"{{ cart.coupon.code }}" coupon
65+
({{ cart.coupon.discount }}% off)
66+
</td>
67+
<td colspan="4"></td>
68+
<td class="num neg">
69+
- ${{ cart.get_discount|floatformat:2 }}
70+
</td>
71+
</tr>
72+
{% endif %}
5373
<tr class="total">
5474
<td>Total</td>
5575
<td colspan="4"></td>
56-
<td class="num">${{ cart.get_total_price }}</td>
76+
<td class="num">
77+
${{ cart.get_total_price_after_discount|floatformat:2 }}
78+
</td>
5779
</tr>
80+
5881
</tbody>
5982
</table>
83+
<p>Apply a coupon:</p>
84+
<form action="{% url "coupons:apply" %}" method="post">
85+
{{ coupon_apply_form }}
86+
<input type="submit" value="Apply">
87+
{% csrf_token %}
88+
</form>
6089
<p class="text-right">
6190
<a href="{% url "shop:product_list" %}" class="button
6291
light">Continue shopping</a>

cart/views.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from django.shortcuts import get_object_or_404, redirect, render
22
from django.views.decorators.http import require_POST
33

4+
from coupons.froms import CouponApplyForm
45
from shop.models import Product
56
from .cart import Cart
67
from .forms import CartAddProductForm
@@ -35,10 +36,12 @@ def cart_detail(request):
3536
'override': True,
3637
}
3738
)
39+
coupon_apply_form = CouponApplyForm()
3840
return render(
3941
request,
4042
'cart/detail.html',
4143
context={
4244
'cart': cart,
45+
'coupon_apply_form': coupon_apply_form
4346
}
4447
)

coupons/froms.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from django import forms
2+
3+
4+
class CouponApplyForm(forms.Form):
5+
code = forms.CharField(max_length=50)

coupons/urls.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from django.urls import path
2+
3+
from . import views
4+
5+
app_name = 'coupons'
6+
7+
urlpatterns = [
8+
path('apply/', views.coupon_apply, name='apply'),
9+
]

myshop/settings/local.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
'shop.apps.ShopConfig',
4242
'orders.apps.OrdersConfig',
4343
'payment.apps.PaymentConfig',
44+
'coupons.apps.CouponsConfig',
4445
]
4546

4647
MIDDLEWARE = [
@@ -148,3 +149,9 @@
148149
EMAIL_USE_TLS = True
149150

150151
STRIPE_WEBHOOK_SECRET = config('STRIPE_WEBHOOK_SECRET')
152+
153+
154+
# Redis settings
155+
REDIS_HOST = 'localhost'
156+
REDIS_PORT = 6379
157+
REDIS_DB = 1

myshop/settings/production.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
'shop.apps.ShopConfig',
4343
'orders.apps.OrdersConfig',
4444
'payment.apps.PaymentConfig',
45+
'coupons.apps.CouponsConfig',
4546
]
4647

4748
MIDDLEWARE = [
@@ -141,3 +142,8 @@
141142
STRIPE_API_VERSION = '2025-02-24.acacia'
142143

143144
STRIPE_WEBHOOK_SECRET = config('STRIPE_WEBHOOK_SECRET')
145+
146+
# Redis settings
147+
REDIS_HOST = 'localhost'
148+
REDIS_PORT = 6379
149+
REDIS_DB = 1

myshop/urls.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
path('cart/', include('cart.urls', namespace='cart')),
2525
path('orders/', include('orders.urls', namespace='orders')),
2626
path('payment/', include('payment.urls', namespace='payment')),
27+
path('coupons/', include('coupons.urls', namespace='coupons')),
2728
path('', include('shop.url', namespace='shop')),
2829
]
2930

orders/models.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1+
from decimal import Decimal
2+
13
from django.conf import settings
4+
from django.core.validators import MaxValueValidator, MinValueValidator
25
from django.db import models
36

7+
from coupons.models import Coupon
8+
49

510
class Order(models.Model):
611
first_name = models.CharField(max_length=50)
@@ -15,6 +20,21 @@ class Order(models.Model):
1520

1621
stripe_id = models.CharField(max_length=250, blank=True)
1722

23+
coupon = models.ForeignKey(
24+
Coupon,
25+
related_name='orders',
26+
on_delete=models.SET_NULL,
27+
blank=True,
28+
null=True,
29+
)
30+
discount = models.SmallIntegerField(
31+
default=0,
32+
validators=[
33+
MinValueValidator(0),
34+
MaxValueValidator(100)
35+
]
36+
)
37+
1838
class Meta:
1939
ordering = ['-created']
2040
indexes = [
@@ -26,9 +46,19 @@ class Meta:
2646
def __str__(self):
2747
return f'Order {self.id}'
2848

29-
def total_cost(self):
49+
def get_total_cost_before_discount(self):
3050
return sum(item.get_cost() for item in self.items.all())
3151

52+
def get_discount(self):
53+
total_cost = self.get_total_cost_before_discount()
54+
if self.discount:
55+
return total_cost * (self.discount / Decimal(100))
56+
return Decimal(0)
57+
58+
def get_total_cost(self):
59+
total_cost = self.get_total_cost_before_discount()
60+
return total_cost - self.get_discount()
61+
3262
def get_stripe_url(self):
3363
if not self.stripe_id:
3464
# no payment associated

orders/templates/admin/orders/order/detail.html

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,24 @@ <h2>Items bought</h2>
8282
<td class="num">${{ item.get_cost }}</td>
8383
</tr>
8484
{% endfor %}
85+
86+
{% if order.coupon %}
87+
<tr class="subtotal">
88+
<td colspan="3">Subtotal</td>
89+
<td class="num">
90+
${{ order.get_total_cost_before_discount|floatformat:2 }}
91+
</td>
92+
</tr>
93+
<tr>
94+
<td colspan="3">
95+
"{{ order.coupon.code }}" coupon
96+
({{ order.discount }}% off)
97+
</td>
98+
<td class="num neg">
99+
- ${{ order.get_discount|floatformat:2 }}
100+
</td>
101+
</tr>
102+
{% endif %}
85103
<tr class="total">
86104
<td colspan="3">Total</td>
87105
<td class="num">${{ order.get_total_cost }}</td>

0 commit comments

Comments
 (0)