简介基于Django的购物商城系统源码及数据库是面向Python课程设计需求的完整参考项目。项目围绕在线购物核心流程实现了商品展示、用户注册登录、购物车管理、订单生成、多种支付方式对接、后台订单状态更新以及商品评价留言等主要功能适合计算机专业学生或Django初学者进行课程设计、毕业设计或框架实战练习。压缩包共包含226个文件以Python源码文件为主同时配有HTML页面模板、CSS样式表、JavaScript交互脚本、商品图片素材、SQL数据库文件以及项目配置文件等完整覆盖了前端展示、后端逻辑与数据存储三个方面整体大小仅1.52MB下载后可直接导入开发环境运行清晰的目录结构和应用模块划分也便于课程设计报告撰写和二次开发。目前已有66人学习使用内容来源于网络分享若涉及侵权请联系删除。整体而言资源具备较完整的电商业务闭环既可作为课程设计答辩时的功能演示基础也可以作为深入学习Django的实践范例。1. 基于Django的购物商城系统在课程设计里该做到什么程度基于Django的购物商城系统源码及数据库是Python课程设计里出现频率最高的一类方向。它最容易踩的误区是把精力放在页面数量上最后交付一堆互不关联的表单而商品、购物车、订单、库存之间的关系经不起追问。真正能拿高分的做法不是复刻一个完整电商平台而是先把“浏览商品→加入购物车→生成订单→扣减库存”这条主链路跑通再用数据库表结构把每一步都验证清楚。这个选题天然覆盖Django的ORM、视图、模板、会话和Admin后台同时也能兼顾数据库课程设计对ER图和约束的要求。下面直接按我搭这套系统的顺序把数据建模、视图流程、事务处理和答辩前要做的验证一次说清。2. 商城系统的 Django 数据建模与数据库设计商城系统的后端能力几乎都体现在数据表关系上。Django的ORM把表关系映射成Python类所以先把模型写对后面的视图和模板才有落脚点。这里以“用户、商品、购物车、订单、订单项”五张表作为最小闭环正好覆盖一个购物流程需要的全部核心实体。2.1 从商品、用户、购物车到订单的 ER 关系在设计商城库表时建议直接用Django自带的auth.User保存用户而不是重新建一张user表。这样注册、登录、session都能复用Django现成机制课程设计代码量能少很多。如果必须加手机号、收货地址这类扩展字段常见做法是再建一张user_profile表用OneToOneField关联到User而不是在Django默认用户表上直接改。下面这个表格列出最小闭环里的核心实体和需要特别注意的约束实体关键外键必须设置的约束Product无价格用 DecimalField库存不能为负CartItemuser, product同一用户和商品只能有一条记录Orderuser订单号唯一状态用 choices 限定OrderItemorder, product保存商品快照字段订单删除时级联删除这里的关键点是OrderItem不能只存product外键。因为商品的价格、名称修改之后历史订单必须保留下单那一刻的数据所以OrderItem里除了product_id之外还要单独存product_name和product_price两个快照字段。这个设计在数据库课程设计答辩中几乎必问把理由写清楚比背概念有用得多。2.2 用 Django ORM 把模型写进 models.py在Django项目里创建应用执行python manage.py startapp shop。如果是在已有项目里补模块还需要在settings.py的INSTALLED_APPS里加入shop否则后面makemigrations会提示没有变化。注册后把下面的模型代码放到shop/models.py中。from django.conf import settings from django.db import models class Product(models.Model): name models.CharField(商品名称, max_length128) price models.DecimalField(单价, max_digits10, decimal_places2) stock models.PositiveIntegerField(库存, default0) is_on_sale models.BooleanField(是否上架, defaultTrue) created_at models.DateTimeField(创建时间, auto_now_addTrue) class Meta: ordering [-created_at] def __str__(self): return self.name class CartItem(models.Model): user models.ForeignKey(settings.AUTH_USER_MODEL, on_deletemodels.CASCADE, verbose_name用户) product models.ForeignKey(Product, on_deletemodels.CASCADE, verbose_name商品) quantity models.PositiveIntegerField(数量, default1) class Meta: constraints [ models.UniqueConstraint(fields[user, product], nameunique_cart_user_product) ] def subtotal(self): return self.product.price * self.quantity class Order(models.Model): STATUS_CHOICES [ (pending, 待支付), (paid, 已支付), (cancelled, 已取消), ] user models.ForeignKey(settings.AUTH_USER_MODEL, on_deletemodels.PROTECT, verbose_name用户) order_no models.CharField(订单号, max_length32, uniqueTrue) status models.CharField(状态, max_length16, choicesSTATUS_CHOICES, defaultpending) total_amount models.DecimalField(总金额, max_digits10, decimal_places2, default0) created_at models.DateTimeField(创建时间, auto_now_addTrue) def __str__(self): return self.order_no class OrderItem(models.Model): order models.ForeignKey(Order, on_deletemodels.CASCADE, related_nameitems, verbose_name订单) product models.ForeignKey(Product, on_deletemodels.SET_NULL, nullTrue, blankTrue, verbose_name商品) product_name models.CharField(商品快照名称, max_length128) product_price models.DecimalField(商品快照单价, max_digits10, decimal_places2) quantity models.PositiveIntegerField(数量, default1)这里有几个参数需要在编码前确认。settings.AUTH_USER_MODEL是Django推荐的外键引用方式比直接from django.contrib.auth.models import User更灵活以后换自定义用户模型时不用改这里。价格用DecimalField而不用FloatField是为了避免9.9这类金额出现浮点尾差。on_deletemodels.CASCADE表示用户或商品被删时购物车记录同步删除on_deletemodels.PROTECT则表示用户被删时禁止删订单以保留交易记录。OrderItem.product外键用SET_NULL而不是CASCADE这样商品删除后订单项仍然存在配合product_name和product_price快照字段历史订单才完整。related_nameitems让 order.items.all() 能直接取到订单项列表模板和shell查询都会方便很多。2.3 迁移数据库表并选择适合课程设计的库模型写好后执行两条命令生成并应用迁移python manage.py makemigrations shop python manage.py migratemakemigrations会生成类似shop/migrations/0001_initial.py的迁移文件migrate真正把表建到数据库里。如果改了models.py后执行makemigrations没有反应先检查app是否写进INSTALLED_APPS这是新手最容易卡住的位置。默认SQLite零配置文件型数据库很适合本地演示。如果课程设计要求使用MySQL或PostgreSQL只需要修改settings.py。以MySQL为例DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: shop_db, USER: root, PASSWORD: 你的密码, HOST: 127.0.0.1, PORT: 3306, OPTIONS: {charset: utf8mb4}, } }在连接MySQL之前要先安装驱动常见命令是pip install mysqlclient。如果Windows下安装报错也可以尝试pymysql并在项目__init__.py里调用install_as_MySQLdb()但额外适配较多不如用MySQL 8加mysqlclient省心。下面是三种数据库的选型对照数据库适合场景需要注意的地方SQLite本地演示、代码量小select_for_update行锁无效MySQL课程设计常用要配置utf8mb4安装mysqlclientPostgreSQL生产级功能完整需要本地服务或Docker环境成本稍高提示如果老师要求同时体现“数据库课程设计”建议在文档里画出ER图并把外键的on_delete规则、联合唯一约束、订单快照字段单独讲一遍。这比堆页面更有说服力。3. 用 Django 视图和模板把购物流程跑起来有了模型之后最需要打通的是商品浏览和购物车两条链路。视图只做两件事把请求参数收敛成Python对象再调用ORM读写数据库。按照Django的MTV模式模板负责展示视图负责控制器逻辑。课程设计里不需要上复杂框架内置的FBV加函数装饰器已经够用。3.1 商品列表与详情页的最小实现在shop/views.py中编写商品浏览相关的视图from django.shortcuts import render, redirect, get_object_or_404 from django.contrib import messages from django.contrib.auth.decorators import login_required from django.db import transaction from .models import Product, CartItem, Order, OrderItem def product_list(request): products Product.objects.filter(is_on_saleTrue) return render(request, shop/product_list.html, {products: products}) def product_detail(request, product_id): product get_object_or_404(Product, idproduct_id) return render(request, shop/product_detail.html, {product: product})filter(is_on_saleTrue)会把下架商品过滤掉避免用户把已下架商品加进购物车。get_object_or_404在商品不存在时自动抛404比手动try/except更简洁。接着在shop/urls.py里配置路由from django.urls import path from . import views app_name shop urlpatterns [ path(, views.product_list, nameproduct_list), path(product/int:product_id/, views.product_detail, nameproduct_detail), path(cart/, views.cart_detail, namecart_detail), path(cart/add/int:product_id/, views.add_to_cart, nameadd_to_cart), path(order/create/, views.create_order, namecreate_order), ]在项目根urls.py里用include注册这个文件例如path(shop/, include(shop.urls))。加了app_name之后模板里用{% url shop:product_detail product.id %}生成地址而不是硬编码/shop/product/1/这样以后调整路由不会影响模板和视图之间的引用。3.2 加入购物车与购物车页面购物车操作的常见实现是复用Django自带的session但既然已经有User和CartItem表直接用ORM把购物车落到数据库会更清晰。在views.py里继续写login_required def add_to_cart(request, product_id): product get_object_or_404(Product, idproduct_id) cart_item, created CartItem.objects.get_or_create( userrequest.user, productproduct, defaults{quantity: 1} ) if not created: cart_item.quantity 1 cart_item.save() return redirect(shop:cart_detail) login_required def cart_detail(request): cart_items CartItem.objects.filter(userrequest.user).select_related(product) total sum(item.subtotal() for item in cart_items) return render(request, shop/cart_detail.html, {items: cart_items, total: total})get_or_create依赖2.1节里的UniqueConstraint同一用户和商品只会存在一条记录若记录刚创建created为True若已经存在则数量加一。select_related(product)在查询购物车时一次性把关联商品带上避免在模板里循环时逐条触发数据库查询。add_to_cart用redirect而不是render是为了防止刷新页面时浏览器重复提交同一个POST符合POST/Redirect/GET模式。商品详情页里的加入购物车表单必须带上CSRF tokenform action{% url shop:add_to_cart product.id %} methodpost {% csrf_token %} button typesubmit加入购物车/button /form3.3 订单创建与库存扣减的简单事务下面的创建订单代码是整个项目里最关键的部分。从购物车生成订单时扣减库存和创建订单必须放在同一个数据库事务里否则会出现“订单建了但库存没扣”或“库存扣了订单失败”的情况。同时要用服务端库存作为判断依据不能相信前端传回来的数量。login_required def create_order(request): cart_items list(CartItem.objects.filter(userrequest.user).select_for_update()) if not cart_items: messages.warning(request, 购物车是空的) return redirect(shop:cart_detail) try: with transaction.atomic(): order Order.objects.create( userrequest.user, order_nogenerate_order_no(), statuspending, total_amount0 ) total 0 for item in cart_items: product Product.objects.select_for_update().get(iditem.product_id) if product.stock item.quantity: raise ValueError(f{product.name} 库存不足) product.stock - item.quantity product.save() OrderItem.objects.create( orderorder, productproduct, product_nameproduct.name, product_priceproduct.price, quantityitem.quantity ) total product.price * item.quantity order.total_amount total order.save() CartItem.objects.filter(userrequest.user).delete() except ValueError as exc: messages.error(request, str(exc)) return redirect(shop:cart_detail) return redirect(shop:order_detail, order_idorder.id) def order_detail(request, order_id): order get_object_or_404(Order, pkorder_id, userrequest.user) return render(request, shop/order_detail.html, {order: order})订单号可以用时间戳加随机串生成import uuid from datetime import datetime def generate_order_no(): return datetime.now().strftime(%Y%m%d%H%M%S) uuid.uuid4().hex[:6]transaction.atomic保证下面所有ORM操作要么全部提交、要么全部回滚。select_for_update会给选中的购物车记录和商品行加锁防止两个用户同时购买最后一件库存。注意SQLite不支持行锁真正并发场景要切到MySQL或PostgreSQL但课程设计文档里写出“在数据库层面做了并发控制”本身就是加分项。订单号用时间戳加随机串避免并发下主键重复也比自增id更能追溯到下单时间。还需要把订单详情路由追加到urlpatternspath(order/int:order_id/, views.order_detail, nameorder_detail),订单状态建议保留三个值方便后续扩展状态值显示名可写点pending待支付创建订单时写入paid已支付模拟支付回调或管理后台cancelled已取消管理后台或超时任务课程设计里能做到pending到paid的管理后台切换已经算流程闭环。若想再进一步可以加一个模拟支付的按钮把订单状态从pending改成paid但核心事务逻辑不需要变化。4. 购物商城联调中的 Django 验证与易踩坑商城代码写完不代表交付完成。课程设计评分最看重的三点是源码能跑、数据库能重建、逻辑自洽。这一章用Admin后台、测试用例和常见错误排查把验证过程补完整。4.1 用 Django Admin 查看模型关系并快速造数据Django Admin是调试阶段的利器。在shop/admin.py中注册商品和订单from django.contrib import admin from .models import Product, Order, OrderItem class ProductAdmin(admin.ModelAdmin): list_display (name, price, stock, is_on_sale) list_editable (stock, is_on_sale) list_filter (is_on_sale,) admin.site.register(Product, ProductAdmin) class OrderItemInline(admin.TabularInline): model OrderItem extra 0 admin.register(Order) class OrderAdmin(admin.ModelAdmin): list_display (order_no, user, status, total_amount, created_at) list_editable (status,) inlines [OrderItemInline]创建超级用户后访问 /admin/就能直接在界面上看到商品和订单。list_editable会在列表页生成可编辑控件把库存改为0不需要进入编辑页。OrderItemInline让订单编辑页里直接显示订单项也比命令行查数据直观。答辩时现场演示Admin里改一个商品价格再刷新订单页能快速体现快照字段的价值。4.2 用 Django shell 或测试用例验证库存扣减和订单快照如果只在浏览器里操作一遍很多边界问题不会暴露。建议在shop/tests.py里写一个最小测试至少覆盖“购物车2件商品、生成订单后库存减少2”这条链路from django.test import TestCase from django.contrib.auth.models import User from .models import Product, CartItem class ShopFlowTestCase(TestCase): def test_order_stock_deduction(self): user User.objects.create_user(stu, passwordpass) product Product.objects.create(name测试商品, price12.5, stock5) CartItem.objects.create(useruser, productproduct, quantity2) self.client.force_login(user) response self.client.post(/shop/order/create/) product.refresh_from_db() self.assertEqual(response.status_code, 302) self.assertEqual(product.stock, 3)运行python manage.py test shop -v 2看到OK即说明创建订单事务里的扣库存逻辑生效。注意post的路径必须和urls.py里的shop/order/create/一致且需要先登录。force_login是测试客户端提供的方法不用真的输入密码。这个测试如果通过答辩时可以直接拿出来当验证证据。4.3 模板、CSRF 和迁移数据库时最常见的坑课程设计和外包项目的最大区别是老师会在新环境里重新跑一遍。最容易翻车的几个位置用表格记下来现象原因处理办法表单提交返回403模板里缺少{% csrf_token %}在form内部加上该标签新加的商品页面看不到is_on_sale默认False在Admin里把上架状态打勾商品金额出现浮点误差用了FloatField存金额模型改DecimalField后重新迁移NoReverseMatchapp_name与include的namespace不一致统一用app_name shop这里要特别强调执行迁移之前如果库里已有旧数据修改模型约束可能报错。比如给CartItem加UniqueConstraint时如果原来就有重复的(user, product)记录migrate会失败。常见做法是先清理重复数据再执行迁移。常见工具可以用Django shell做一次按分组取第一条的处理但如果是课程设计通常在代码开发阶段就建好约束不要拖到答辩前才改模型。另外create_order这类POST视图前面一定要加login_required否则未登录用户直接构造POST会拿不到request.user导致外键约束错误。多人协作时还会遇到数据库连接配置冲突出现Access denied for user优先检查settings.py里的HOST是否真的指向127.0.0.1而不是远程服务器。5. 给购物商城源码做最后加分fixtures 数据导出让答辩不背锅代码和数据库一起交付时最怕的是老师换台电脑跑起来后商品列表是空的又要从Admin重新录入。用Django的fixtures机制可以避免这个问题。5.1 用 dumpdata 导出一份与源码配套的初始数据在项目根目录执行python manage.py dumpdata shop --indent 2 -o shop/fixtures/initial_shop.jsonDjango会自动识别每个app下的fixtures目录。执行后得到一份JSON里面包含了Product、CartItem、Order等模型当前的数据。交付源码时把这个JSON文件一起放进仓库新环境跑完migrate后执行python manage.py loaddata initial_shop就能把商品目录和演示订单恢复出来。这个文件与SQLite、MySQL、PostgreSQL都兼容比直接拷贝数据库文件更干净。5.2 把 SECRET_KEY 和数据库密码从 settings.py 里拆出来既然要交付源码就不应该把真实密码写在settings.py里。常见做法是用环境变量覆盖import os SECRET_KEY os.environ.get(DJANGO_SECRET_KEY, course-design-dev-key) DEBUG os.environ.get(DJANGO_DEBUG, True).lower() true如果机器上没有设置环境变量就用默认的开发key如果设置了就用环境变量里的值。数据库密码同样可以用os.environ.get(DB_PASSWORD)读取然后在老师运行项目时通过bashrc或.env临时注入。生产环境不能保留这种默认key但课程设计为了降低跑通成本可以接受如果想写得更严谨可以在key缺失时抛出ImproperlyConfigured。5.3 答辩演示时的 60 秒验证序列我习惯在演示前按三个命令过一遍先python manage.py migrate --plan确认所有迁移文件都在再python manage.py test shop -v 2让自动化测试跑一遍最后演示python manage.py loaddata initial_shop把初始数据灌进去。这三步做完源码和数据库的关联就闭环了。如果时间允许再打开Admin把订单状态从pending改成paid然后展示订单总额不变、商品快照不变说明历史订单没有受商品改价影响。把dumpdata命令写进README验收老师在新环境下一键恢复初始数据源码和数据库就真正成为一体。本文还有配套的精品资源点击获取