Django2 (10. 상품 주문하기) feat.페스트캠퍼스

min seung moon·2021년 3월 16일
0

Django

목록 보기
26/37

상품 주문하기(1)

  • 따로 페이지를 만들지는 않고 상세보기 페이지에서 몇개를 주문할건지 수량을 지정할 수 있게하고 주문하기 버튼을 달아서 바로 주문할 수 있게 구성!

01. product_detail.html 수정하기!

{% extends "base.html" %}

{% load humanize%}
{% block contents %}
<div class="row mt-5">
    <div class="col-12">
        <div class="card" style="width: 100%;">
            <div class="card-body">
                <h5 class="card-title">{{ product.name }}</h5>
            </div>
            <ul class="list-group list-group-flush">
                <li class="list-group-item">
                    <form method="POST" action="/order/create/">
                        {% for field in form %}
                        <div class="form-group">
                            {% ifnotequal field.name 'product' %}
                            <label for="{{ field.id_for_label}}">{{field.label}}</label>
                            {% endifnotequal %}
                            <input type="{{field.field.widget.input_type}}" class="form-control"
                                id="{{ field.id_for_label}}" placeholder="{{field.label}}" name="{{field.name}}" />
                        </div>
                        {% if field.errors %}
                        <span style="color: red;">{{ field.errors }}</span>
                        {% endif %}
                        {% endfor %}
                        <button type="submit" class="btn btn-primary">주문하기</button>
                    </form>
                </li>
                <li class="list-group-item">가격 : {{ product.price|intcomma }} 원</li>
                <li class="list-group-item">등록날짜 : {{ product.register_date|date:'Y-m-d H:i' }}</li>
                <!-- description은 태그이기 때문에 필터로 |safe를 해주면 html 태그로 만들어준다! -->
                <li class="list-group-item">{{ product.description|safe }}</li>
            </ul>
        </div>
    </div>
    <div class="row">
        <div class="col-12">
            <a href="/product/">목록보기</a>
        </div>
    </div>
</div>
{% endblock %}

02. order에 forms.py 추가하기!

from django import forms
from .models import Order


class RegisterForm(forms.Form):
    quantity = forms.IntegerField(
        error_messages={
            'required': '수량을 입력해주세요.'
        }, label='수량'
    )
    # 실제로 입력받는 값이 아니라 선택한 상품의 아이디를 받아옴
    # widget=forms.HiddenInput
    product = forms.IntegerField(
        error_messages={
            'required': '재고를 입력해주세요.'
        }, label='재고', widget=forms.HiddenInput
    )

    # validate
    def clean(self):
        cleaned_data = super().clean()

03. product에 views.py 수정하기!

from django.shortcuts import render
from django.views.generic import ListView, DetailView
from django.views.generic.edit import FormView
from .models import Product
from .forms import RegisterForm
from order.forms import RegisterForm as OrderForm

# Create your views here.


class ProductList(ListView):
    model = Product
    template_name = 'product.html'
    # object_list로 사용하기 싫으면 context_object_name으로 변경 가능
    context_object_name = 'product_list'


class ProductCreate(FormView):
    template_name = 'register_product.html'
    form_class = RegisterForm
    success_url = '/product/'


class ProductDetail(DetailView):
    template_name = 'product_detail.html'
    # 어떤 모델이 아닌 쿼리셋을 지정
    # 필터를 통해서 보여질지 말지도 결정할 수 있다
    queryset = Product.objects.all()
    # template에서 사용할 변수명 지정
    context_object_name = 'product'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['form'] = OrderForm()
        return context

04. order에 views.py 입력하기!

from django.shortcuts import render
from .forms import RegisterForm
from django.views.generic.edit import FormView

# Create your views here.


class OrderCreate(FormView):
    # 화면은 따로 구연할 필요 없기 때문에 template_name = 은 생략
    form_class = RegisterForm
    success_url = '/product/'

05. root 폴더에 urls.py에 연결하기!

from django.contrib import admin
from django.urls import path
from fcuser.views import index, RegisterView, LoginiView
from product.views import ProductList, ProductCreate, ProductDetail
from order.views import OrderCreate

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', index),
    # class는 .as_view()를 입력!
    path('register/', RegisterView.as_view()),
    path('login/', LoginiView.as_view()),
    path('product/', ProductList.as_view()),
    path('product/create/', ProductCreate.as_view()),
    # 상세보기 페이지는 주소에 어떤 상품인지 나타낼수 있어야한다!
    # url에 지정을 할 수 있는데, detailView에서 기본적으로 pk라는 값을 사용
    # <int:pk> 숫자형으로 받고 받아온 숫자는 pk라는 변수로 사용
    path('product/<int:pk>/', ProductDetail.as_view()),
    path('order/create/', OrderCreate.as_view()),
]

06. product_detail.html 수정하기!

{% extends "base.html" %}

{% load humanize%}
{% block contents %}
<div class="row mt-5">
    <div class="col-12">
        <div class="card" style="width: 100%;">
            <div class="card-body">
                <h5 class="card-title">{{ product.name }}</h5>
            </div>
            <ul class="list-group list-group-flush">
                <li class="list-group-item">
                    <form method="POST" action="/order/create/">
                        {% for field in form %}
                        <div class="form-group">
                            {% ifnotequal field.name 'product' %}
                            <label for="{{ field.id_for_label}}">{{field.label}}</label>
                            {% endifnotequal %}
                            <input type="{{field.field.widget.input_type}}" class="form-control"
                                id="{{ field.id_for_label}}" placeholder="{{field.label}}" name="{{field.name}}"
                                value="{% ifequal field.name 'product' %}{{ product.id }}{% endifequal %}" />
                        </div>
                        {% if field.errors %}
                        <span style="color: red;">{{ field.errors }}</span>
                        {% endif %}
                        {% endfor %}
                        <button type="submit" class="btn btn-primary">주문하기</button>
                    </form>
                </li>
                <li class="list-group-item">가격 : {{ product.price|intcomma }} 원</li>
                <li class="list-group-item">등록날짜 : {{ product.register_date|date:'Y-m-d H:i' }}</li>
                <!-- description은 태그이기 때문에 필터로 |safe를 해주면 html 태그로 만들어준다! -->
                <li class="list-group-item">{{ product.description|safe }}</li>
            </ul>
        </div>
    </div>
    <div class="row">
        <div class="col-12">
            <a href="/product/">목록보기</a>
        </div>
    </div>
</div>
{% endblock %}

07. order forms.py 수정하기!

from django import forms
from .models import Order


class RegisterForm(forms.Form):
    quantity = forms.IntegerField(
        error_messages={
            'required': '수량을 입력해주세요.'
        }, label='수량'
    )
    # 실제로 입력받는 값이 아니라 선택한 상품의 아이디를 받아옴
    # widget=forms.HiddenInput
    product = forms.IntegerField(
        error_messages={
            'required': '재고를 입력해주세요.'
        }, label='재고', widget=forms.HiddenInput
    )

    # validate
    def clean(self):
        cleaned_data = super().clean()
        quantity = cleaned_data.get('quantity')
        product = cleaned_data.get('product')
        # 사용자 정보!, requst.session에 접근이 필요!

상품 주문하기(2)

01. order의 forms.py에 생성자 함수(init) 만들기!

  • request를 form에다가 전달할 수 있게 함수를 만든다!
from django import forms
from .models import Order
from product.models import Product
from fcuser.models import Fcuser


class RegisterForm(forms.Form):

    # 생성자함수
    # requst에 전달하기 위해서 생성자 함수를 생성
    # product views의 productDetaile에서 form을 생성하는 부분이 있습니다!
    # 그리고 order의 views.py에서 request를 처리한다
    def __init__(self, request, *args, **kwargs):
        # 기존에 있던 생성자를 사용할 수 있도록 super()에서 불러온다!
        super().__init__(*args, **kwargs)
        self.request = request

    quantity = forms.IntegerField(
        error_messages={
            'required': '수량을 입력해주세요.'
        }, label='수량'
    )
    # 실제로 입력받는 값이 아니라 선택한 상품의 아이디를 받아옴
    # widget=forms.HiddenInput
    product = forms.IntegerField(
        error_messages={
            'required': '재고를 입력해주세요.'
        }, label='재고', widget=forms.HiddenInput
    )

    # validate
    def clean(self):
        cleaned_data = super().clean()
        quantity = cleaned_data.get('quantity')
        product = cleaned_data.get('product')
        # 사용자 정보!, requst.session에 접근이 필요!

        print(self.request.session)

02. forms를 사용할 product_detail.html에 csrf_token 생성!

{% extends "base.html" %}

{% load humanize%}
{% block contents %}
<div class="row mt-5">
    <div class="col-12">
        <div class="card" style="width: 100%;">
            <div class="card-body">
                <h5 class="card-title">{{ product.name }}</h5>
            </div>
            <ul class="list-group list-group-flush">
                <li class="list-group-item">
                    <form method="POST" action="/order/create/">
                        {% csrf_token %}
                        {% for field in form %}
                        <div class="form-group">
                            {% ifnotequal field.name 'product' %}
                            <label for="{{ field.id_for_label}}">{{field.label}}</label>
                            {% endifnotequal %}
                            <input type="{{field.field.widget.input_type}}" class="form-control"
                                id="{{ field.id_for_label}}" placeholder="{{field.label}}" name="{{field.name}}"
                                value="{% ifequal field.name 'product' %}{{ product.id }}{% endifequal %}" />
                        </div>
                        {% if field.errors %}
                        <span style="color: red;">{{ field.errors }}</span>
                        {% endif %}
                        {% endfor %}
                        <button type="submit" class="btn btn-primary">주문하기</button>
                    </form>
                </li>
                <li class="list-group-item">가격 : {{ product.price|intcomma }} 원</li>
                <li class="list-group-item">등록날짜 : {{ product.register_date|date:'Y-m-d H:i' }}</li>
                <!-- description은 태그이기 때문에 필터로 |safe를 해주면 html 태그로 만들어준다! -->
                <li class="list-group-item">{{ product.description|safe }}</li>
            </ul>
        </div>
    </div>
    <div class="row">
        <div class="col-12">
            <a href="/product/">목록보기</a>
        </div>
    </div>
</div>
{% endblock %}

02. product의 views.py 수정!

from django.shortcuts import render, redirect
from .forms import RegisterForm
from django.views.generic.edit import FormView

# Create your views here.


class OrderCreate(FormView):
    # 화면은 따로 구연할 필요 없기 때문에 template_name = 은 생략
    form_class = RegisterForm
    success_url = '/product/'

    # form을 생성할 때 어떤 인자값을 전달해서 만들건지를 결정하는 함수
    # formview안에서도request를 전달할 수 있게 해주어야 한다!
    def get_form_kwargs(self, **kwargs):
        kw = super().get_form_kwargs(**kwargs)
        kw.update({
            # 기존에 있던 인자 값에다가 request를 포함하겠다!
            'request': self.request
        })
        return kw

03. order의 forms.py에 session이 정상적으로 저장되는지 확인!

from django import forms
from .models import Order
from product.models import Product
from fcuser.models import Fcuser


class RegisterForm(forms.Form):

    # 생성자함수
    # requst에 전달하기 위해서 생성자 함수를 생성
    # product views의 productDetaile에서 form을 생성하는 부분이 있습니다!
    # 그리고 order의 views.py에서 request를 처리한다
    def __init__(self, request, *args, **kwargs):
        # 기존에 있던 생성자를 사용할 수 있도록 super()에서 불러온다!
        super().__init__(*args, **kwargs)
        self.request = request

    quantity = forms.IntegerField(
        error_messages={
            'required': '수량을 입력해주세요.'
        }, label='수량'
    )
    # 실제로 입력받는 값이 아니라 선택한 상품의 아이디를 받아옴
    # widget=forms.HiddenInput
    product = forms.IntegerField(
        error_messages={
            'required': '재고를 입력해주세요.'
        }, label='재고', widget=forms.HiddenInput
    )

    # validate
    def clean(self):
        cleaned_data = super().clean()
        quantity = cleaned_data.get('quantity')
        product = cleaned_data.get('product')
        # 사용자 정보!, requst.session에 접근이 필요!

        print(self.request.session)

04. order의 forms.py에 Order model만들기!

from django import forms
from .models import Order
from product.models import Product
from fcuser.models import Fcuser


class RegisterForm(forms.Form):

    # 생성자함수
    # requst에 전달하기 위해서 생성자 함수를 생성
    # product views의 productDetaile에서 form을 생성하는 부분이 있습니다!
    # 그리고 order의 views.py에서 request를 처리한다
    def __init__(self, request, *args, **kwargs):
        # 기존에 있던 생성자를 사용할 수 있도록 super()에서 불러온다!
        super().__init__(*args, **kwargs)
        self.request = request

    quantity = forms.IntegerField(
        error_messages={
            'required': '수량을 입력해주세요.'
        }, label='수량'
    )
    # 실제로 입력받는 값이 아니라 선택한 상품의 아이디를 받아옴
    # widget=forms.HiddenInput
    product = forms.IntegerField(
        error_messages={
            'required': '재고를 입력해주세요.'
        }, label='재고', widget=forms.HiddenInput
    )

    # validate
    def clean(self):
        cleaned_data = super().clean()
        quantity = cleaned_data.get('quantity')
        product = cleaned_data.get('product')
        # 사용자 정보!, requst.session에 접근이 필요!
        fcuser = self.request.session.get('user')
        
        if quantity and product and fcuser:
            order = Order(
                quantity=quantity,
                product=Product.objects.get(pk=product),
                fcuser=Fcuser.objects.get(email=fcuser)
            )
            order.save()
        # 실패했을 경우에
        # 템플릿 이름이 지정되지 않아 이대로면 오류가 난다
        # 주문하기 페이지를 따로 만들지 않기 때문에 템플릿을 만들필요는 없다!
        # Order의 views에 함수를 만들고 추가하기!(self.product = product)
        else:
            self.product = product
            self.add_error('quantity', '값이 없습니다')
            self.add_error('product', '값이 없습니다')


05. 주문이 실패했을 때의 처리 order.views.py에 추가하기!

from django.shortcuts import render, redirect
from .forms import RegisterForm
from django.views.generic.edit import FormView

# Create your views here.


class OrderCreate(FormView):
    # 화면은 따로 구연할 필요 없기 때문에 template_name = 은 생략
    form_class = RegisterForm
    success_url = '/product/'

    # 실패했을 때 redirect하는 함수
    # 숫자형이기 때문에 문자열로 변경해주어야 한다
    def form_invalid(self, form):
        return redirect('/product/'+str(form.product))

    # form을 생성할 때 어떤 인자값을 전달해서 만들건지를 결정하는 함수
    # formview안에서도request를 전달할 수 있게 해주어야 한다!
    def get_form_kwargs(self, **kwargs):
        kw = super().get_form_kwargs(**kwargs)
        kw.update({
            # 기존에 있던 인자 값에다가 request를 포함하겠다!
            'request': self.request
        })
        return kw

06. views.py에 전달할 product 보내기!

from django import forms
from .models import Order
from product.models import Product
from fcuser.models import Fcuser


class RegisterForm(forms.Form):
    # 생성자함수
    # requst에 전달하기 위해서 생성자 함수를 생성
    # product views의 productDetaile에서 form을 생성하는 부분이 있습니다!
    # 그리고 order의 views.py에서 request를 처리한다
    def __init__(self, request, *args, **kwargs):
        # 기존에 있던 생성자를 사용할 수 있도록 super()에서 불러온다!
        super().__init__(*args, **kwargs)
        self.request = request

    quantity = forms.IntegerField(
        error_messages={
            'required': '수량을 입력해주세요.'
        }, label='수량'
    )
    # 실제로 입력받는 값이 아니라 선택한 상품의 아이디를 받아옴
    # widget=forms.HiddenInput
    product = forms.IntegerField(
        error_messages={
            'required': '재고를 입력해주세요.'
        }, label='재고', widget=forms.HiddenInput
    )
    # validate

    def clean(self):
        cleaned_data = super().clean()
        quantity = cleaned_data.get('quantity')
        product = cleaned_data.get('product')
        # 사용자 정보!, requst.session에 접근이 필요!
        fcuser = self.request.session.get('user')

        if quantity and product and fcuser:
            order = Order(
                quantity=quantity,
                product=Product.objects.get(pk=product),
                fcuser=Fcuser.objects.get(email=fcuser)
            )
            order.save()
        # 실패했을 경우에
        # 템플릿 이름이 지정되지 않아 이대로면 오류가 난다
        # 주문하기 페이지를 따로 만들지 않기 때문에 템플릿을 만들필요는 없다!
        # Order의 views에 함수를 만들고 추가하기!(self.product = product)
        else:
            self.product = product
            self.add_error('quantity', '값이 없습니다')
            self.add_error('product', '값이 없습니다')

07. 로그인을 하지 않은 경우에는 주문이 실행 안되게 처리!

  • 다시 원래 페이지로 돌아간다!
  • 현재 저장된 주문페이지(제가 들으면서 넣어서 몇개 있습니다!)
  • 토큰은 생겼지만 실패로 원래 페이지로 돌아오고 주문페이지에 저장 안됨!

08. 로그인 후 테스트!




상품 주문하기(3)

  • 재고 줄이기
  • 여러 모델을 처리하는 트랜젝션 적용!
  • 트랜잭션으로 만들겠다!
    • 일련의 여러 동작을 하나의 동작을 만들겠다!(전체가 다 성공하면 성공, 실패하면 rollback)
    • 주문 들어가기 + 재고 줄이기!

01. order의 forms.py에 트랜잭션 적용!

from django import forms
from .models import Order
from product.models import Product
from fcuser.models import Fcuser
# transaction 인터페이스
from django.db import transaction


class RegisterForm(forms.Form):
    # 생성자함수
    # requst에 전달하기 위해서 생성자 함수를 생성
    # product views의 productDetaile에서 form을 생성하는 부분이 있습니다!
    # 그리고 order의 views.py에서 request를 처리한다
    def __init__(self, request, *args, **kwargs):
        # 기존에 있던 생성자를 사용할 수 있도록 super()에서 불러온다!
        super().__init__(*args, **kwargs)
        self.request = request

    quantity = forms.IntegerField(
        error_messages={
            'required': '수량을 입력해주세요.'
        }, label='수량'
    )
    # 실제로 입력받는 값이 아니라 선택한 상품의 아이디를 받아옴
    # widget=forms.HiddenInput
    product = forms.IntegerField(
        error_messages={
            'required': '재고를 입력해주세요.'
        }, label='재고', widget=forms.HiddenInput
    )
    # validate

    def clean(self):
        cleaned_data = super().clean()
        quantity = cleaned_data.get('quantity')
        product = cleaned_data.get('product')
        # 사용자 정보!, requst.session에 접근이 필요!
        fcuser = self.request.session.get('user')

        if quantity and product and fcuser:
            # 이렇게 with으로 감싸서 atomic()으로 묶어주게 되면
            # with안에서 일어나는 모든 데이터베이스 관련 동작들은
            # 트랜잭션으로 처리
            with transaction.atomic():
                prod = Product.objects.get(pk=product)
                order = Order(
                    quantity=quantity,
                    product=Product.objects.get(pk=product),
                    fcuser=Fcuser.objects.get(email=fcuser)
                )
                order.save()
                # 수량만큼 재고에서 차감
                prod.stock -= quantity
                prod.save()
        # 실패했을 경우에
        # 템플릿 이름이 지정되지 않아 이대로면 오류가 난다
        # 주문하기 페이지를 따로 만들지 않기 때문에 템플릿을 만들필요는 없다!
        # Order의 views에 함수를 만들고 추가하기!(self.product = product)
        else:
            self.product = product
            self.add_error('quantity', '값이 없습니다')
            self.add_error('product', '값이 없습니다')


02. product의 product_detail에 재고 보이게 수정!

{% extends "base.html" %}

{% load humanize%}
{% block contents %}
<div class="row mt-5">
    <div class="col-12">
        <div class="card" style="width: 100%;">
            <div class="card-body">
                <h5 class="card-title">{{ product.name }}</h5>
            </div>
            <ul class="list-group list-group-flush">
                <li class="list-group-item">
                    <form method="POST" action="/order/create/">
                        {% csrf_token %}
                        {% for field in form %}
                        <div class="form-group">
                            {% ifnotequal field.name 'product' %}
                            <label for="{{ field.id_for_label}}">{{field.label}}</label>
                            {% endifnotequal %}
                            <input type="{{field.field.widget.input_type}}" class="form-control"
                                id="{{ field.id_for_label}}" placeholder="{{field.label}}" name="{{field.name}}"
                                value="{% ifequal field.name 'product' %}{{ product.id }}{% endifequal %}" />
                        </div>
                        {% if field.errors %}
                        <span style="color: red;">{{ field.errors }}</span>
                        {% endif %}
                        {% endfor %}
                        <button type="submit" class="btn btn-primary">주문하기</button>
                    </form>
                </li>
                <li class="list-group-item">가격 : {{ product.price|intcomma }} 원</li>
                <li class="list-group-item">등록날짜 : {{ product.register_date|date:'Y-m-d H:i' }}</li>
                <!-- description은 태그이기 때문에 필터로 |safe를 해주면 html 태그로 만들어준다! -->
                <!-- 재고 출력 -->
                <li class="list-group-item">재고 : {{ product.stock|intcomma }} 개</li>
                <li class="list-group-item">{{ product.description|safe }}</li>
            </ul>
        </div>
    </div>
    <div class="row">
        <div class="col-12">
            <a href="/product/">목록보기</a>
        </div>
    </div>
</div>
{% endblock %}


03. 주문해보기!



profile
아직까지는 코린이!

0개의 댓글