实操在线商城

一、今日学习内容概述

二、模型设计

# models.py
from django.db import models
from django.contrib.auth.models import User
from django.core.validators import MinValueValidator
from decimal import Decimal

class Category(models.Model):
    name = models.CharField('分类名称', max_length=100)
    slug = models.SlugField('URL', unique=True)
    description = models.TextField('描述', blank=True)
    image = models.ImageField('分类图片', upload_to='categories/', blank=True)
    
    class Meta:
        verbose_name = '商品分类'
        verbose_name_plural = verbose_name
        
    def __str__(self):
        return self.name

class Product(models.Model):
    category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='products')
    name = models.CharField('商品名称', max_length=200)
    slug = models.SlugField('URL', unique=True)
    description = models.TextField('商品描述')
    price = models.DecimalField('价格', max_digits=10, decimal_places=2)
    stock = models.PositiveIntegerField('库存')
    available = models.BooleanField('是否可用', default=True)
    created = models.DateTimeField('创建时间', auto_now_add=True)
    updated = models.DateTimeField('更新时间', auto_now=True)
    image = models.ImageField('商品图片', upload_to='products/')
    
    class Meta:
        verbose_name = '商品'
        verbose_name_plural = verbose_name
        ordering = ['-created']
        
    def __str__(self):
        return self.name

class Cart(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    created = models.DateTimeField('创建时间', auto_now_add=True)
    updated = models.DateTimeField('更新时间', auto_now=True)
    
    class Meta:
        verbose_name = '购物车'
        verbose_name_plural = verbose_name
    
    def get_total_price(self):
        return sum(item.get_cost() for item in self.items.all())

class CartItem(models.Model):
    cart = models.ForeignKey(Cart, related_name='items', on_delete=models.CASCADE)
    product = models.ForeignKey(Product, on_delete=models.CASCADE)
    quantity = models.PositiveIntegerField('数量', default=1)
    
    class Meta:
        verbose_name = '购物车项目'
        verbose_name_plural = verbose_name
        
    def get_cost(self):
        return self.product.price * self.quantity

class Order(models.Model):
    STATUS_CHOICES = [
        ('pending', '待支付'),
        ('paid', '已支付'),
        ('shipped', '已发货'),
        ('completed', '已完成'),
        ('cancelled', '已取消'),
    ]
    
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    address = models.TextField('收货地址')
    phone = models.CharField('联系电话', max_length=20)
    total_amount = models.DecimalField('总金额', max_digits=10, decimal_places=2)
    status = models.CharField('订单状态', max_length=10, choices=STATUS_CHOICES, default='pending')
    created = models.DateTimeField('创建时间', auto_now_add=True)
    updated = models.DateTimeField('更新时间', auto_now=True)
    
    class Meta:
        verbose_name = '订单'
        verbose_name_plural = verbose_name
        ordering = ['-created']
        
    def __str__(self):
        return f'Order {self.id}'

class OrderItem(models.Model):
    order = models.ForeignKey(Order, related_name='items', on_delete=models.CASCADE)
    product = models.ForeignKey(Product, on_delete=models.CASCADE)
    price = models.DecimalField('价格', max_digits=10, decimal_places=2)
    quantity = models.PositiveIntegerField('数量', default=1)
    
    class Meta:
        verbose_name = '订单项目'
        verbose_name_plural = verbose_name
        
    def get_cost(self):
        return self.price * self.quantity

三、视图实现

# views.py
from django.shortcuts import render, get_object_or_404, redirect
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from .models import Product, Cart, CartItem, Order, OrderItem

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

@login_required
def add_to_cart(request, product_id):
    product = get_object_or_404(Product, id=product_id)
    cart, created = Cart.objects.get_or_create(user=request.user)
    cart_item, item_created = CartItem.objects.get_or_create(
        cart=cart,
        product=product
    )
    
    if not item_created:
        cart_item.quantity += 1
        cart_item.save()
    
    messages.success(request, f'{product.name} 已添加到购物车')
    return redirect('cart_detail')

@login_required
def cart_detail(request):
    cart, created = Cart.objects.get_or_create(user=request.user)
    return render(request, 'shop/cart_detail.html', {'cart': cart})

@login_required
def checkout(request):
    cart = get_object_or_404(Cart, user=request.user)
    if request.method == 'POST':
        # 创建订单
        order = Order.objects.create(
            user=request.user,
            address=request.POST.get('address'),
            phone=request.POST.get('phone'),
            total_amount=cart.get_total_price()
        )
        
        # 创建订单项目
        for item in cart.items.all():
            OrderItem.objects.create(
                order=order,
                product=item.product,
                price=item.product.price,
                quantity=item.quantity
            )
        
        # 清空购物车
        cart.items.all().delete()
        
        messages.success(request, '订单创建成功!')
        return redirect('order_detail', order_id=order.id)
    
    return render(request, 'shop/checkout.html', {'cart': cart})

四、购物车流程图

每天40分玩转Django:实操在线商城-LMLPHP

五、模板实现

<!-- templates/shop/product_list.html -->
{% extends "base.html" %}

{% block content %}
<div class="container mt-4">
    <div class="row">
        <!-- 分类侧边栏 -->
        <div class="col-md-3">
            <div class="list-group">
                <a href="{% url 'product_list' %}" class="list-group-item list-group-item-action">所有商品</a>
                {% for c in categories %}
                    <a href="{{ c.get_absolute_url }}" class="list-group-item list-group-item-action">{{ c.name }}</a>
                {% endfor %}
            </div>
        </div>
        <!-- 商品列表 -->
        <div class="col-md-9">
            <div class="row">
                {% for product in products %}
                    <div class="col-md-4 mb-4">
                        <div class="card">
                            <img src="{{ product.image.url }}" class="card-img-top" alt="{{ product.name }}">
                            <div class="card-body">
                                <h5 class="card-title">{{ product.name }}</h5>
                                <p class="card-text">价格: ¥{{ product.price }}</p>
                                <form action="{% url 'add_to_cart' product.id %}" method="post">
                                    {% csrf_token %}
                                    <button type="submit" class="btn btn-primary">加入购物车</button>
                                </form>
                            </div>
                        </div>
                    </div>
                {% endfor %}
            </div>
        </div>
    </div>
</div>
{% endblock %}

<!-- templates/shop/cart_detail.html -->
{% extends "base.html" %}

{% block content %}
<div class="container mt-4">
    <h2>购物车</h2>
    {% if cart.items.all %}
        <table class="table">
            <thead>
                <tr>
                    <th>商品</th>
                    <th>单价</th>
                    <th>数量</th>
                    <th>小计</th>
                    <th>操作</th>
                </tr>
            </thead>
            <tbody>
                {% for item in cart.items.all %}
                    <tr>
                        <td>{{ item.product.name }}</td>
                        <td>¥{{ item.product.price }}</td>
                        <td>{{ item.quantity }}</td>
                        <td>¥{{ item.get_cost }}</td>
                        <td>
                            <form action="{% url 'remove_from_cart' item.id %}" method="post">
                                {% csrf_token %}
                                <button type="submit" class="btn btn-danger btn-sm">删除</button>
                            </form>
                        </td>
                    </tr>
                {% endfor %}
            </tbody>
            <tfoot>
                <tr>
                    <td colspan="3"><strong>总计</strong></td>
                    <td><strong>¥{{ cart.get_total_price }}</strong></td>
                    <td></td>
                </tr>
            </tfoot>
        </table>
        <a href="{% url 'checkout' %}" class="btn btn-primary">去结算</a>
    {% else %}
        <p>购物车是空的。</p>
    {% endif %}
</div>
{% endblock %}

六、URL配置

# urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('', views.product_list, name='product_list'),
    path('category/<slug:category_slug>/', 
         views.product_list, 
         name='product_list_by_category'),
    path('product/<int:id>/<slug:slug>/', 
         views.product_detail, 
         name='product_detail'),
    path('cart/', 
         views.cart_detail, 
         name='cart_detail'),
    path('add/<int:product_id>/',
         views.add_to_cart,
         name='add_to_cart'),
    path('remove/<int:item_id>/',
         views.remove_from_cart,
         name='remove_from_cart'),
    path('checkout/',
         views.checkout,
         name='checkout'),
    path('orders/',
         views.order_list,
         name='order_list'),
    path('order/<int:order_id>/',
         views.order_detail,
         name='order_detail'),
]

七、订单处理

# services.py
from django.db import transaction
from .models import Order, OrderItem, Cart

class OrderService:
    @staticmethod
    @transaction.atomic
    def create_order(user, address, phone):
        """创建订单"""
        cart = Cart.objects.get(user=user)
        
        # 检查库存
        for item in cart.items.all():
            if item.quantity > item.product.stock:
                raise ValueError(f'{item.product.name} 库存不足')
        
        # 创建订单
        order = Order.objects.create(
            user=user,
            address=address,
            phone=phone,
            total_amount=cart.get_total_price()
        )
        
        # 创建订单项目并更新库存
        for item in cart.items.all():
            OrderItem.objects.create(
                order=order,
                product=item.product,
                price=item.product.price,
                quantity=item.quantity
            )
            item.product.stock -= item.quantity
            item.product.save()
        
        # 清空购物车
        cart.items.all().delete()
        
        return order

八、测试代码

# tests.py
from django.test import TestCase
from django.contrib.auth.models import User
from .models import Product, Cart, CartItem
from decimal import Decimal

class ShopTest(TestCase):
    def setUp(self):
        self.user = User.objects.create_user(
            username='testuser',
            password='testpass123'
        )
        self.product = Product.objects.create(
            name='Test Product',
            price=Decimal('99.99'),
            stock=10
        )
    
    def test_add_to_cart(self):
        self.client.login(username='testuser', password='testpass123')
        response = self.client.post(f'/add/{self.product.id}/')
        
        self.assertEqual(response.status_code, 302)
        cart = Cart.objects.get(user=self.user)
        self.assertEqual(cart.items.count(), 1)
        

# tests.py 
    def test_checkout(self):
        self.client.login(username='testuser', password='testpass123')
        
        # 添加商品到购物车
        cart = Cart.objects.create(user=self.user)
        CartItem.objects.create(
            cart=cart,
            product=self.product,
            quantity=2
        )
        
        # 提交订单
        response = self.client.post('/checkout/', {
            'address': 'Test Address',
            'phone': '1234567890'
        })
        
        self.assertEqual(response.status_code, 302)
        self.assertEqual(cart.items.count(), 0)  # 购物车已清空
        
        # 检查订单是否创建成功
        order = Order.objects.filter(user=self.user).first()
        self.assertIsNotNone(order)
        self.assertEqual(order.total_amount, Decimal('199.98'))
        
    def test_stock_management(self):
        """测试库存管理"""
        self.client.login(username='testuser', password='testpass123')
        
        # 添加超出库存数量的商品
        cart = Cart.objects.create(user=self.user)
        CartItem.objects.create(
            cart=cart,
            product=self.product,
            quantity=15  # 大于库存
        )
        
        # 尝试结算
        response = self.client.post('/checkout/', {
            'address': 'Test Address',
            'phone': '1234567890'
        })
        
        # 应该返回错误
        self.assertEqual(response.status_code, 400)
        self.assertIn('库存不足', response.content.decode())

九、支付集成

# payment.py
from decimal import Decimal
import stripe
from django.conf import settings

stripe.api_key = settings.STRIPE_SECRET_KEY

class PaymentService:
    @staticmethod
    def create_payment_intent(order):
        """创建支付意图"""
        try:
            intent = stripe.PaymentIntent.create(
                amount=int(order.total_amount * 100),  # 转换为分
                currency='cny',
                metadata={
                    'order_id': order.id
                }
            )
            return intent.client_secret
        except stripe.error.StripeError as e:
            raise ValueError(f"支付创建失败: {str(e)}")
    
    @staticmethod
    def confirm_payment(payment_intent_id):
        """确认支付"""
        try:
            intent = stripe.PaymentIntent.retrieve(payment_intent_id)
            return intent.status == 'succeeded'
        except stripe.error.StripeError as e:
            raise ValueError(f"支付确认失败: {str(e)}")

# views.py 添加支付相关视图
@login_required
def payment_process(request, order_id):
    order = get_object_or_404(Order, id=order_id, user=request.user)
    
    if request.method == 'POST':
        # 创建支付意图
        try:
            client_secret = PaymentService.create_payment_intent(order)
            return render(request, 'shop/payment.html', {
                'client_secret': client_secret,
                'order': order,
                'STRIPE_PUBLIC_KEY': settings.STRIPE_PUBLIC_KEY
            })
        except ValueError as e:
            messages.error(request, str(e))
            return redirect('order_detail', order_id=order.id)
    
    return render(request, 'shop/payment.html', {'order': order})

十、异步任务处理

# tasks.py
from celery import shared_task
from django.core.mail import send_mail
from .models import Order

@shared_task
def send_order_confirmation(order_id):
    """发送订单确认邮件"""
    order = Order.objects.get(id=order_id)
    subject = f'订单确认 #{order.id}'
    message = f'''
    亲爱的 {order.user.username}:
    
    您的订单 #{order.id} 已确认。
    订单总金额:¥{order.total_amount}
    
    感谢您的购买!
    '''
    send_mail(
        subject,
        message,
        'noreply@example.com',
        [order.user.email],
        fail_silently=False,
    )

@shared_task
def check_order_timeout():
    """检查超时未支付订单"""
    from django.utils import timezone
    from datetime import timedelta
    
    timeout = timezone.now() - timedelta(hours=24)
    orders = Order.objects.filter(
        status='pending',
        created__lt=timeout
    )
    
    for order in orders:
        order.status = 'cancelled'
        order.save()

十一、性能优化建议

  1. 查询优化

    • 使用select_related和prefetch_related
    • 添加适当的索引
    • 缓存热门商品
  2. 缓存策略

    • 缓存商品列表
    • 缓存分类信息
    • 使用页面缓存
  3. 异步处理

    • 邮件发送
    • 订单状态更新
    • 库存检查
  4. 前端优化

    • 图片懒加载
    • 静态资源压缩
    • AJAX局部刷新

十二、扩展功能建议

  1. 商品功能

    • 商品评价系统
    • 商品收藏
    • 商品推荐
  2. 订单功能

    • 订单跟踪
    • 订单导出
    • 退换货处理
  3. 用户功能

    • 会员等级
    • 积分系统
    • 优惠券
  4. 统计分析

    • 销售报表
    • 用户行为分析
    • 库存预警

怎么样今天的内容还满意吗?再次感谢朋友们的观看,关注GZH:凡人的AI工具箱,回复666,送您价值199的AI大礼包。最后,祝您早日实现财务自由,还请给个赞,谢谢!

12-23 06:29