728x90
쇼핑 카트 및 세션 사용하기
1. 세션(Session)
- 정의: 웹 브라우저를 통한 사용자의 요청을 하나의 상태로 보고 유지시키는 기술.
- 특징: 서버에 정보를 저장하며, 브라우저를 닫거나 서버에서 삭제될 때만 세션이 종료됨.
- 세션 vs. 쿠키: 세션은 서버 자원을 사용, 쿠키는 클라이언트 자원을 사용.
2. 장고 세션 프레임워크
- 기능: 익명 및 사용자 세션을 지원하며, 각 방문자의 데이터를 저장 가능.
- 사용 방법:
- request.session을 사용해 세션에 접근.
- 세션 데이터는 딕셔너리처럼 취급 가능.
3. 세션 설정
- SESSION_ENGINE: 세션 데이터의 저장 위치를 설정.
- 세션 엔진 옵션:
- 데이터베이스 세션 (기본)
- 파일 기반 세션, 캐시 기반 세션 등.
- 세션 설정 옵션:
- SESSION_COOKIE_AGE: 세션 쿠키의 유효기간.
- SESSION_EXPIRE_AT_BROWSER_CLOSE: 브라우저 종료 시 세션 만료 여부.
- SESSION_SAVE_EVERY_REQUEST: 요청마다 세션을 저장할지 여부.
4. 세션 만료
- 브라우저 종료 시 세션 만료: SESSION_EXPIRE_AT_BROWSER_CLOSE 설정으로 제어.
- 세션 지속 시간: SESSION_COOKIE_AGE로 제어, request.session.set_expiry()로 덮어쓰기 가능.
5. 세션에 쇼핑 카트 저장
- 카트 구성: 제품 ID, 수량, 단가 등의 정보를 포함.
- 쇼핑 카트 생성:
- 세션 키를 통해 카트를 생성 및 관리.
- 동일한 세션 키로 카트 아이템들을 조회 가능.
- myshop/settings.py
CART_SESSION_ID = 'cart'
|
- Terminal
- cart 앱 생성
python manage.py startapp cart
|
- myshop/settings.py
INSTALLED_APPS = [
...
'shop.apps.ShopConfig',
'cart.apps.CartConfig',
]
|
- cart/cart.py
- 카트 초기화를 위한 __ init__() 정의
- 카트에 아이템을 추가하기 위한 add(), save() 정의
- add()의 매개변수
- product: 카트에 추가하거나 업데이트할 제품의 인스턴스
- quantity: 제품의 수량. 기본 값은 1
- override_quantity: 지정된 수량으로 수량을 재정의해야 하는지(True) 또는 기존 수량에 새로운 수량을 추가해야 하는지(False)를 나타내는 Boolean 값
- add()의 매개변수
- 카트의 아이템을 제거하기 위한 remove() 정의
from decimal import Decimal
from django.conf import settings
from shop.models import Product
class Cart:
def __init__(self, request):
self.session = request.session
cart = self.session.get(settings.CART_SESSION_ID)
if not cart:
cart = self.session[settings.CART_SESSION_ID] = {}
self.cart = cart
def add(self, product, quantity=1, override_quantity=False):
product_id = str(product.id)
if product_id not in self.cart:
self.cart[product_id] = {'quantity': 0, 'price': str(product.price)}
if override_quantity:
self.cart[product_id]['quantity'] = quantity
else:
self.cart[product_id]['quantity'] += quantity
self.save()
def save(self):
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()
|
- cart/cart.py
- 카트에 포함된 아이템들을 반복해서 관련 Product 인스턴스에 접근해야 함. 이를 위해서 __ iter__()정의
- 카트에 포함된 아이템들의 총 수를 반환하기 위하여 __ len__() 정의
def __iter__(self):
product_ids = self.cart.keys()
# get the product objects and add them to the cart
products = Product.objects.filter(id__in=product_ids)
cart = self.cart.copy()
for product in products:
cart[str(product.id)]['product'] = product
for item in cart.values():
item['price'] = Decimal(item['price'])
item['total_price'] = item['price'] * item['quantity']
yield item
def __len__(self):
return sum(item['quantity'] for item in self.cart.values())
|
- cart/cart.py
- 모든 카트 아이템들의 수량의 합을 반환하기 위하여 get_total_price() 정의
- 카트 세션을 지우기 위한 clear() 정의
def get_total_price(self):
return sum(Decimal(item['price']) * item['quantity'] for item in self.cart.values())
def clear(self):
# remove cart from session
del self.session[settings.CART_SESSION_ID]
self.save()
|
2.2.6 카트 관련 뷰 만들기
- cart/forms.py
- 카트에 아이템 추가하기
from django import forms
PRODUCT_QUANTITY_CHOICES = [(i, str(i)) for i in range(1, 21)]
class CartAddProductForm(forms.Form):
quantity = forms.TypedChoiceField(choices=PRODUCT_QUANTITY_CHOICES, coerce=int)
override = forms.BooleanField(required=False, initial=False, widget=forms.HiddenInput)
|
- cart/views.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 .cart import Cart
from .forms import CartAddProductForm
@require_POST
def cart_add(request, product_id):
cart = Cart(request)
product = get_object_or_404(Product, id=product_id)
form = CartAddProductForm(request.POST)
if form.is_valid():
cd = form.cleaned_data
cart.add(product=product,
quantity=cd['quantity'],
override_quantity=cd['override'])
return redirect('cart:cart_detail')
@require_POST
def cart_remove(request, product_id):
cart = Cart(request)
product = get_object_or_404(Product, id=product_id)
cart.remove(product)
return redirect('cart:cart_detail')
def cart_detail(request):
cart = Cart(request)
for item in cart:
item['update_quantity_form'] = CartAddProductForm(initial={
'quantity': item['quantity'],
'override': True})
return render(request, 'cart/detail.html', {'cart': cart})
|
- cart/urls.py
from django.urls import path
from . import views
app_name = 'cart'
urlpatterns = [
path('', views.cart_detail, name='cart_detail'),
path('add/<int:product_id>/', views.cart_add, name='cart_add'),
path('remove/<int:product_id>/', views.cart_remove, name='cart_remove'),
]
|
- cart/templates/cart/detail.html
- 카트를 표시하기 위한 템플릿 작성
{% extends "shop/base.html" %}
{% load static %}
{% block title %}
Your shopping cart
{% endblock %}
{% block content %}
<h1>Your shopping cart</h1>
<table class="cart">
<thead>
<tr>
<th>Image</th>
<th>Product</th>
<th>Quantity</th>
<th>Remove</th>
<th>Unit price</th>
<th>Price</th>
</tr>
</thead>
<tbody>
{% for item in cart %}
{% with product=item.product %}
<tr>
<td>
<a href="{{ product.get_absolute_url }}">
<img src="{% if product.image %}{{ product.image.url }}
{% else %}{% static "img/no_image.png" %}{% endif %}">
</a>
</td>
<td>{{ product.name }}</td>
<td>{{ item.quantity}}</td>
<td>
<form action="{% url "cart:cart_remove" product.id %}" method="post">
<input type="submit" value="Remove">
{% csrf_token %}
</form>
</td>
<td class="num">${{ item.price }}</td>
<td class="num">${{ item.total_price }}</td>
</tr>
{% endwith %}
{% endfor %}
<tr class="total">
<td>Total</td>
<td colspan="4"></td>
<td class="num">${{ cart.get_total_price }}</td>
</tr>
</tbody>
</table>
<p class="text-right">
<a href="{% url "shop:product_list" %}" class="button light">Continue shopping</a>
<a href="{% url "orders:order_create" %}" class="button">Checkout</a>
</p>
{% endblock %}
|
- shop/views.py
- 카트에 제품 추가하기
from cart.forms import CartAddProductForm
...
def product_detail(request, id, slug):
product = get_object_or_404(Product, id=id, slug=slug, available=True)
cart_product_form = CartAddProductForm()
return render(request, 'shop/product/detail.html',
{'product': product, 'cart_product_form': cart_product_form})
|
- shop/templates/shop/product/detail.html
...
<p class="price">${{ product.price }}</p>
<form action="{% url "cart:cart_add" product.id %}" method="post">
{{ cart_product_form }}
{% csrf_token %}
<input type="submit" value="Add to cart">
</form>
{{ product.description|linebreaks }}
...
|
- myshop/urls.py
urlpatterns = [
path('admin/', admin.site.urls),
path('cart/', include('cart.urls', namespace="cart")),
path('', include('shop.urls', namespace='shop')),
]
|
- cart/views.py
- 카트에 제품 수량 업데이트하기
...
def cart_detail(request):
cart = Cart(request)
for item in cart:
item['update_quantity_form'] = CartAddProductForm(initial={
'quantity': item['quantity'],
'override': True})
return render(request, 'cart/detail.html', {'cart': cart})
|
- cart/templates/cart/detail.html
# <td>{{ item.quantity}}</td>를 찾아서 수정
...
<td>
<form action="{% url "cart:cart_add" product.id %}" method="post">
{{ item.update_quantity_form.quantity }}
{{ item.update_quantity_form.override }}
<input type="submit" value="Update">
{% csrf_token %}
</form>
</td>
...
|
from django.shortcuts import render, redirect, get_object_or_404
from django.views.decorators.http import require_POST
from shop.models import Product
from .cart import Cart
from .forms import CartAddProductForm
@require_POST
def cart_add(request, product_id):
cart = Cart(request)
product = get_object_or_404(Product, id=product_id)
form = CartAddProductForm(request.POST)
if form.is_valid():
cd = form.cleaned_data
cart.add(product=product, quantity=cd['quantity'], override_quantity=cd['override'])
return redirect('cart:cart_detail')
@require_POST
def cart_remove(request, product_id):
cart = Cart(request)
product = get_object_or_404(Product, id=product_id)
cart.remove(product)
return redirect('cart:cart_detail')
def cart_detail(request):
cart = Cart(request)
for item in cart:
item['update_quantity_form'] = CartAddProductForm(initial={
'quantity': item['quantity'], 'override': True})
return render(request, 'cart/detail.html', {'cart': cart})
|
2.2.7 현재 카트에 대한 콘텍스트 프로세스 생성하기
- 콘텍스트 프로세서
- 요청 객체를 인수로 받아서 요청 콘텍스트에 추가된 딕셔너리를 반환하는 파이썬 함수
- 모든 템플릿에서 전역적으로 사용할 수 있는 함수를 만들어야 할 때 유용함
- startproject를 사용하여 새로운 프로젝트를 생성할 때 TEMPLATES 설정 내의 context_processors 옵션에 다음과 같은 템플릿 콘텍스트 프로세스가 포함됨
- django.template.context_processors.debug
- 요청에서 실행된 SQL 쿼리 목록을 출력하는 콘텍스트의 debug와 sql_queries 변수를 설정함
- django.template.context_processors.request
- 콘텍스트의 request 변수를 설정함
- django.template.context_processors.auth
- 요청의 user 변수를 설정함
- django.template.context_processors.messages
- 콘텍스트에서 메시지 프레임워크를 사용해서 생성된 모든 메시지를 담는 message 변수를 설정함
- django.template.context_processors.debug
- 또한 장고는 django.template.context_processors.csrf를 활성화하여 사이트 간 요청 위조(CSRF, Cross Site Request Forgery) 공격을 방지함. 이 프로세서는 설정에는 포함되어 있지 않지만 항상 활성화되어 있으며, 보안상의 이유로 인해 해제할 수 없음
요청 콘텍스트에 카트 설정하기
- 현재 카트를 요청 콘텍스트에 설정하는 콘텍스트 프로세서 만들기
- 이 프로세서를 사용하면 모든 템플릿에서 카트에 엑세스할 수 있음
- 순서
- cart 애플리케이션 디렉토리 내에 새로운 파일(context_processors.py)을 생성
- 콘텍스트 프로세서는 어디에 저장해도 무방함(코드 작성, 구성에 용이한 장소에 저장할 것을 권장)
- context_processors.py 파일에 다음 코드를 추가
- cart 애플리케이션 디렉토리 내에 새로운 파일(context_processors.py)을 생성
from.cart import Cart def cart(request): return { 'cart' : Cart(request)} |
-
- 콘텍스트 프로세서에서 요청 객체를 사용하여 Cart를 인스턴스화하고 템플릿에서 cart라는 이름의 변수를 사용할 수 있도록 함
3. 프로젝트의 settings.py 파일을 편집하여 TEMPLATES 설정 내의 context_processors 옵션에 cart.context_processors.cart 추가
TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', 'cart.context_processors.cart', ], }, }, ] |
cart 콘텍스트 프로세서는 템플릿이 렌더링될 때마다 장고의 RequestContext를 사용해서 실행됨
cart 변수는 템플릿의 콘텍스트에 설정됨
- 4. shop 애플리케이션의 shop/base.html 템플릿 편집
다음의 코드를 찾음
<div class="cart"> Your cart is empty. </div> |
찾은 코드를 다음의 코드로 수정함
<div class="cart"> {% with total_items=cart|length %} {% if total_items > 0 %} Your cart: <a href="{% url "cart:cart_detail" %}"> {{ total_items }} item{{ total_items|pluralize }}, ${{ cart.get_total_price }} </a> {% elif not order %} Your cart is empty. {% endif %} {% endwith %} </div> |
5. 서버 재시작
python manege.py runserver |
728x90
'PYTHON-BACK' 카테고리의 다른 글
#파이썬 24.09.02(월) ~ 24.09.13(금) 마켓컬리 클론코딩 프로젝트 (2) | 2024.09.13 |
---|---|
#파이썬 33일차_온라인 상점 구축 프로젝트3 (0) | 2024.08.27 |
#파이썬 32일차_온라인 상점 구축 프로젝트1 (0) | 2024.08.23 |
#파이썬 34일차_이커머스 클론코딩5_(2) (0) | 2024.08.22 |
#파이썬 34일차_이커머스 클론코딩5_(1) (0) | 2024.08.22 |