Django购物商城源码深度解析:模型、视图与部署实战 📅 发布时间:2026/9/16 4:41:19 👁 浏览次数: 简介基于Django的购物商城系统源码与数据库是经导师指导并评审98分的高分课程设计主要面向计算机专业正在完成期末大作业、毕业设计或需要项目实战练习的学生。项目难度适中代码经过本地编译和严格调试可直接运行适合用作电商类Web开发的完整参考。资源包共294个文件约11.94MB其中包含45个Python源码文件、36个HTML模板页面、10个JavaScript脚本、4个CSS样式文件及1个SQL数据库脚本另有60个pyc编译文件、图片素材和项目说明文档便于读者快速查看结构并部署运行。目前已有98人学习下载说明项目在同类课设中具备一定参考价值。通过本项目可以了解Django框架的MVT架构、商城商品展示、购物车、订单处理等核心模块结合附带的数据库脚本和文档可辅助完成从环境配置到功能测试的完整流程对提升Django项目开发能力很有帮助。1. 为什么课程设计选 Django 购物商城源码而不是从零写打开这套 Python 基于 Django 的购物商城系统源码第一眼看到的是 client.conf、main.css、reset.css、index.html而不是一排 .py 文件很多人会误以为打包错误。实际上Django 商城项目从来不是“代码文件集合”而是「应用代码 模板 静态资源 数据库」的完整组合index.html 是商城首页原型main.css 和 reset.css 控制页面表现client.conf 是部署阶段静态资源服务的参考配置。对期末大作业来说用 Django 做商城最划算ORM 省掉大量 SQL 拼接Admin 后台直接录入商品Session 解决登录态连数据库课程设计的要求也能一并覆盖。适合课程设计赶进度、需要二次开发的人也适合想快速读懂 Django 电商项目结构的一线开发者。2. 数据层先行Django 商城的模型设计与数据库初始化2.1 先看懂源码里的工程文件与 app 边界源码包解压后manage.py所在层决定了运行方式。很多下载者把index.html当成入口直接双击结果只有静态页面因为 Django 需要python manage.py runserver启动后由路由决定返回哪个模板。正常情况下工程内至少有一个项目配置目录和一个业务 app例如mall和shopmanage.py放在两者外层项目说明文档.docx与python电商项目.docx是助教要求交的文档.gitattributes只影响 Git 换行符与运行无关。需要区分项目配置和业务 app 的边界settings.py、urls.py属于项目配置models.py、views.py、admin.py属于业务 app。课程设计里真正动手改的是后者外加templates和static目录。如果资源包里的shopapp 缺少migrations目录第一次迁移会报错解决办法是保留空__init__.py后再执行makemigrations。用python manage.py startapp shop创建出来的骨架就是这么来的。2.2 商品分类、商品、购物车与订单的 models 设计商城核心表可以拆成五张商品分类、商品、购物车项、订单、订单项。用户直接用 Django 内置的User不要再建用户表既减少工作量也方便 Admin 后台直接管理。下面这份模型代码是商城最低可用的结构from django.db import models from django.contrib.auth.models import User class Category(models.Model): name models.CharField(分类名, max_length50) slug models.SlugField(URL标识, uniqueTrue) # 用于详情页 URL # 这里不写 list_display 也能跑Admin 里默认显示 __str__ class Meta: verbose_name 商品分类 verbose_name_plural verbose_name def __str__(self): return self.name class Product(models.Model): category models.ForeignKey(Category, on_deletemodels.CASCADE, related_nameproducts) name models.CharField(商品名, max_length200) price models.DecimalField(单价, max_digits10, decimal_places2) # 避免 FloatField 的浮点误差 stock models.PositiveIntegerField(库存, default0) image models.ImageField(主图, upload_toproducts/, blankTrue) on_sale models.BooleanField(上架, defaultTrue) # 下架不删数据只改状态 class Meta: ordering [-id] def __str__(self): return self.name价格字段用DecimalField而不是FloatField因为 float 的二进制存储会让0.1 0.2出现尾差电商里任何价签错误都会被老师抓住max_digits10, decimal_places2表示最大 99999999.99对课程设计足够。on_sale是上下架状态位商品列表只取on_saleTrue这样不用物理删除商品也能保证前台不显示下架内容。购物车和订单继续拆成三个模型class CartItem(models.Model): user models.ForeignKey(User, on_deletemodels.CASCADE, related_namecart_items) product models.ForeignKey(Product, on_deletemodels.CASCADE) quantity models.PositiveIntegerField(数量, default1) class Meta: unique_together (user, product) # 同一商品只保留一条购物车记录 class Order(models.Model): STATUS_CHOICES [ (pending, 待付款), (paid, 已付款), (shipped, 已发货), (done, 已完成), (cancel, 已取消), ] user models.ForeignKey(User, on_deletemodels.CASCADE, related_nameorders) created_at models.DateTimeField(auto_now_addTrue) status models.CharField(状态, max_length20, choicesSTATUS_CHOICES, defaultpending) # 字符串状态可读性好 class OrderItem(models.Model): order models.ForeignKey(Order, on_deletemodels.CASCADE, related_nameitems) product models.ForeignKey(Product, on_deletemodels.PROTECT) price models.DecimalField(成交价, max_digits10, decimal_places2) # 价格快照防止改价影响旧订单 quantity models.PositiveIntegerField(数量)CartItem用unique_together (user, product)保证同一用户同一商品只有一条购物车记录加购动作从“插入”变成“更新数量”避免购物车出现重复行。Order.status使用字符串 choices 而非整数后台下拉框显示中文状态代码里order.status paid也一眼可读。OrderItem.price是成交价快照不是外键取商品现价这样商品后续改价也不会影响历史订单金额。on_deletemodels.PROTECT表示存在订单项时禁止删除商品防手滑。2.3 迁移命令、SQLite 初始化与 MySQL 切换模型定义好之后数据库初始化按顺序执行# 按模型变更生成迁移文件 python manage.py makemigrations shop # 把迁移写入当前数据库 python manage.py migrate shop # 只看 SQL不修改数据库答辩时贴这段即可 python manage.py sqlmigrate shop 0001makemigrations根据模型变化生成迁移文件migrate把迁移写入当前数据库sqlmigrate只打印对应 SQL不改数据库。最后一条命令在答辩时非常有用老师问“你的表怎么建的”直接贴出 SQL 比口述 models.py 更有说服力。默认数据库是 SQLite源码里如果带了db.sqlite3可以直接runserver如果没带重新迁移即可。但不少学校的数据库课程设计指定 MySQL切换方法是在settings.py修改DATABASESDATABASES { default: { ENGINE: django.db.backends.mysql, NAME: mall, USER: root, PASSWORD: 你的密码, HOST: 127.0.0.1, PORT: 3306, } }驱动用pip install mysqlclient最省事在 Windows 装不上时我一般会pip install pymysql并在业务 app 的__init__.py加两行import pymysql; pymysql.install_as_MySQLdb()。切换后要重新migrate原来 SQLite 里的数据不会自动同步。下面几个检查点在初始化阶段最常用检查点命令正常结果模型语法与检测python manage.py checkSystem check identified no issues生成迁移文件python manage.py makemigrations shopMigrations for shop写入数据库python manage.py migrateApplying shop.0001 OK查看原生 SQLpython manage.py sqlmigrate shop 0001CREATE TABLE 语句出现no such table: shop_product基本就是跳过了migrate出现column product.price has no default是数据库里已有旧表模型加了非空字段删掉旧库或先给字段加default都可以。3. 视图与路由商品浏览、加购和 Django Session 购物车实现3.1 urls.py 的命名空间与动态路由配置路由层第一件事是给 app 加上app_name shop否则reverse(product_detail)会因为应用多了而找不到。下面是一组典型路由from django.urls import path from . import views app_name shop # 提供命名空间配合 reverse/{% url %} 使用 urlpatterns [ path(, views.product_list, nameproduct_list), path(product/int:pk/, views.product_detail, nameproduct_detail), path(cart/, views.cart_detail, namecart_detail), path(cart/add/int:pk/, views.add_to_cart, nameadd_to_cart), ]int:pk是 Django 的路径转换器只会匹配整数product/abc/会自动 404不用自己写正则校验。app_name加上name后模板里用{% url shop:product_list %}视图里用reverse(shop:product_detail, args[1])比写死 URL 更抗改动。调试路由时我会用django.urls.resolve(/product/1/)看当前路径到底匹配到哪个视图函数避免两个path顺序颠倒导致永远走到第一个。3.2 商品列表、详情与加购视图的代码组织视图层保持“读取数据 → 构造上下文 → render”三步不要直接在视图里拼 HTML。下面这段覆盖了商品列表、商品详情和登录后加购from django.shortcuts import render, get_object_or_404, redirect from django.contrib.auth.decorators import login_required from .models import Product, CartItem def product_list(request): products Product.objects.filter(on_saleTrue).select_related(category) # 只展示上架商品 return render(request, shop/product_list.html, {products: products}) def product_detail(request, pk): product get_object_or_404(Product, pkpk, on_saleTrue) return render(request, shop/product_detail.html, {product: product}) login_required def add_to_cart(request, pk): product get_object_or_404(Product, pkpk, on_saleTrue) 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)select_related(category)会在一条 SQL 里用 JOIN 把商品和分类查出来避免模板里product.category.name触发 N1 查询课程设计的商品数据可能只有几十条看不出差距但面试官问到性能优化时这就是加分点。get_object_or_404传入pk和on_saleTrue商品下架后直接返回 404而不是让用户看到一个能加购但结算失败的页面。login_required会强制用户先登录否则跳转到settings.LOGIN_URL如果测试时发现加购永远跳登录检查LOGIN_URL是否配置在/accounts/login/且对应路由存在。购物车详情页需要把商品信息和金额汇总出来def cart_detail(request): items CartItem.objects.filter(userrequest.user).select_related(product) total sum(item.product.price * item.quantity for item in items) return render(request, shop/cart_detail.html, {items: items, total: total})这里total是在 Python 里算的原因很简单购物车数量在 request 阶段可能刚被修改数据库里的聚合函数读不到未提交到内存的最新状态等创建订单时再落到数据库事务里。课程设计通常不要求保存收货地址但如果你想扩展可以在Order上增加receiver、phone、address三个CharField不影响现有迁移。3.3 购物车状态数据库 CartItem 与 Session 两种承载方式购物车既可以放在数据库表里也可以放在 Session 里。资源项目的源码一般用数据库方案因为它和用户关联清晰后台能直接看到谁加了什么。但生产商城为了减少无登录状态下的写入也会把临时购物车放 Session用户登录后再合并。两种方案对比如下方案优点缺点典型场景数据库 CartItem数据可靠可分析用户行为后台可见未登录用户无法使用需要建表课程设计、完整商城Django Session无需额外建表天然跟随会话重启会丢量大时占用服务端存储临时购物车、秒杀场景Redis 缓存读写快可设置过期时间要额外启动 Redis 服务生产环境高并发如果要在 Session 里实现加购核心逻辑是def add_to_cart_session(request, pk): cart request.session.get(cart, {}) if str(pk) in cart: cart[str(pk)] 1 else: cart[str(pk)] 1 request.session[cart] cart # 写回 Session 才会持久化注意键写成str(pk)而不是pk。Django 的 Session 默认序列化为 JSONJSON 对象的键只能是字符串整数键存进去再取出来会变成字符串不主动转换就会在第二次判断时出现0 in cart为 False 的情况。Session 方式还有个常见坑清空购物车要用request.session[cart] {}不要直接del request.session[cart]前者保留键方便下次覆盖后者会让后续get(cart, {})反复兜底。后台删除购物车项或清空购物车时Django 的delete()返回的是(受影响行数, 明细字典)不是布尔值。很多人在视图里写if CartItem.objects.filter(...).delete():判断是否删除成功这个条件永远是 True因为元组非空。正确做法是判断deleted_count CartItem.objects.filter(...).delete()[0]大于 0 才说明真的有记录被删。4. 模板与静态资源接入index.html、main.css、reset.css 和 client.conf 的落地方式4.1 资源里的前端文件在 Django 里的真实角色这套源码附带的前端文件是index.html、main.css、reset.css。reset.css的作用是重置浏览器默认样式比如清掉 body 的 margin、ul 的 padding让页面在不同浏览器下表现一致main.css才是商城实际的布局和主题样式。两文件同时存在是前端项目常见习惯课程设计里能体现工程化意识。index.html如果放在项目根目录而不是templates/shop/下直接双击是能显示静态页面但 Django 的render找不到它。正确做法是把index.html复制到templates/shop/并改成模板语法。判断模板是否被 Django 接管最简单的方法是看到body里是否出现{% extends %}和{% block %}纯静态 HTML 只能算原型。4.2 STATICFILES_DIRS、模板继承与 collectstatic 配置Django 不会主动读取根目录下的 CSS/JS必须告诉它静态资源在哪。settings.py里的标准配置是STATIC_URL /static/ STATICFILES_DIRS [ BASE_DIR / static, # 开发时让 runserver 直接找到静态资源 ] STATIC_ROOT BASE_DIR / staticfiles MEDIA_URL /media/ MEDIA_ROOT BASE_DIR / media # 商品主图上传到这个地方STATICFILES_DIRS是开发环境检索静态文件的目录STATIC_ROOT是部署时collectstatic收集的目标目录两者不能指向同一个目录否则命令会报错。商品主图属于上传文件走MEDIA_ROOT所以要为Product.image创建media/目录。模板里使用静态文件必须带头加载标签{% load static %} !-- 引入静态文件模板标签 -- !DOCTYPE html html langzh-CN head meta charsetUTF-8 link relstylesheet href{% static shop/reset.css %} link relstylesheet href{% static shop/main.css %} title{% block title %}购物商城{% endblock %}/title /head body {% block content %}{% endblock %} /body /html上面的base.html是整站模板骨架子模板只需要写{% extends shop/base.html %} {% block content %} div classproduct-grid {% for product in products %} div classcard h3{{ product.name }}/h3 p{{ product.price }}/p a href{% url shop:product_detail product.pk %}查看详情/a /div {% endfor %} /div {% endblock %}base.html这种模板继承方案比每个页面重复写head好维护也因为{% static %}会根据STATIC_URL自动改写路径本地开发时能立刻看到样式。模板变更后不需要重启runserver但settings.py改动一定要重启这是新手最容易忽略的。reset.css和main.css大概率位于static/shop/下如果源码包把它们放到了static/css/记得把模板里的href{% static shop/reset.css %}改为实际路径否则浏览器 404页面“变丑”不是样式问题是路径问题。4.3 client.conf 是部署参考不是运行必需源码包里的client.conf容易让人迷惑。它不是 Python 文件也不是模板更像是一份从部署环境里带出来的 Nginx 站点配置。本地开发python manage.py runserver完全不依赖它只有当你要把商城项目部署到服务器时它才有参考价值。典型的配置长这样server { listen 80; server_name mall.example.com; # 静态文件由 Nginx 直接返回不占 Django 进程 location /static/ { alias /opt/mall/static/; } location /media/ { alias /opt/mall/media/; } location / { include uwsgi_params; uwsgi_pass 127.0.0.1:8000; } }上面这份配置把/static/和/media/交给 Nginx 处理其他请求交给 Django。很多人的课程设计在本地好端端的一部署到宝塔就白屏原因就是没有collectstaticpython manage.py collectstatic会把所有 app 的静态文件复制到STATIC_ROOT之后再确认client.conf里的alias路径正确。把server_name改成测试机 IPnginx -t nginx -s reload后请求/static/shop/main.css状态码 200 就是成功。开发与部署两阶段的静态资源配置差异如下阶段命令/配置作用本地开发STATICFILES_DIRSrunserverDjango 自动处理静态文件请求正式部署collectstatic到STATIC_ROOT把所有静态文件集中到一处容器/宝塔Nginxalias指向静态目录让 Nginx 直接返回文件不占用 Django 进程如果client.conf里写的是 cache 或域名回源那说明原项目还做过 CDN 或缓存节点配置不是课程设计必需内容看懂即可不要被它带偏。5. 跑通源码的验证清单Admin 初始化、购物车命令测试与排错技巧5.1 初始化后台与商品数据先确认 Python 环境和依赖。假设你已经按 python 安装教程装好了 3.10 版本接着安装依赖pip install django pillow python manage.py check python manage.py migrate python manage.py createsuperuserpillow是ImageField的必需依赖不装会在迁移后访问商品图片时报ModuleNotFoundError。迁移成功后执行python manage.py runserver打开http://127.0.0.1:8000/admin用刚创建的超级用户登录先把Category和Product录入几条数据。如果觉得默认 Admin 界面太朴素装django-admin-interface可以美化但课程设计不建议为换肤引入额外依赖容易在答辩现场因为版本冲突翻车。5.2 用 Django Test Client 验证核心链路不打开浏览器也能验证加购逻辑。在项目根目录运行python manage.py shell然后执行from django.test import Client from django.contrib.auth.models import User from shop.models import CartItem u User.objects.get(usernameadmin) c Client() c.force_login(u) # 注入登录态无需密码 resp c.get(/cart/add/1/) print(resp.status_code, resp.url) print(CartItem.objects.filter(useru).count())force_login会直接给测试客户端注入登录态不需要密码。resp.status_code为 302 表示加购后重定向到了购物车页CartItem数量大于 0 说明数据库写操作成功。用同样的方式可以继续测/cart/页面是否返回 200不用每次都在浏览器里登录、加购、再查库。常见的排错问题都集中在下面几张表现象原因处理no such table: shop_product模型建好后没有执行迁移python manage.py migrateTemplateDoesNotExist模板路径不在templates/下检查DIRS或APP_DIRS配置Invalid HTTP_HOST headerALLOWED_HOSTS没有放行访问域名改成[*]或写入具体域名ModuleNotFoundError: MySQLdb使用 MySQL 但未装驱动pip install pymysql并 install_as_MySQLdbadmin 中文显示乱码数据库字符集不是 utf8mb4MySQL 建库时使用utf8mb4最后给一个验证部署的技巧本地启动 Django 后在浏览器控制台请求/static/shop/reset.css如果返回的不是 CSS 而是 Django 404 页面说明STATICFILES_DIRS没对上改完配置重启服务再把client.conf里的server_name改成服务器 IPnginx -t nginx -s reload后重新访问同一路径看到 200 才算真正接通静态资源链路。本文还有配套的精品资源点击获取