지난 시간 : 로그인까지 구현
오늘은 장바구니를 구현할 것이다.
일단 장바구니를 구현하기 위해서 cart
라는 앱을 추가하자.
python manage.py startapp cart
이후 config/settings.py 에 등록하자.
INSTALLED_APPS = [
'django.contrib.sites',
'allauth',
'allauth.account',
'allauth.socialaccount',
'allauth.socialaccount.providers.naver',
'allauth.socialaccount.providers.kakao',
'shop',
'cart',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.humanize',
'disqus'
]
cart = 선택한 상품을 주문하기 위해 보관 하는 기능
cart/cart.py 생성
from decimal import Decimal
from 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) # settings에 CART_ID와 연결
if not cart : # cart 정보가 없으면
cart = self.session[settings.CART_ID] = {} # 새 딕셔너리 생성
self.cart = cart
def __len__(self): # 장바구니에 있는 상품의 수량을 전부 더한 결과
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) # 장바구니에 있는 제품 정보만 Product db에서 가져옴
for product in products: # 제품 정보를 하나씩 읽어와서
self.cart[str(product.id)]['product'] = product # session에 키 값들을 넣을 때 문자로 넣는다
for item in self.cart.values(): # 장바구니에 있는 제품을 하나씩 꺼내
item['total_price'] = item['price'] * item['quantity'] # 가격 x item 수를 총 가격에 넣고
item['price'] = Decimal(item['price']) # 가격에 숫자형으로 바꿔 item에 넣는다
yield item
# 장바구니에 넣기
def add(self, product, quantity=1, is_update=False): # 제품 정보를 업데이트하는지 아닌지 확인
product_id = str(product.id)
if product_id not in self.cart:
self.cart[product_id] = {'quantity':0, 'price':str(product.price)}
if is_update:
self.cart[product_id]['quantity'] = quantity
else:
self.cart[product_id]['quantity'] += quantity
self.save()
# 장바구니 저장하기
def save(self):
self.session[settings.CART_ID] = self.cart
self.session.modified = True
# 장바구니에서 삭제하기
def remove(self, product):
product_id = str(product.id)
if product_id in self.cart: # 장바구니에 제품이 있다면
del(self.cart[product_id]) # 해당하는 제품을 삭제하고
self.save() # 현재 장바구니를 저장한다
# 장바구니 비우기
def clear(self):
self.session[settings.CART_ID] = {}
self.session.modified = True
# 장바구니에 있는 제품의 총 가격 계산
def get_product_total(self):
return sum(Decimal(item['price']) * item['quantity'] for item in self.cart.values())
CART_ID = 'cart_in_session'
세션으로 사용하는 방식이기 때문에 request.session에 데이터를 저장하고 꺼내오는 구조
세션에 값을 저장하려면 키 값을 설정해야 하는데 settings.py 에 CART_ID 라는 변수를 만들고
거기에 설정된 값을 가져다 사용하기 위해 settings.pyCART_ID 라는 변수를 사용
cart/forms.py
from django import forms
class AddProductForm(forms.Form):
quantity = forms.IntegerField()
is_update = forms.BooleanField(required=False, initial=False, widget=forms.HiddenInput)
is_update == 상세 페이지에서 추가할 때와 장바구니에서 수량을 바꿀 때 동작하는 방식이 다르도록 하기 위해 사용
cart/vies.py
from django.shortcuts import render, redirect, get_object_or_404
from django.views.decorators.http import require_POST
from shop.models import Product
from .forms import AddProductForm
from .cart import Cart
# Create your views here.
@require_POST
def add(request, product_id):
cart = Cart(request)
product = get_object_or_404(Product, id=product_id)
form = AddProductForm(request.POST)
if form.is_valid():
cd = form.cleaned_data
cart.add(product=product, quantity=cd['quantity'], is_update=['is_update'])
return redirect('cart:detail')
def remove(request, product_id) :
cart = Cart(request)
product = get_object_or_404(Product, id=product_id)
cart.remove(product)
return redirect('cart:detail')
def detail(request):
cart = Cart(request)
for product in cart :
product['quantity_form'] = AddProductForm(initial={'quantity':product['quantity'], 'is_update':True})
return render(request, 'cart/detail.html', {'cart':cart})
cart/urls.py
from django.urls import path
from .views import *
app_name = 'cart'
urlpatterns = [
path('', detail, name='detail'),
path('add/<int:product_id>', add, name='product_add'),
path('remove/<product_id>', remove, name='product_remove'),
]
config/urls.py
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
urlpatterns = [
path('admin/', admin.site.urls),
path('accounts/', include('allauth.urls')),
path('cart/', include('cart.urls')),
path('', include('shop.urls')),
]
cart/templates/cart/detail.html
{% extends 'base.html' %}
{% load static %}
{% block title %}
Shopping cart
{% endblock %}
{% block content %}
<table class="table table-striped">
<thead>
<tr>
<th scope="col">Image</th>
<th scope="col">Product</th>
<th scope="col">Quantity</th>
<th scope="col">Remove</th>
<th scope="col">Unit price</th>
<th scope="col">Price</th>
</tr>
</thead>
<tbody>
{% for item in cart %}
{% with product=item.product %}
<tr>
<th scope="row">
<a href="{{ product.get_absolute_url }}">
<img src="{{ product.image.url }}" class="img-thumbnail">
</a>
</th>
<td>{{ product.name }}</td>
<td>
<form action="{% url 'cart:product_add' product.id %}" method="post">
{{ item.quantity_form.quantity }}
{{ item.quantity_form.is_update }}
<input type="submit" class="btn btn-primary" value="Update">
{% csrf_token %}
</form>
</td>
<td><a href="{% url 'cart:product_remove' product.id %}">Remove</a></td>
<td class="num">${{ item.price }}</td>
<td class="num">${{ item.total_price }}</td>
</tr>
{% endwith %}
{% endfor %}
{% if cart.coupon %}
<tr class="subtotal">
<td>Subtotal</td>
<td colspan="4"></td>
<td >${{ cart.get_product_total }}</td>
</tr>
<tr>
<td>"{{ cart.coupon.code }}" coupon (${{ cart.coupon.amount }})</td>
<td colspan="4"></td>
<td >- ${{ cart.get_discount_total|floatformat:"2" }}</td>
</tr>
{% endif %}
<tr class="total">
<td>Total</td>
<td colspan="4"></td>
<td class="num">${{ cart.get_total_price|floatformat:"2" }}</td>
</tr>
</tbody>
</table>
<p class="text-right">
<a href="{% url 'shop:product_all' %}" class="btn btn-secondary">Continue shopping</a>
<a href="#" class="btn btn-secondary">Checkout</a>
</p>
{% endblock %}
장바구니에 담는 기능은 상품의 상세 페이지에서 동작
shop/views.py의 product_detail 수정
from cart.forms import AddProductForm
# 상품 상세 페이지 뷰
def product_detail(request, id, product_slug=None) :
product = get_object_or_404(Product, id=id, slug=product_slug)
add_to_cart = AddProductForm(initial={'quantity':1})
return render(request, 'shop/detail.html', {'product':product, 'add_to_cart':add_to_cart})
좋은 글 감사합니다.