Django 기반 쇼핑몰에서 Ajax를 활용한 장바구니 항목 추가 구현

상품 상세 페이지에서 사용자가 구매 수량을 선택하고 '장바구니 담기' 버튼을 클릭하면, 전체 페이지를 새로 고치지 않고 화면 오른쪽 상단의 장바구니 아이콘에 표시되는 품목 수량만 실시간으로 갱신되도록 구현하는 것이 목표입니다. 이를 위해 전통적인 폼 제출 대신 비동기식 Ajax 요청을 사용합니다. 이 방식은 사용자 경험을 향상시키며, 불필요한 전체 렌더링 없이 부분 UI만 업데이트할 수 있습니다.

Ajax 기반 장바구니 추가 로직 개요

  1. 프론트엔드에서 jQuery를 이용해 사용자 입력 데이터(상품 ID, 수량)와 함께 POST 요청을 전송
  2. 서버 측 뷰에서 데이터 검증 후 Redis 기반 장바구니에 저장
  3. 처리 결과를 JSON 형태로 반환
  4. 클라이언트에서 응답을 받아 성공 시 시각적 피드백(예: 점프 애니메이션) 및 카운트 갱신

프론트엔드 JavaScript 로직 구현

아래는 jQuery 기반의 동작 스크립트입니다. 수량 조정, 유효성 검사, 그리고 Ajax 요청 처리를 포함합니다.

{% block endfiles %}
<div class="add_jump"></div>

<script type="text/javascript" src="{% static 'js/jquery-1.12.4.min.js' %}"></script>
<script type="text/javascript">
// 가격 계산 함수
function updateTotalPrice() {
    const unitPrice = parseFloat($('.show_price em').text());
    const quantity = parseInt($('.num_show').val());
    const totalPrice = (unitPrice * quantity).toFixed(2);
    $('.total em').text(totalPrice + '원');
}

// 수량 증가/감소 핸들러
$('.add').click(() => adjustQuantity(1));
$('.minus').click(() => adjustQuantity(-1));

function adjustQuantity(delta) {
    let currentQty = parseInt($('.num_show').val());
    currentQty += delta;
    if (currentQty < 1) currentQty = 1;
    $('.num_show').val(currentQty);
    updateTotalPrice();
}

// 수동 입력 시 유효성 검사
$('.num_show').blur(function() {
    let inputVal = $(this).val().trim();
    if (!inputVal || isNaN(inputVal) || parseInt(inputVal) < 1) {
        $(this).val(1);
    }
    updateTotalPrice();
});

// 애니메이션 좌표 계산
const fromTop = $('#add_cart').offset().top;
const fromLeft = $('#add_cart').offset().left;
const toTop = $('#show_count').offset().top;
const toLeft = $('#show_count').offset().left;

// 장바구니 추가 이벤트 바인딩
$('#add_cart').click(function() {
    const productId = $(this).data('goods-id');
    const quantity = $('.num_show').val();
    const csrfToken = $('input[name="csrfmiddlewaretoken"]').val();

    $.post(
        '/cart/add/',
        {
            goods_id: productId,
            count: quantity,
            csrfmiddlewaretoken: csrfToken
        },
        function(response) {
            if (response.status === 'S') {
                // 애니메이션 효과 트리거
                $('.add_jump')
                    .css({ left: fromLeft + 80, top: fromTop + 10, display: 'block' })
                    .stop()
                    .animate(
                        { left: toLeft + 7, top: toTop + 7 },
                        'fast',
                        function() {
                            $(this).fadeOut('fast', () => {
                                $('#show_count').text(response.cart_count);
                            });
                        }
                    );
            } else {
                alert('오류: ' + response.errmsg);
            }
        }
    );
});
</script>
{% endblock endfiles %}

URL 라우팅 설정

cart 앱 내 urls.py에 Ajax 엔드포인트를 등록합니다.

from django.urls import path
from .views import CartAddView, CartListView

urlpatterns = [
    path('add/', CartAddView.as_view(), name='cart_add'),
    path('', CartListView.as_view(), name='cart_list'),
]

뷰 클래스 작성

사용자 인증 상태 확인, 입력값 검증, Redis 저장소와의 상호작용을 처리하는 뷰 클래스입니다. LoginRequiredMixin 미사용 이유는 Ajax 요청 시 리디렉션 응답이 클라이언트에서 예상대로 작동하지 않기 때문입니다.

from django.views import View
from django.http import JsonResponse
from django.contrib.auth.models import User
from goods.models import Goods
from django_redis import get_redis_connection

class CartAddView(View):
    def post(self, request):
        # 인증 여부 확인
        if not request.user.is_authenticated:
            return JsonResponse({
                'status': 'E',
                'errmsg': '로그인이 필요합니다.'
            })

        user = request.user
        data = request.POST
        product_id = data.get('goods_id')
        quantity_str = data.get('count')

        # 필수 파라미터 검증
        if not all([product_id, quantity_str]):
            return JsonResponse({
                'status': 'E',
                'errmsg': '필수 정보 누락.'
            })

        # 상품 존재 여부 확인
        try:
            product_id = int(product_id)
            product = Goods.objects.get(id=product_id)
        except (ValueError, Goods.DoesNotExist):
            return JsonResponse({
                'status': 'E',
                'errmsg': '유효하지 않은 상품입니다.'
            })

        # 수량 형식 검증
        try:
            quantity = int(quantity_str)
            if quantity < 1:
                raise ValueError
        except ValueError:
            return JsonResponse({
                'status': 'E',
                'errmsg': '수량은 1 이상이어야 합니다.'
            })

        # 재고 초과 여부 확인
        if quantity > product.onhand:
            return JsonResponse({
                'status': 'E',
                'errmsg': '재고 수량을 초과했습니다.'
            })

        # Redis에 장바구니 정보 저장
        redis_conn = get_redis_connection('default')
        cart_key = f'cart_{user.id}'
        
        # 기존 수량 가져와서 합산
        existing_qty = redis_conn.hget(cart_key, product_id)
        new_quantity = quantity + (int(existing_qty) if existing_qty else 0)
        
        redis_conn.hset(cart_key, product_id, new_quantity)
        
        # 현재 장바구니 전체 품목 수 계산
        total_items = redis_conn.hlen(cart_key)

        return JsonResponse({
            'status': 'S',
            'cart_count': total_items
        })

태그: Django AJAX jQuery Redis 장바구니

7월 5일 03:13에 게시됨