쇼핑 센터 건설 프로젝트

1. 상점 앱 만들기


2. 쇼핑 > models.py

from django.db import models

from django.urls import reverse

# 카테고리 모델
class Category(models.Model):

    # 카테고리 이름 결정, db_index를 true로 설정하면 카테고리 정보 저장 테이블은 이 이름 열을 인덱스로 설정
    name = models.CharField(max_length=200, db_index=True)

    # SEO(search engine optimization) => 요즘은 OG(open graph)도 넣음
    meta_description = models.TextField(blank=True)

    # 카테고리와 상품 모두에 설정, 상품명을 이용해서 url 만들기
    # allow_unicode는 영문 외 모든 언어 사용 가능
    slug = models.SlugField(max_length=200, db_index=True, unique=True, allow_unicode=True)

    # 관리자 페이지에서 보여지는 객체가 단수일 때, 복수일 때 표현하는 값 결정
    class Meta:
        ordering = ('name')
        verbose_name="category"
        verbose_name_plural="categories"

    def __str__(self):
        return self.name

    def get_absolute_url(self):
        return reverse('shop:product_in_category', args=(self.slug))


# 상품 모델
class Product(models.Model):

    # 상품 모델은 foreignkey 필드를 이용해서 카테고리 모델과 관계 설정
    # 카테고리를 삭제해도 상품은 남아있어야 하니 on_delete를 set_null로 설정
    # null 값 저장 가능하기 위해 null = true로 설정
    category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, related_name="products")
    name = models.CharField(max_length=200, db_index=True)

    slug = models.SlugField(max_length=200, db_index=True, unique=True, allow_unicode=True)

    image = models.ImageField(upload_to='products/%Y/%m/%d',blank=True)
    description = models.TextField(blank=True)
    meta_description = models.TextField(blank=True)

    # price: 제품 가격, stock: 재고
    price = models.DecimalField(max_digits=10,decimal_places=2)
    stock = models.PositiveIntegerField()

    # available_display: 상품 노출 여부, available_order: 상품 주문 가능 여부
    available_display = models.BooleanField('Display', default=True)
    available_order = models.BooleanField('Order', default=True)

    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)

    # index_together은 멀티 컬럼 색인 기능으로 id와 slug 필드를 묶음
    class Meta:
        ordering = ('-created')
        index_together = (('id','slug'))

    def __str__(self):
        return self.name

    def get_absolute_url(self):
        return reverse('shop:product_detail', args=(self.id, self.slug))

3. makemigrations를 사용하여 데이터베이스에 적용 및 마이그레이션


갑자기 오류가 발생하여 python -m pip install Pillow를 실행했는데 해결되었습니다.


4. 이제 보기를 만들어 보겠습니다.

from django.shortcuts import render, get_object_or_404

from .models import *

# get_object_or_404는 찾는 객체가 없을 경우 자동으로 404를 보여줌

# 카테고리 페이지
def product_in_category(request, category_slug=None):
    current_category = None
    categories = Category.objects.all()
    products = Product.objects.filter(available_display=True)

    # url로부터 category_slug를 찾아서 현재 어느 카테고리를 보여주는 것인지 판단
    # 선택한 카테고리가 없으면 전체 상품 목록 노출
    if category_slug:
        current_category = get_object_or_404(Category, slug=category_slug)
        products = products.filter(category=current_category)

    # filter 메서드는 여러번 쓰이지만, 실제 DB에는 단 한번 전달 (Lazy evalutation 사용하기 때문)

    return render(request, 'shop/list.html',
                  {'current_category': current_category, 'categories': categories, 'products': products})

# 제품 상세 뷰
# URL에서 slug 값을 읽어와서 해당 제품을 찾고 노출
def product_detail(request, id, product_slug=None):
    product = get_object_or_404(Product, id=id, slug=product_slug)
    return render(request, 'shop/detail.html', {'product': product})

5. URL 연결

– 생성된 보기를 제공하려면 urls.py에서 URL을 연결해야 합니다.

– shop 폴더에 urls.py 생성

from django.urls import path
from .views import *

app_name="shop"

urlpatterns = (
    # 전체 제품 노출
    path('', product_in_category, name="product_all"), 

    # 카테고리 선택이 있는 경우
    path('<slug:category_slug>/', product_in_category, name="product_in_category"),

    # 제품 상세
    path('<int:id>/<product_slug>/', product_detail, name="product_detail"),
)


6. 템플릿 만들기

템플릿 > base.html에서 만들기

<!
DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>{% block title %}{% endblock %}</title> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous"> <!
-- jquery slim 지우고 minified 추가 --> <script src="https://code.jquery.com/jquery-3.3.1.min.js" crossorigin="anonymous"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script> {% block script %} {% endblock %} {% block style %} {% endblock %} </head> <body> <nav class="navbar navbar-expand-lg navbar-light bg-light"> <a class="navbar-brand" href="http://win2dvp21.">Django Shop</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse justify-content-end" id="navbarSupportedContent"> <ul class="navbar-nav justify-content-end"> <li class="nav-item"> {% if user.is_authenticated %} <a class="nav-link" href="{% url 'account_logout' %}">Logout</a> {% else %} <a class="nav-link" href="{% url 'account_login' %}">Login</a> {% endif %} </li> <li class="nav-item "> <a class="nav-link btn btn-outline-success" href="{% url 'cart:detail' %}">Cart {% if cart|length > 0 %} ${{ cart.get_total_price }} with {{cart|length}} items {% else %} : Empty {% endif %} </a> </li> </ul> </div> </nav> <div class="container"> {% block content %} {% endblock %} </div> </body> </html>


{% 확장된 ‘base.html’ %} {% 블록 제목 %}카테고리 페이지{% endblock %} {% 블록 콘텐츠 %}

모든 {% 카테고리 %} {{c.name}} {% endfor %}

{제품의 제품 %}

제품 사진

{{상품명}}

{{제품 설명}} ${{제품.가격}}

자세히 보기

{% endfor %}

{% 끝 블록 %}

{% 확장된 ‘base.html’ %} {% 블록 제목 %}제품 세부정보{% endblock %} {% 블록 콘텐츠 %}

{{상품명}}

가격 {{제품 가격}}

{{add_to_cart}} {% csrf_token %}

설명{{product.description|줄 바꿈}}

{% 끝 블록 %}

7. 서버 실행

python manage.py 런서버

8. 관리자 페이지 등록

from django.contrib import admin

from .models import *

# 카테고리와 제품 모두 등록해서 관리하겠다
class CategoryAdmin(admin.ModelAdmin):
    list_display = ('name','slug')
    prepopulated_fields = {'slug':('name',)}

admin.site.register(Category, CategoryAdmin)

class ProductAdmin(admin.ModelAdmin):
    list_display = ('name','slug','category','price','stock','available_display','available_order','created','updated')
    list_filter = ('available_display','created','updated','category')
    prepopulated_fields = {'slug': ('name',)}
    list_editable = ('price','stock','available_display','available_order')

admin.site.register(Product, ProductAdmin)

9. 소셜 로그인 추가

– pip 설치 django-allauth