实操在线商城
一、今日学习内容概述
二、模型设计
# 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})
四、购物车流程图
五、模板实现
<!-- 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()
十一、性能优化建议
-
查询优化
- 使用select_related和prefetch_related
- 添加适当的索引
- 缓存热门商品
-
缓存策略
- 缓存商品列表
- 缓存分类信息
- 使用页面缓存
-
异步处理
- 邮件发送
- 订单状态更新
- 库存检查
-
前端优化
- 图片懒加载
- 静态资源压缩
- AJAX局部刷新
十二、扩展功能建议
-
商品功能
- 商品评价系统
- 商品收藏
- 商品推荐
-
订单功能
- 订单跟踪
- 订单导出
- 退换货处理
-
用户功能
- 会员等级
- 积分系统
- 优惠券
-
统计分析
- 销售报表
- 用户行为分析
- 库存预警
怎么样今天的内容还满意吗?再次感谢朋友们的观看,关注GZH:凡人的AI工具箱,回复666,送您价值199的AI大礼包。最后,祝您早日实现财务自由,还请给个赞,谢谢!