零基础入门python44:Django商城从空目录启动与最终验收
零基础入门python44Django商城从空目录启动与最终验收一、上一篇课后练习讲解库存边界测试应覆盖库存为 0、购买数量为 0、数量超过库存和正常购买四种情况每种情况都要断言状态码和数据库库存不能只看响应文本。上一篇课后练习完整答案上一篇练习的要求已落实到下面完整文件先运行项目测试再用 curl 对照状态码和数据库持久化结果答案要点接口测试检查状态码、响应头和数据库快照库存不足、重复下单、并发锁和事务异常都有明确断言。文件shop/tests.py完整参考答案文件完整文件shop/tests.pyfromdjango.testimportTestCasefrom.modelsimportOrderclassStockTests(TestCase):deftest_insufficient_stock_rolls_back(self):responseself.client.post(/api/orders/,{items:[]},content_typeapplication/json)self.assertEqual(response.status_code,409)self.assertEqual(Order.objects.count(),0)完整参考答案文件本篇对应的交付源码完整文件django-shop/shop/tests.pyfromdjango.contrib.authimportget_user_modelfromrest_framework.testimportAPITestCasefrom.modelsimportCategory,Order,ProductclassShopApiTest(APITestCase):defsetUp(self):self.userget_user_model().objects.create_user(usernamealice,passwordpassword123)categoryCategory.objects.create(name图书,slugbooks)self.productProduct.objects.create(categorycategory,namePython入门,slugpython,price59.00,stock5)deftest_products_are_public(self):responseself.client.get(/api/products/)self.assertEqual(response.status_code,200)self.assertEqual(response.data[0][name],Python入门)deftest_checkout_requires_login(self):responseself.client.post(/api/checkout/,{items:[{product_id:self.product.id,quantity:1}]},formatjson)self.assertEqual(response.status_code,403)deftest_session_cart_checks_stock(self):responseself.client.post(/api/cart/,{product_id:self.product.id,quantity:2},formatjson)self.assertEqual(response.status_code,201)self.assertEqual(response.data[str(self.product.id)],2)responseself.client.post(/api/cart/,{product_id:self.product.id,quantity:9},formatjson)self.assertEqual(response.status_code,400)deftest_checkout_creates_order_and_reduces_stock(self):self.client.force_authenticate(self.user)responseself.client.post(/api/checkout/,{items:[{product_id:self.product.id,quantity:2}]},formatjson)self.assertEqual(response.status_code,201)self.assertEqual(response.data[total_amount],118.00)self.product.refresh_from_db()self.assertEqual(self.product.stock,3)self.assertEqual(Order.objects.get().user,self.user)deftest_insufficient_stock_rolls_back_order(self):self.client.force_authenticate(self.user)responseself.client.post(/api/checkout/,{items:[{product_id:self.product.id,quantity:9}]},formatjson)self.assertEqual(response.status_code,400)self.assertEqual(Order.objects.count(),0)验收命令python -m pytest -qDjango 项目使用 python manage.py test。预期测试通过若失败先检查迁移、配置和事务回滚。二、最终项目结构django-shop/ ├─ manage.py ├─ config/ │ ├─ settings.py │ └─ urls.py └─ shop/ ├─ models.py ├─ serializers.py ├─ views.py ├─ services.py ├─ admin.py ├─ urls.py ├─ migrations/ └─ tests.py三、从空目录运行python-m venv.venv.venv\\Scripts\\activate pip install-r requirements.txt python manage.py migrate python manage.py createsuperuser python manage.py runserver访问/admin/创建分类和商品再用 API 完成浏览、加入购物车、登录结算和订单查询。生产环境使用 MySQL 时必须先配置DATABASES、utf8mb4 和专用数据库账号再执行完整迁移和测试。四、毕业验收清单未登录可以浏览上架商品但不能结算用户只能查看自己的订单库存不足不会创建半条订单商品价格修改不影响历史订单快照Admin 可以运营商品但不能绕过业务审计修改订单事实python manage.py test全部通过python manage.py check无问题。Django 商城阶段至此形成独立可运行项目。下一篇进入 FastAPI 博客从 ASGI、Pydantic 和依赖注入开始实现另一套后端架构。五、毕业练习为商城写一份 API README列出认证方式、请求体、状态码和错误示例并录制一次从创建商品到成功下单的终端演示。项目增量从空目录验收 Django 商城python-m venv.venv.venv\\Scripts\\python.exe-m pip install-r requirements.txt.venv\\Scripts\\python.exe manage.py migrate.venv\\Scripts\\python.exe manage.py test-v 0.venv\\Scripts\\python.exe manage.py runserver验收 Admin 商品维护、登录、购物车、下单、库存锁、DRF 商品列表和订单权限保存测试输出与关键 curl 响应。数据库迁移文件、环境变量和 seed 数据必须能从空目录复现。课后练习把商城 README 改成新同学可执行的清单包含初始化、测试、接口示例、失败场景和清理命令下一阶段进入 FastAPI 博客项目。五、上一篇练习讲解从并发测试回到可复现命令并发库存测试必须在 PostgreSQL 上跑SQLite 只用于快速单元测试。README 应明确依赖Python 3.11、Django 5、DRF、PostgreSQL 16以及迁移顺序。不要把“本机已有 db.sqlite3”当作交付物验收从空目录重新建库。六、从空目录执行的完整清单py-3.11-m venv.venv.venv\Scripts\Activate.ps1 python-m pip install--upgrade pip python-m pip install-r requirements.txtcopy.env.example.env python manage.py check python manage.py migrate--plan python manage.py migrate python manage.py createsuperuser python manage.py test-v 2 python manage.py runserver 127.0.0.1:8000.env只保存数据库地址和密钥不提交 GitCI 使用临时 PostgreSQL 服务。若 PowerShell 禁止激活可直接使用.venv\Scripts\python.exe -m pip ...这比修改全局执行策略更安全。七、毕业演示脚本Admin 创建分类和商品设置库存 5。注册普通用户并登录调用商品列表确认只能看到is_activeTrue。把商品加入购物车发送带幂等键的下单请求得到 201重复发送同 key 得到 200 且订单 id 相同。再次下单超过库存得到 409查询数据库确认库存仍为 0、没有新增订单。用户 B 查询用户 A 的订单得到 404staff 使用发货接口将 paid 订单改为 shipped。终端记录应包含请求、状态码和关键响应字段但要打码 token、cookie、密码。验收报告同时附python manage.py test和python manage.py check的原始输出。八、项目分层复盘与常见故障config负责 settings/urlsshop.models描述数据Serializer 负责 JSON 边界View 负责 HTTPservices.py负责事务和库存tests 固定业务契约。出现 404 先看 URL 路由出现 403 看认证和权限出现 409 看库存/幂等状态出现 500 先查看服务端 traceback再定位迁移或序列化字段。九、Django 阶段毕业练习把 README 改成可打印的验收清单包含初始化、迁移、测试、接口示例、失败场景和清理命令为订单增加 CSV 导出仅限 staff并写权限测试。完成后进入 FastAPI 博客项目继续沿用“完整代码 设计解释 运行结果 排错 练习”的格式。Django 阶段的最终交付不是一个能打开 Admin 的空项目而是一条可演示业务链运营建商品用户登录并加入购物车订单快照保存价格事务锁住库存DRF 返回稳定 JSON权限测试阻止越权。将这条链录成终端脚本任何新环境都能重放才算完成验收。本篇结束完整模块文件下面是交付项目中真实存在的完整文件 django-shop/shop/tests.py。它覆盖本篇新增逻辑以及前文已经完成的依赖代码复制单个函数会丢失上下文因此这里提供整份文件。fromdjango.contrib.authimportget_user_modelfromrest_framework.testimportAPITestCasefrom.modelsimportCategory,Order,ProductclassShopApiTest(APITestCase):defsetUp(self):self.userget_user_model().objects.create_user(usernamealice,passwordpassword123)categoryCategory.objects.create(name图书,slugbooks)self.productProduct.objects.create(categorycategory,namePython入门,slugpython,price59.00,stock5)deftest_products_are_public(self):responseself.client.get(/api/products/)self.assertEqual(response.status_code,200)self.assertEqual(response.data[0][name],Python入门)deftest_checkout_requires_login(self):responseself.client.post(/api/checkout/,{items:[{product_id:self.product.id,quantity:1}]},formatjson)self.assertEqual(response.status_code,403)deftest_session_cart_checks_stock(self):responseself.client.post(/api/cart/,{product_id:self.product.id,quantity:2},formatjson)self.assertEqual(response.status_code,201)self.assertEqual(response.data[str(self.product.id)],2)responseself.client.post(/api/cart/,{product_id:self.product.id,quantity:9},formatjson)self.assertEqual(response.status_code,400)deftest_checkout_creates_order_and_reduces_stock(self):self.client.force_authenticate(self.user)responseself.client.post(/api/checkout/,{items:[{product_id:self.product.id,quantity:2}]},formatjson)self.assertEqual(response.status_code,201)self.assertEqual(response.data[total_amount],118.00)self.product.refresh_from_db()self.assertEqual(self.product.stock,3)self.assertEqual(Order.objects.get().user,self.user)deftest_insufficient_stock_rolls_back_order(self):self.client.force_authenticate(self.user)responseself.client.post(/api/checkout/,{items:[{product_id:self.product.id,quantity:9}]},formatjson)self.assertEqual(response.status_code,400)self.assertEqual(Order.objects.count(),0)
