장고 - 쇼핑몰 장바구니 기능 학습
- 이번 포스트에서는 온라인 쇼핑몰 사이트를 예로 들어, 장바구니 기능 구현 방법에 대해 알아볼 것이다.
1. 장바구니 기능 작성
- 경로 : cart(앱) > cart.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61from django.conf import settings
from shop.models import Product
class Cart(object):
def __init__(self, request):
self.session = request.session
cart = self.session.get(settings.CART_ID)
if not cart:
# 세션에 없던 키 값을 생성하면 자동 저장
cart = self.session[settings.CART_ID] = {}
# 세션에 이미 있는 키 값에 대한 값을 수정하면 수동으로 저장
self.cart = cart
def __len__(self):
# 요소가 몇개인지 갯수를 반환해주는 함수
"""
id : 실제제품
"""
return sum(item['quantity'] for item in self.cart.values())
def __iter__(self):
# for문 같은 문법을 사용할 때 안에 있는 요소를 어떤 형태로 반환할 것인지 결정하는 함수
product_ids = self.cart.keys()
products = Product.objects.filter(id__in=product_ids)
for product in products:
self.cart[str(product.id)]['product'] = product
for item in self.cart.values():
item['total_price'] = item['price'] * item['quantity']
yield item
def add(self, product, quantity=1, is_update=False):
product_id = str(product.id)
if product_id not in self.cart:
# 만약 제품 정보가 Decimal 이라면 세션에 저장할 때는 str로 형변환 해서 저장하고
# 꺼내올 때는 Decimal로 형변환해서 사용해야 한다.
self.cart[product_id] = {'quantity':0, 'price':product.price}
if is_update:
self.cart[product_id]['quantity'] = quantity
else:
self.cart[product_id]['quantity'] += quantity
self.save()
def remove(self, product):
product_id = str(product.id)
if product_id in self.cart:
del(self.cart[product_id])
self.save()
def save(self):
self.session[settings.CART_ID] = self.cart
self.session.modified = True
def clear(self):
self.cart = {}
self.save()
# 전체 제품 가격
def get_total_price(self):
return sum(item['quantity']*item['price'] for item in self.cart.values())
2. 여러 페이지에 cart를 context data로 전달하기 위해 context processors 작성
- 경로 : cart > context_processors.py
1
2
3
4
5from .cart import Cart
def cart(request):
cart = Cart(request)
return {'cart':cart}
3. 장고 프로젝트 설정에 context_processors 추가
- 경로 : config(프로젝트) > settings.py
1
2
3
4
5
6
7
8
9
10
11TEMPLATES = [
{
...
'OPTIONS': {
'context_processors': [
...
'cart.context_processors.cart',
],
},
},
]
4. 상품을 장바구니에 추가하기 위해 고객이 입력해야 할 폼 설정
- 경로 : cart > forms.py
1
2
3
4
5
6
7from django import forms
class AddToCartForm(forms.Form):
# 상품 수량 설정 field
quantity = forms.IntegerField(initial=1)
# 수정 여부 확인 field
is_update = forms.BooleanField(required=False, initial=False, widget=forms.HiddenInput)
5. 상품 상세 페이지에 특정 상품을 장바구니에 저장해주는 버튼 생성
경로 : shop > views.py
1
2
3
4
5
6
7
8
9class ProductDetail(DetailView):
model = Product
template_name = 'shop/product_detail.html'
# context data로 form 전달
def get_context_data(self, **kwargs):
form = AddToCartForm()
kwargs.update({'form':form})
return super().get_context_data(**kwargs)경로 : shop > templates > shop > product_detail.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41{% extends 'base.html' %}
{% block content %}
<!-- 금액을 10^3 단위로 comma 표시하기 위해 humanize 로드 (settings.py에서 INSTALLED_APPS = [ 'django.contrib.humanize',] 추가) -->
{% load humanize %}
<div class="row no-gutters bg-light position-relative mt-3">
<div class="col-md-6 position-static p-4 pl-md-0">
<h5 class="mt-0">{{object.name}}</h5>
<p>
<form action="{% url 'add_product' object.id %}" method="post">
<table class="table table-striped">
<tr>
<td>Price</td>
<!-- 해당 금액 10^3 단위로 comma 표시 -->
<td>{{object.price|intcomma}}</td>
</tr>
<tr>
<td>Stock</td>
<td>{{object.stock|intcomma}}</td>
</tr>
<tr>
<td>Order Available</td>
<td>{{object.available_order}}</td>
</tr>
<tr>
<td>
{{form.quantity.label}}
</td>
<td>
{{form.quantity}}
</td>
</tr>
</table>
{% csrf_token %}
<!-- 장바구니 이동 버튼 -->
<input type="submit" class="btn btn-sm btn-outline-success float-right" value="Add to Cart">
</form>
</p>
</div>
</div>
{% endblock %}
6. 장바구니 관련 페이지 경로 설정
- 경로 : cart > urls.py
1
2
3
4
5
6
7
8
9
10
11from django.urls import path
from .views import *
urlpatterns = [
# 장바구니에 상품 추가하는 페이지로 이동하는 경로
path('add/<int:product_id>/', add_product, name='add_product'),
# 장바구니에서 상품 삭제하는 view로 이동하는 경로
path('remove/<int:product_id>/', remove_product, name='remove_product'),
# 장바구니 상세정보가 조회되는 페이지로 이동하는 경로
path('detail/', cart_detail, name='cart_detail'),
]
7. 장바구니 페이지 구현
경로 : cart > views.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45from django.shortcuts import render
from django.views.decorators.http import require_POST
from django.shortcuts import redirect
from shop.models import Product
from .cart import Cart
from .forms import AddToCartForm
def add_product(request, product_id):
product = Product.objects.filter(pk=product_id)
if product.exists():
cart = Cart(request)
form = AddToCartForm(request.POST)
if form.is_valid():
cd = form.cleaned_data
# filter로 객체 가져와서, 그 객체를 사용할 때는 객체 뒤에 '[0]'을 붙여야 한다.
cart.add(product=product[0], quantity=cd['quantity'], is_update=cd['is_update'])
print(cart.cart.values())
return redirect('cart_detail')
def remove_product(request, product_id):
product = Product.objects.filter(pk=product_id)
if product.exists():
cart = Cart(request)
cart.remove(product[0])
return redirect('cart_detail')
def cart_detail(request):
# 장바구니에 담겨 있는 제품 목록 띄우기, 제품 수량 수정, 지우기, 장바구니 비우기 버튼 구현
cart = Cart(request)
for item in cart:
item['quantity_form'] = AddToCartForm(initial={'quantity':item['quantity'], 'is_update':True})
continue_url = '/'
# 현재 페이지 주소 얻기
# 1) request.build_absolute_uri('?') : 쿼리스트링 없이 현재 페이지 주소 얻기
# 2) request.build_absolute_uri() : 쿼리스트링까지 얻어오기
current_url = request.build_absolute_uri('?')
if 'HTTP_REFERER' in request.META and current_url != request.META['HTTP_REFERER']:
# 이전 페이지로 이동하는 continue_url 설정
continue_url = request.META['HTTP_REFERER']
return render(request,'cart/cart_detail.html', {'cart':cart, 'continue_url':continue_url})경로 : cart > templates > cart > cart_detail.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57{% extends 'base.html' %}
{% block content %}
{% load humanize %}
<table class="table table-striped mt-3">
<thead>
<tr>
<th>#</th>
<th>Image</th>
<th>Name</th>
<th>Quantity</th>
<th>Unit Price</th>
<th>Price</th>
<th>Remove</th>
</tr>
</thead>
<tbody>
{% for item in cart %}
{% with product=item.product %}
<tr>
<td>{{forloop.counter}}</td>
<td><a href="{{product.get_absolute_url}}" target="_blank"><img src="{{product.image.url}}" width="100%" class="img-thumbnail"></a></td>
<td><a href="{{product.get_absolute_url}}" target="_blank">{{product.name}}</a></td>
<td>
<form action="{% url 'add_product' product.id %}" method="post">
{% csrf_token %}
{{item.quantity_form.quantity}}
{{item.quantity_form.is_update}}
<input type="submit" class="btn btn-success btn-sm" value="Update">
</form>
</td>
<td>{{item.price|intcomma}}</td>
<td>{{item.total_price|intcomma}}</td>
<td><a href="{% url 'remove_product' product.id %}" class="btn btn-sm btn-outline-warning">remove</a></td>
</tr>
{% endwith %}
{% endfor %}
<tr>
<td>
Total
</td>
<td colspan="5"></td>
<td class="num">{{cart.get_total_price|intcomma}}</td>
</tr>
<tr>
<td colspan="7">
<!-- 이전 페이지로 이동 -->
<a href="{{continue_url}}" class="float-left btn btn-lg btn-primary">Continue Shopping</a>
<!-- 결제창으로 이동(결제 기능 포스트 참고) -->
<a href="{% url 'order_create' %}" class="float-right btn btn-lg btn-info">Checkout</a>
</td>
</tr>
</tbody>
</table>
{% endblock %}
Posted