Django 2実践例 第7章:ECサイトの作成

目次

  • Django2実践例 第1章 ブログアプリケーションの作成
  • Django2実践例 第2章 ブログ機能の拡張
  • Django2実践例 第3章 ブログ機能の更なる拡張
  • Django2実践例 第4章 SNSサイトの作成
  • Django2実践例 第5章 コンテンツ共有機能
  • Django2実践例 第6章 ユーザー行動の追跡
  • Django2実践例 第7章 ECサイトの作成
  • Django2実践例 第8章 支払いと注文の管理
  • Django2実践例 第9章 店舗機能の拡張
  • Django2実践例 第10章 オンライン教育プラットフォームの作成
  • Django2実践例 第11章 コースコンテンツのレンダリングとキャッシュ
  • Django2実践例 第12章 APIの作成
  • Django2実践例 第13章 ライブ配信

第7章 ECサイトの作成

前章では、ユーザーのフォローシステムとアクティビティフロー機能を構築し、Djangoのシグナル機能の使用とRedisデータベースでの画像閲覧回数・ランキングの保存方法を学習しました。本章では、基本的なECサイトの構築方法について学習します。本章では、商品カテゴリーディレクトリの作成、セッションを使用したカート機能の実装、カスタムコンテキストマネージャーの使用、Celeryによる非同期タスクの実行について取り上げます。

1. ECサイトプロジェクトの作成

ECサイトプロジェクトを作成します。ユーザーは商品カテゴリーディレクトリを閲覧し、特定の商品をカートに追加し、最後にカートから注文を作成できます。本章で実装するECサイトの主な機能:

  • 商品カテゴリーモデルの作成と管理画面への登録、商品カテゴリービューの作成
  • ユーザーがサイトを閲覧中にカートに商品を保持するカートシステムの実装
  • 注文提出ページの作成
  • 注文が成功した後、ユーザーに非同期メール通知を送信
mkdir env
virtualenv env/myshop
source env/myshop/bin/activate
pip install Django==2.0.5
django-admin startproject myshop
cd myshop/
django-admin startapp shop
INSTALLED_APPS = [
    # ...
    'shop.apps.ShopConfig',
]

1.1 商品カテゴリーモデルの作成

from django.db import models

class ItemCategory(models.Model):
    name = models.CharField(max_length=200, db_index=True)
    slug = models.SlugField(max_length=200, db_index=True, unique=True)

    class Meta:
        ordering = ('name',)
        verbose_name = 'category'
        verbose_name_plural = 'categories'

    def __str__(self):
        return self.name

class ProductItem(models.Model):
    category = models.ForeignKey(ItemCategory, related_name='category', on_delete=models.CASCADE)
    name = models.CharField(max_length=200, db_index=True)
    slug = models.SlugField(max_length=200, db_index=True)
    image = models.ImageField(upload_to='products/%Y/%m/%d', blank=True)
    description = models.TextField(blank=True)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    available = models.BooleanField(default=True)
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ('name',)
        index_together = (('id', 'slug'),)

    def __str__(self):
        return self.name

1.2 モデルを管理画面に登録

from django.contrib import admin
from .models import ItemCategory, ProductItem

@admin.register(ItemCategory)
class CategoryAdmin(admin.ModelAdmin):
    list_display = ['name', 'slug']
    prepopulated_fields = {'slug': ('name',)}

@admin.register(ProductItem)
class ProductAdmin(admin.ModelAdmin):
    list_display = ['name', 'slug', 'price', 'available', 'created', 'updated']
    list_filter = ['available', 'created', 'updated']
    list_editable = ['price', 'available']
    prepopulated_fields = {'slug': ('name',)}

1.3 商品カテゴリービューの作成

from django.shortcuts import render, get_object_or_404
from .models import ItemCategory, ProductItem

def product_list(request, category_slug=None):
    category = None
    categories = ItemCategory.objects.all()
    products = ProductItem.objects.filter(available=True)
    if category_slug:
        category = get_object_or_404(categories, slug=category_slug)
        products = products.filter(category=category)
    return render(request, 'shop/product/list.html',
                  {'category': category, 'categories': categories, 'products': products})

1.4 商品カテゴリーテンプレートの作成

{% extends "shop/base.html" %}
{% load static %}
{% block title %}
    {% if category %}{{ category.name }}{% else %}Products{% endif %}
{% endblock %}
{% block content %}
    <div id="sidebar">
        <h3>Categories</h3>
        <ul>
            <li {% if not category %}class="selected"{% endif %}>
                <a href="{% url "shop:product_list" %}">All</a>
            </li>
            {% for c in categories %}
                <li {% if category.slug == c.slug %}class="selected"
                    {% endif %}>
                    <a href="{{ c.get_absolute_url }}">{{ c.name }}</a>
                </li>
            {% endfor %}
        </ul>
    </div>
    <div id="main" class="product-list">
        <h1>{% if category %}{{ category.name }}{% else %}Products
        {% endif %}</h1>
        {% for product in products %}
            <div class="item">
                <a href="{{ product.get_absolute_url }}">
                    <img src="
                            {% if product.image %}{{ product.image.url }}{% else %}{% static "img/no_image.png" %}{% endif %}">
                </a>
                <a href="{{ product.get_absolute_url }}">{{ product.name }}</a>
                <br>
                ${{ product.price }}
            </div>
        {% endfor %}
    </div>
{% endblock %}

2. カート機能の作成

2.1 Djangoセッションモジュールの使用

from django.conf import settings
from shop.models import ProductItem

class ShoppingCart:

    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

2.2 セッション設定

CART_SESSION_ID = 'cart'

2.3 カートデータのセッション保存

def add(self, product, quantity=1, update_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 update_quantity:
        self.cart[product_id]['quantity'] = quantity
    else:
        self.cart[product_id]['quantity'] += quantity
    self.save()

def save(self):
    self.session.modified = True

2.4 カートビューの作成

from django.views.decorators.http import require_POST
from shop.models import ProductItem
from .shopping_cart import ShoppingCart
from .forms import CartAddProductForm

@require_POST
def cart_add(request, product_id):
    cart = ShoppingCart(request)
    product = get_object_or_404(ProductItem, id=product_id)
    form = CartAddProductForm(request.POST)
    if form.is_valid():
        cd = form.cleaned_data
        cart.add(product=product, quantity=cd['quantity'], update_quantity=cd['update'])
    return redirect('cart:cart_detail')

3. 注文処理

3.1 注文モデルの作成

from django.db import models
from shop.models import ProductItem

class Order(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    email = models.EmailField()
    address = models.CharField(max_length=250)
    postal_code = models.CharField(max_length=20)
    city = models.CharField(max_length=100)
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)
    paid = models.BooleanField(default=False)

    def get_total_cost(self):
        return sum(item.get_cost() for item in self.items.all())

class OrderItem(models.Model):
    order = models.ForeignKey(Order, related_name='items', on_delete=models.CASCADE)
    product = models.ForeignKey(ProductItem, related_name='order_items', on_delete=models.CASCADE)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    quantity = models.PositiveIntegerField(default=1)

    def get_cost(self):
        return self.price * self.quantity

3.2 注文モデルを管理画面に登録

from django.contrib import admin
from .models import Order, OrderItem

class OrderItemInline(admin.TabularInline):
    model = OrderItem
    raw_id_fields = ['product']

@admin.register(Order)
class OrderAdmin(admin.ModelAdmin):
    list_display = ['id', 'first_name', 'last_name', 'email',
                    'address', 'postal_code', 'city', 'paid',
                    'created', 'updated']
    list_filter = ['paid', 'created', 'updated']
    inlines = [OrderItemInline]

3.3 注文ビューとテンプレートの作成

from django.shortcuts import render
from .models import OrderItem
from .forms import OrderCreateForm
from shopping_cart import ShoppingCart

def order_create(request):
    cart = ShoppingCart(request)
    if request.method == "POST":
        form = OrderCreateForm(request.POST)
        if form.is_valid():
            order = form.save()
            for item in cart:
                OrderItem.objects.create(order=order, product=item['product'], price=item['price'],
                                         quantity=item['quantity'])
            cart.clear()
            return render(request, 'orders/order/created.html', {'order': order})
    else:
        form = OrderCreateForm()
    return render(request, 'orders/order/create.html', {'cart': cart, 'form': form})

4. Celeryによる非同期タスクの実行

4.1 Celeryのインストール

pip install celery==4.1.0

4.2 RabbitMQのインストール

apt-get install rabbitmq

4.3 Celeryのプロジェクト統合

import os
from celery import Celery

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myshop.settings')

app = Celery('myshop')
app.config_from_object('django.conf:settings', namespace='CELERY')
app.autodiscover_tasks()

4.4 非同期タスクの追加

from celery import task
from django.core.mail import send_mail
from .models import Order

@task
def order_created(order_id):
    order = Order.objects.get(id=order_id)
    subject = 'Order {}'.format(order.id)
    message = 'Dear {},\n\nYou have successfully placed an order. Your order id is {}.'.format(order.first_name,
                                                                                               order_id)
    mail_sent = send_mail(subject, message, 'lee0709@vip.sina.com', [order.email])
    return mail_sent

4.5 Celeryの監視

pip install flower==0.9.2

タグ: Django Celery redis Session shopping-cart

6月1日 18:21 投稿