尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Django服装进销存系统开发实战:从源码解析到部署上线

Django服装进销存系统开发实战:从源码解析到部署上线 简介基于Django的服装仓库进销存管理系统源码面向服装行业信息化开发人员及Python/Django学习者解决服装商品在尺码、颜色、款式等多属性维度下的库存、采购、销售与财务一体化管理问题。项目覆盖商品信息管理、库存预警、采购入库、销售出库、后台管理、用户认证等核心业务模块体现了Django MTV架构的实际落地方式。压缩包共149个文件大小582KB以57个py源码与46个pyc编译文件为核心辅以15个html模板、5个js与4个css前端样式、sqlite3数据库及sql脚本另有静态字体资源与说明文档目录结构清晰便于直接运行和二次开发。通过源码可系统学习Django模型建表、视图业务逻辑、模板渲染、表单验证、Admin后台配置及路由映射等关键环节也可借鉴服装行业进销存的完整设计方案。目前已有71人学习适合希望快速搭建同类系统或深入理解Django开发流程的读者。1. 从“源码.zip”到能跑通的进销存这套 Django 项目到底解决什么问题如果你在带小仓、管电商后端或者刚接了一个“帮服装店做库存系统”的私活大概率会先搜“基于Django的服装仓库进销存管理系统源码”。这个标题拆开看其实是三件事Django是技术底座服装仓库是业务场景进销存是核心诉求。而源码.zip这个后缀恰恰说明绝大多数人希望拿到的是“解压就能跑”的东西而不是从零搭框架。但实际落地上一套进销存系统真正难的不是 CRUD而是“单据、库存、账目”三者怎么保持一致。服装行业又有特殊规则同一款衣服按“颜色尺码”拆成多个 SKU入库、出库、退货、调拨都要落到最小 SKU 级别否则月底盘账必然对不上。Django 自带 ORM、Admin、迁移机制和事务控制恰好能把这些业务规则组织得清晰。文章会从项目结构开始逐步讲清楚数据模型设计、核心流程实现、生产部署和排错技巧。无论你最终是改源码做二次开发还是参考这套设计自己写一个都能在这里找到可直接复用的路径。2. 拆开压缩包先搞清这套 Django 项目的目录结构与依赖关系拿到源码.zip后的第一件事不是双击解压而是先确认这个压缩包是“完整项目”还是“只有部分文件”。常见做法是把压缩包解压到一个不含中文和空格的路径如D:\projects\garment_warehouse然后用tree或直接看目录结构。正规 Django 项目在根目录下应该包含manage.py、项目配置目录通常与项目同名、应用目录可能有多个 app以及requirements.txt或Pipfile。我一般会用下面的命令先看一眼目录全貌cd /d D:\projects\garment_warehouse tree /F /A输出大致会包含│ manage.py │ requirements.txt │ db.sqlite3 │ ├─inventory │ │ settings.py │ │ urls.py │ │ wsgi.py │ │ asgi.py │ │ celery.py │ │ │ ├─apps │ │ ├─goods │ │ ├─warehouse │ │ ├─purchase │ │ ├─sales │ │ ├─reports │ │ └─userstree /F /A中的/F显示每个目录下的文件名/A用纯文本符号代替图形符号避免终端乱码。看到这种按业务拆分的多 app 结构说明源码组织得比较规范如果只有一个myapp包打天下后续加功能会比较痛苦。2.1 依赖分析requirements.txt 里的版本搭配直接决定能不能跑起来依赖文件是源码能否在你的机器上复现运行的关键。最常见的问题是版本不兼容Django 4.x 与mysqlclient的某个版本可能编译失败Django 3.2 与djangorestframework3.14 的组合也会出现 urls 命名空间的兼容性差异。先打开requirements.txt确认依赖清单再看是否与你的 Python 版本匹配。python --version pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple-i参数指定了清华 PyPI 镜像国内网络环境下可以显著加快安装速度。如果requirements.txt中用的是Django3.2,4.0那么 Python 3.8 到 3.10 均可如果写的是Django4.2.4建议使用 Python 3.10 或 3.11避免出现AttributeError: module os has no attribute add_dll_directory一类的问题。2.2 创建虚拟环境不让全局 Python 环境变成依赖泥潭源码包里的依赖版本是作者当时锁定的不代表与你本机全局环境兼容。我在接触任何 Django 源码项目时都会先建一个独立的虚拟环境把系统环境隔离开。Python 3.3 内置venv模块不需要额外安装python -m venv venv venv\Scripts\activate # Windows source venv/bin/activate # Linux/macOS pip install --upgrade pip setuptools wheel pip install -r requirements.txtvenv的创建速度很快但它只创建解释器和基础工具所有包都要重新安装。激活后命令行前会出现(venv)前缀这是判断是否处于虚拟环境中的最直观标识。如果没有看到前缀说明激活失败Windows 下通常是因为 PowerShell 执行策略限制改用 CMD 执行即可。2.3 数据库初始化为什么第一步要跑 migrate 而不是 runserver很多新手拿到源码包直接python manage.py runserver结果页面报错no such table: auth_user。原因是这套 Django 项目虽然有db.sqlite3文件但源码包里附带的数据文件可能是空的或者模型已经改过没有生成对应迁移文件。正确操作是清理掉原有的db.sqlite3如果是测试阶段的话重新生成迁移并建表python manage.py makemigrations python manage.py migrate python manage.py createsuperusermakemigrations负责把模型变更生成迁移脚本migrate负责把脚本应用到数据库。createsuperuser创建后台管理员账号Django Admin 是这套系统最可能使用的后台入口没有账号就无法登录后台看数据。执行完后用python manage.py runserver 0.0.0.0:8000启动浏览器访问http://127.0.0.1:8000/admin测试登录。如果作者提供了data.sql或init_data.json这类初始化数据文件还需要做一步python manage.py loaddata init_data.jsonloaddata会把 JSON 或 XML 格式的 fixture 数据加载到数据库中包含基础的商品分类、供应商列表等初始数据。这一步能帮你快速看到页面有内容而不是空荡荡的白屏。加载失败时重点看报错信息是“已经存在主键冲突”还是“找不到对应的表”前者可以加--ignorenonexistent跳过部分数据后者说明迁移没有跑完。整个初始化流程跑通后这套源码包才真正成为一套可编辑、可运行的系统。不要跳过任何一步尤其是migrate否则后面的业务逻辑全部建立在错误的表结构上。调试过程中如果反复改模型也可以直接用python manage.py flush清空所有表数据但记得备份自己的测试数据。3. Django 模型层设计服装 SKU、库存流水与进销存单据的字段边界进销存系统的灵魂在数据模型不在视图函数。如果模型设计错了后面每个功能都会拧巴。这套源码里的goods、warehouse、purchase、sales四个 app 各司其职数据流的走向是商品档案 → 入库单 → 库存表 → 出库单 → 销售/领用记录。我把重点模型抽出来说明字段设计的思路。3.1 商品模型与服装 SKU 拆分为什么不能用“一个商品一个库存”服装仓库最核心的建模难点是 SKU。一件 T 恤有白色、黑色两个颜色每个颜色各有 M、L、XL 三个尺码实际库存管理的最小单元是“白色-M”或“黑色-XL”而不是“T 恤”这个抽象概念。常见做法是把基础信息和 SKU 信息分成两张表避免在商品表里重复记录大量冗余字段。# goods/models.py class Product(models.Model): name models.CharField(商品名称, max_length100) brand models.CharField(品牌, max_length50, blankTrue) category models.ForeignKey(goods.Category, on_deletemodels.PROTECT, verbose_name分类) style_no models.CharField(款号, max_length30, db_indexTrue) season models.CharField(季节, max_length10, choicesSEASON_CHOICES, defaultALL) unit models.CharField(计量单位, max_length10, default件) created_at models.DateTimeField(auto_now_addTrue) class Meta: verbose_name 商品 ordering [-created_at] class Sku(models.Model): product models.ForeignKey(Product, on_deletemodels.CASCADE, related_nameskus) color models.CharField(颜色, max_length20) size models.CharField(尺码, max_length10) barcode models.CharField(条码, max_length30, uniqueTrue) cost_price models.DecimalField(成本价, max_digits10, decimal_places2) sale_price models.DecimalField(销售价, max_digits10, decimal_places2) status models.BooleanField(启用状态, defaultTrue) class Meta: unique_together (product, color, size) verbose_name SKUstyle_no加了db_indexTrue是为了后续按款号查询更快因为在服装仓库里按款号检索远比按 ID 检索常见。unique_together保证同款同色同码不会出现重复数据这是防止库存数据漂移的第一道防线。on_deletemodels.PROTECT表示如果该分类下还有商品则不能删除分类避免出现孤儿数据。3.2 库存表与流水表的双写策略库存数量永远可以从流水重算进销存系统里的库存表是一个“快照”它记录当前此刻的结余数而库存流水表则是“事实”记录每一次数量变动的来龙去脉。两者缺一不可只留库存表无法追溯只留流水表又让每次查询都全表扫描。常见做法是在每次出入库操作时同时写流水和更新库存放在一个数据库事务里保证一致性。# warehouse/models.py class Stock(models.Model): sku models.OneToOneField(goods.Sku, on_deletemodels.CASCADE, related_namestock) warehouse models.ForeignKey(warehouse.Warehouse, on_deletemodels.CASCADE) quantity models.IntegerField(当前库存, default0) locked_quantity models.IntegerField(锁定库存, default0) updated_at models.DateTimeField(auto_nowTrue) class Meta: unique_together (sku, warehouse) verbose_name 库存 class StockLog(models.Model): LOG_TYPES ( (IN, 入库), (OUT, 出库), (RETURN, 退货), (ADJUST, 盘点调整), ) sku models.ForeignKey(goods.Sku, on_deletemodels.CASCADE, related_namelogs) log_type models.CharField(变动类型, max_length10, choicesLOG_TYPES) quantity models.IntegerField(变动数量) # 正数入库、负数出库 before_qty models.IntegerField(变动前数量) after_qty models.IntegerField(变动后数量) ref_no models.CharField(关联单号, max_length50, db_indexTrue) remark models.CharField(备注, max_length200, blankTrue) created_by models.ForeignKey(settings.AUTH_USER_MODEL, nullTrue, on_deletemodels.SET_NULL) created_at models.DateTimeField(auto_now_addTrue)locked_quantity字段的作用是记录已经被销售订单预留但还没实际出库的数量比如客户下单 10 件仓库还没发货这 10 件要锁定。可用库存 quantity - locked_quantity这样既能防止超卖又能在取消订单时直接解锁。before_qty和after_qty这两个字段看起来冗余但它们在排查问题时价值极大——可以直接看出数量是从 5 变到 3而不是净变化 -2避免“一正一副互相抵消”的迷惑。ref_no用于关联原始业务单据比如采购单号PO20250110001。3.3 进销存单据模型采购、销售、退货共用一张“主表明细表”业务单据在 Django 里的建模通常采用“主单 明细”两层结构。主单保存全局信息供应商/客户、总金额、状态、制单人明细保存每一行商品的 SKU、单价、数量。这是进销存系统的经典范式它允许一张单据多行商品也方便做审批流和后续对账。# purchase/models.py class PurchaseOrder(models.Model): STATUS ( (DRAFT, 草稿), (CONFIRMED, 已确认), (PARTIAL, 部分入库), (COMPLETED, 已完成), (CANCELLED, 已取消), ) po_no models.CharField(采购单号, max_length30, uniqueTrue) supplier models.ForeignKey(purchase.Supplier, on_deletemodels.PROTECT) status models.CharField(状态, max_length10, choicesSTATUS, defaultDRAFT) total_amount models.DecimalField(总金额, max_digits12, decimal_places2, default0) created_by models.ForeignKey(settings.AUTH_USER_MODEL, nullTrue, on_deletemodels.SET_NULL) created_at models.DateTimeField(auto_now_addTrue) class PurchaseOrderItem(models.Model): order models.ForeignKey(PurchaseOrder, on_deletemodels.CASCADE, related_nameitems) sku models.ForeignKey(goods.Sku, on_deletemodels.PROTECT) quantity models.PositiveIntegerField(采购数量) unit_price models.DecimalField(采购单价, max_digits10, decimal_places2) received_qty models.PositiveIntegerField(已入库数量, default0)这里重点注意PurchaseOrderItem中的received_qty字段。它的作用是跟踪采购单“部分入库”的情况——供应商送来 100 件但仓库只验收了 80 件还有 20 件没到。如果直接把采购单状态置为完成后续对账会对不上。正确的做法是received_qty累加每次入库数量当received_qty quantity时主单自动变为COMPLETED否则保持PARTIAL。3.4 进销存业务模型与 Django Admin 的配合误区大多数人会把这套系统直接依托 Django Admin 使用开源源码里也基本都是这个套路。但 Admin 默认以“表单 列表”的方式展现并不提供连带的“录单 → 审核 → 入库”流程控制。直接在生产环境用 Admin 录入进销存单据是有风险的员工误点删除按钮会导致整单数据连同明细被级联删除恢复极难。所以我会建议保留 Admin 做数据维护和查询但把核心操作入库、出库、盘点整成独立的视图或 API。底层模型设计得再规范如果操作入口不设防库存数据早晚会烂掉。为了让你直观看到字段的作用范围下面这张表汇总核心模型的使用场景方便后续写查询时对应取数模型类所在 app用途场景关键字段Productgoods商品基础档案管理款号、品牌、季节style_no,seasonSkugoods颜色/尺码/条码维度barcode,unique_togetherStockwarehouse实时库存快照按 SKU仓库维度存quantity,locked_quantityStockLogwarehouse每次数量变动的流水审计用log_type,before_qty,after_qtyPurchaseOrderpurchase采购单主表控制整体流程状态po_no,statusPurchaseOrderItempurchase采购单明细跟踪分批到货进度received_qty后续写库存查询时优先查Stock表作为当前值要追溯某个时间段内的变化则查StockLog。必不要反着来否则查询性能会非常差。4. 一个完整的“采购入库”流程从采购单到库存变更是怎么串起来的模型设计好了接下来要跑通一条完整的业务链路。我用“采购入库”作为示例展示从创建采购单、确认单据到实际增加库存的完整代码路径。这个流程涵盖了进销存中最复杂的“主单 明细 库存联动”逻辑是这套系统最核心的可复现环节。4.1 创建采购单用 Django Form 做数据校验创建采购单本质上就是写入PurchaseOrder和PurchaseOrderItem两张表。但要做到数据安全不能直接用前端传过来的原始 JSON 一把梭。我习惯用 Django Form 做一次数据清洗与校验# purchase/forms.py from django import forms from .models import PurchaseOrder, PurchaseOrderItem class PurchaseOrderForm(forms.ModelForm): class Meta: model PurchaseOrder fields [supplier, remark] class PurchaseOrderItemForm(forms.Form): sku_id forms.IntegerField(min_value1) quantity forms.IntegerField(min_value1, max_value99999) unit_price forms.DecimalField(min_value0, max_digits10, decimal_places2)PurchaseOrderItemForm不直接绑定PurchaseOrderItem模型是因为新增时还没有主单 ID无法建立外键。先用 Form 把每一项的数据校验好确认无误后再统一创建主单和明细。quantity限制在 1 到 99999 之间避免误录负数或夸张的大数字。创建采购单的视图函数大概是这样的# purchase/views.py from django.shortcuts import render, redirect from django.db import transaction from .forms import PurchaseOrderForm, PurchaseOrderItemForm from .models import PurchaseOrder, PurchaseOrderItem def create_purchase_order(request): if request.method POST: form PurchaseOrderForm(request.POST) items_data request.POST.getlist(items) # 前端传来的明细 JSON 字符串列表 if form.is_valid(): try: with transaction.atomic(): po form.save(commitFalse) po.created_by request.user po.po_no generate_po_no() # 自定义函数生成唯一单号 po.save() for item_str in items_data: # 解析每个明细的 JSON比如 {sku_id: 1, quantity: 100, unit_price: 39.9} item_data json.loads(item_str) item_form PurchaseOrderItemForm(item_data) if item_form.is_valid(): PurchaseOrderItem.objects.create( orderpo, sku_iditem_form.cleaned_data[sku_id], quantityitem_form.cleaned_data[quantity], unit_priceitem_form.cleaned_data[unit_price], ) else: raise ValueError(f采购明细校验失败: {item_form.errors}) return redirect(purchase:order_detail, po_idpo.id) except Exception as e: return render(request, purchase/create.html, {errors: str(e)}) return render(request, purchase/create.html, {form: PurchaseOrderForm()})关键点在于transaction.atomic()包裹了整个创建过程。如果创建了主单但某一项明细校验失败整个事务回滚不会出现“有主单没有明细”的脏数据。generate_po_no()是一个生成单号的函数常见实现是“前缀 日期 当日流水号”比如PO20250125001。4.2 确认入库锁定扣减库存写流水三步采购单创建后真正让库存发生变化的是“确认入库”操作。这个步骤里不仅要增加库存数量还要尽可能地防止并发重复提交。Django 的 ORM 在处理这类操作时有一些需要注意的点。# purchase/services.py from django.db import transaction from warehouse.models import Stock, StockLog from django.db.models import F transaction.atomic def confirm_inbound(po): po: 已确认状态的采购单主表对象 返回: 本次入库的库存变动结果 for item in po.items.select_for_update(): remain item.quantity - item.received_qty if remain 0: continue # select_for_update 对库存行加锁防止并发重复入库 stock, _ Stock.objects.select_for_update().get_or_create( skuitem.sku, warehousepo.warehouse, defaults{quantity: 0, locked_quantity: 0} ) before_qty stock.quantity stock.quantity F(quantity) remain stock.save() # F 表达式更新后需要重新刷新才能得到最新值 stock.refresh_from_db() StockLog.objects.create( skuitem.sku, log_typeIN, quantityremain, before_qtybefore_qty, after_qtystock.quantity, ref_nopo.po_no, remarkf采购入库 ) item.received_qty remain item.save(update_fields[received_qty]) po.refresh_from_db() if all(it.received_qty it.quantity for it in po.items.all()): po.status COMPLETED po.save(update_fields[status]) else: po.status PARTIAL po.save(update_fields[status]) return poselect_for_update()是必须的它会在数据库层面对查询的Stock行加排它锁。如果两个请求同时处理同一个 SKU 的入库第二个请求会阻塞在锁上等第一个事务提交后才继续执行。没有这把锁两个请求都读到quantity0然后各加 100最终库存却只变成 100而不是 200。F(quantity) remain的写法是在数据库层面做加法不是在 Python 内存中先取值再加。这样能进一步减少并发场景下的数据覆盖风险。refresh_from_db()的作用是让 Python 对象的值与数据库实际值同步因为F()表达式不会自动更新 Python 侧的对象属性后续在写StockLog时要拿after_qty必须刷新。4.3 查询库存与报表给仓库主管一个能看懂的数据视图业务流程执行完毕后高频操作用户是仓库主管他们要的不是 Django Admin 的表格式列表而是能看懂“某个 SKU 现在有多少、昨天出了多少、这周会不会断码”。我通常会为这套源码补充一个库存汇总查询视图核心利用模型里已有的字段做聚合分析。# warehouse/views.py from django.db.models import Sum from goods.models import Sku from warehouse.models import Stock, StockLog from datetime import datetime, timedelta def stock_report(request, warehouse_id): today datetime.now().date() week_ago today - timedelta(days7) sku_report [] for sku in Sku.objects.filter(statusTrue).select_related(product): stock Stock.objects.filter(skusku, warehouse_idwarehouse_id).first() current_qty stock.quantity if stock else 0 week_in StockLog.objects.filter( skusku, log_typeIN, created_at__date__gteweek_ago ).aggregate(totalSum(quantity))[total] or 0 week_out StockLog.objects.filter( skusku, log_typeOUT, created_at__date__gteweek_ago ).aggregate(totalSum(quantity))[total] or 0 sku_report.append({ sku: sku, product: sku.product, current: current_qty, week_in: week_in, week_out: abs(week_out), }) return render(request, warehouse/report.html, {rows: sku_report})week_in与week_out分别聚合了最近一周的入库与出库数量。注意出库方向在StockLog里记录为负数所以aggregate后要取绝对值。这样的查询用到了两张表但聚合的范围只是单个 SKU 的数据数据量不大时代价可以接受如果 SKU 数量过万就要考虑改成一次性分组查询一次性聚合所有 SKU。报表视图里把当前库存、近一周入库、近一周出库三列显示在一张页面上仓库主管可以快速判断哪些款号处于“进得多出得少”的积压状态哪些处于“出得快补不上”的断货边缘。这个查询从需求出发比 Admin 里的筛选敏感多了。4.4 为什么不在视图里直接改库存数新手容易犯的错误是“写个按钮点击后直接把 Stock.quantity n然后保存”。这在单用户测试时没问题但生产环境中一旦有并发的销售订单和采购入库同时操作同一 SKU就会出现数据错乱。正确做法永远是通过业务流程单据间接修改库存每一次修改都留下流水记录。规则就是没有StockLog记录的库存变动都是不被承认的。5. 把源码包跑成生产服务Linux Nginx Gunicorn 的部署细节开发环境跑通只完成了一半源码包最终要部署到服务器上才能干活。Linux 服务器上部署 Django 的经典组合是Nginx Gunicorn PostgreSQL/MySQL这套方案能够稳定支撑中小规模的仓储业务并发。相比开发环境里runserver的轻量起停生产部署的配置项要多几个关键步骤。5.1 服务器环境准备与依赖同步如果用宝塔面板BT部署流程是先安装 Python 项目管理器然后创建 Python 版本建议 3.10 或 3.11再通过“映射网站”的方式把源码目录指上去。更直白的命令行方式作为备选也需要了解。我把两种方式中命令行的部分写成脚本便于在无面板的纯净环境中操作。# Ubuntu 22.04 环境 sudo apt update sudo apt install -y python3.11 python3.11-venv python3.11-dev git nginx cd /var/www git clone /path/to/garment_warehouse.zip # 将 zip 包在此解压 cd garment_warehouse python3.11 -m venv venv source venv/bin/activate pip install --upgrade pip pip install -r requirements.txt pip install gunicorn生产环境与本地环境的依赖差异主要是gunicorn它是一个 Python WSGI HTTP 服务器能够直接跑 Django 的 WSGI 应用。python3.11-dev是编译某些 Python C 扩展比如Pillow、mysqlclient时的必要依赖从 apt 安装比 pip 自动编译更可靠。解压 zip 包时如果遇到权限问题用sudo unzip garment_warehouse.zip -d /var/www/garment_warehouse确保站点目录的属主是当前 Linux 用户避免后续静态文件写不进去。5.2 编辑 Django 配置文件DEBUG、ALLOWED_HOSTS、数据库连接开发时你可能会直接使用db.sqlite3但生产环境建议改用 MySQL 或 PostgreSQL因为多进程并发写同一个 SQLite 文件会频繁报“database is locked”。修改settings.py中的关键项# settings.py 生产关键配置片段 DEBUG False ALLOWED_HOSTS [your-domain.com, 你的服务器IP] # 白名单务必替换为实际域名 DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: garment_warehouse, USER: your_db_user, PASSWORD: your_db_password, HOST: 127.0.0.1, PORT: 3306, CONN_MAX_AGE: 60, # 数据库连接池保持时间 } } STATIC_ROOT /var/www/garment_warehouse/static/ MEDIA_ROOT /var/www/garment_warehouse/media/DEBUG False后 Django 不再自动提供静态文件服务所以必须执行collectstatic把所有 app 的静态资源收集到STATIC_ROOT指向的目录。ALLOWED_HOSTS不配置的话Django 会直接拒绝请求并返回 400这是很多新手部署后“页面打不开”的元凶。CONN_MAX_AGE 60可以复用 MySQL 连接减少频繁建连的开销对短时多次查询很有帮助。接着同步数据库并收集静态文件python manage.py makemigrations python manage.py migrate python manage.py collectstatic --noinputcollectstatic --noinput会以非交互模式把所有静态文件复制到STATIC_ROOT。之后还要给该目录写权限sudo chown -R www-data:www-data /var/www/garment_warehouse/static/否则 Nginx 用户无法读取文件。5.3 配置 Gunicorn 与 systemd 服务Gunicorn 的作用是接收来自 Nginx 转发的请求把它交给 Django 应用处理。先用命令行测试 Gunicorn 能否正常启动再用 systemd 把它封装成常驻服务gunicorn garment_warehouse.wsgi:application \ --bind 127.0.0.1:8001 \ --workers 3 \ --timeout 60 \ --access-logfile /var/log/gunicorn.access.log \ --error-logfile /var/log/gunicorn.error.loggarment_warehouse.wsgi:application指向项目配置目录下的wsgi.py中的application对象。--workers 3建议设置为CPU 核心数 × 2 1小型服务器用 3 足够。--bind 127.0.0.1:8001表示只监听本机端口不直接对外由 Nginx 做反向代理。但直接在前台跑 Gunicorn 的话关闭终端就挂了。把它写入 systemd 才能随开机自启sudo nano /etc/systemd/system/gunicorn.service文件内容[Unit] Descriptiongunicorn daemon for garment_warehouse Afternetwork.target [Service] Userwww-data Groupwww-data WorkingDirectory/var/www/garment_warehouse ExecStart/var/www/garment_warehouse/venv/bin/gunicorn \ --workers 3 \ --bind unix:/var/www/garment_warehouse/gunicorn.sock \ garment_warehouse.wsgi:application [Install] WantedBymulti-user.target这里把--bind从 TCP 端口换成了 Unix Socket 文件Nginx 直接连接本机 Socket 比走 TCP 端口性能更好也可以避免端口被外界扫描。Userwww-data是 Nginx 的默认运行用户保持一致可以避免文件权限冲突。改完执行sudo systemctl daemon-reload sudo systemctl start gunicorn sudo systemctl enable gunicornenable表示开机自启。启动报错时查看/var/log/gunicorn.error.log中的 Traceback常见的坑是ModuleNotFoundError原因是 systemd 中的ExecStart没有指向虚拟环境里的 gunicorn导致用了系统全局 Python。5.4 Nginx 反向代理配置静态文件与动态请求分流Nginx 的职责有两块一是把/static/和/media/直接以文件形式返回二是把其余请求转发给 Gunicorn Socket。后者俗称“反向代理”配置如下server { listen 80; server_name your-domain.com; client_max_body_size 50M; location /static/ { alias /var/www/garment_warehouse/static/; } location /media/ { alias /var/www/garment_warehouse/media/; } location / { include proxy_params; proxy_pass http://unix:/var/www/garment_warehouse/gunicorn.sock; } }client_max_body_size 50M是允许上传文件比如商品图片的最大体积。proxy_params通常包含Host、X-Real-IP等请求头信息如果没有这个文件可以在location /里手动写proxy_set_header Host $host;等配置。配置检查sudo nginx -t sudo systemctl reload nginxnginx -t是 Nginx 的配置语法检查工具有任何错误会在输出中直接指出。reload可以在不中断服务的情况下让新配置生效。部署完成后打http://your-domain.com如果能打开首页并且登录后台可以正常操作说明整个链路已经通了。剩下的就是按真实业务调整MEDIA_ROOT里的图片目录、邮箱告警、日志轮转等细节。6. 排错进阶三个高频异常的处理套路以及用测试脚本验证库存变动把源码包改到一半、部署上去就报错这是常态。我梳理了这套进销存项目里最容易遇到的问题对应处理方法写在下面遇到类似报错直接照方抓药。其中最常见的是django.urls.exceptions.NoReverseMatch它是 URL 反向解析失败的典型异常。6.1 NoReverseMatch 反向解析失败的处理方法NoReverseMatch表示模板或视图里写了{% url purchase:detail po.id %}但 Django 在 URLconf 中找不到名为detail的路径或者该路径需要两个参数只传了一个。排查顺序是先看purchase/urls.py中的app_name和路由 name 是否正确。先在视图渲染前手动验证路由是否正确python manage.py shell进入 Django shell 后执行from django.urls import reverse print(reverse(purchase:detail, args[1]))如果返回/purchase/order/1/说明 URLconf 正常如果抛出NoReverseMatch则检查urls.py中path的name参数是否与模板{% url %}写的一致。模板里的purchase:detail意味着在purchase/urls.py中必须先用app_name purchase定义命名空间。另一种常见错误是替换了旧版本的include没有把app_name带上导致反向解析找不到命名空间。6.2 静态文件 404为什么每次部署完样式全丢页面能打开但 CSS、JS 全部 404大概率不是 Django 的问题而是 Nginx 没有找到STATIC_ROOT目录。执行python manage.py collectstatic后检查static/目录是否存在且非空。如果在 Nginx 配置中配了alias /var/www/garment_warehouse/static/;还要注意目录末尾的斜杠不能省略。collectstatic之后如果新增了自定义静态文件需要重新执行一次。一个快速确认的方法是用curl直接请求静态文件curl -I http://your-domain.com/static/admin/css/base.css如果返回404 Not Found代表 Nginx 指向的文件路径不对如果返回403 Forbidden则是目录权限不足需要调整www-data对目录的读权限。命令里的-I参数只获取 HTTP 响应头不下载整个文件排错时响应速度快。6.3 库存数据不一致用 shell 脚本自动验证流水与库存是否对得上比部署更棘手的是业务数据问题——库存表的数据和底层流水不一致。这类问题一靠事务解决二靠事后检测。写一个简单的检测脚本可以对所有 SKU 计算“流水累计变动”和“当前库存”的差值快速定位哪种业务操作导致了异常# script/check_stock.py import os os.environ.setdefault(DJANGO_SETTINGS_MODULE, garment_warehouse.settings) import django django.setup() from django.db.models import Sum from goods.models import Sku from warehouse.models import Stock, StockLog abnormal [] for sku in Sku.objects.all().iterator(): total_in StockLog.objects.filter(skusku, log_typeIN).aggregate(sSum(quantity))[s] or 0 total_out StockLog.objects.filter(skusku, log_typeOUT).aggregate(sSum(quantity))[s] or 0 calc_qty total_in total_out # 因为出库是负数 stock Stock.objects.filter(skusku).first() actual_qty stock.quantity if stock else 0 if calc_qty ! actual_qty: abnormal.append({ sku_id: sku.id, barcode: sku.barcode, calc_qty: calc_qty, actual_qty: actual_qty, }) if abnormal: print(f发现 {len(abnormal)} 个 SKU 数据不一致:) for item in abnormal[:20]: print(item) else: print(所有库存数据一致)脚本执行方式是python script/check_stock.py需要手动设置DJANGO_SETTINGS_MODULE环境变量才能在独立进程中正确初始化 Django 配置。如果StockLog的OUT类型记录的是负数那么total_in total_out就得出净变化量如果记录的是正数出库就需要改成total_in - total_out。这一点要结合源码具体实现来调整。检测出不一致后再针对该 SKU 的流水明细逐条检查看是哪一次操作漏写了流水或没更新库存。6.4 用 Django TestCase 让核心流程“跑完不烂”排错修完后为防止回归给进销存核心流程写一个冒烟测试。这个测试只覆盖“采购入库 → 库存增加 → 流水记录”这一条链路跑一遍就能验证核心逻辑没有在代码修改中被破坏# warehouse/tests.py from django.test import TestCase from django.contrib.auth.models import User from goods.models import Product, Sku from warehouse.models import Warehouse, Stock, StockLog from purchase.models import PurchaseOrder, PurchaseOrderItem from purchase.services import confirm_inbound class InboundFlowTest(TestCase): def setUp(self): self.user User.objects.create_user(tester, testexample.com, pass123) product Product.objects.create(name测试T恤, style_noT001) self.sku Sku.objects.create( productproduct, color白, sizeL, barcodeT001-W-L, cost_price20.00, sale_price39.00 ) self.warehouse Warehouse.objects.create(name主仓) self.po PurchaseOrder.objects.create( po_noPO20250125001, warehouseself.warehouse, supplier_id1, # 需要在 setUp 中实际创建 statusDRAFT, created_byself.user ) def test_inbound_updates_stock_and_log(self): PurchaseOrderItem.objects.create( orderself.po, skuself.sku, quantity100, unit_price20.00 ) confirm_inbound(self.po) stock Stock.objects.get(skuself.sku, warehouseself.warehouse) self.assertEqual(stock.quantity, 100) log StockLog.objects.get(skuself.sku, log_typeIN) self.assertEqual(log.quantity, 100) self.assertEqual(log.after_qty, 100)setUp方法内的数据在每个测试方法执行前都会重建测试之间互不干扰。TestCase类会在测试结束自动回滚数据库事务不会污染现有数据。执行只需要一条命令python manage.py test warehouse这个测试如果失败要么是confirm_inbound里事务处理出错要么是模型字段与预期不符能够快速定位回归点。在二次开发的过程中每改一次库存相关逻辑就执行一遍测试能省下大量手工录单验证的时间。整体来看这套“基于Django的服装仓库进销存管理系统”真正要驾驭的核心链路是SKU 建模 → 单据驱动库存 → 流水可追溯 → 部署可运行。模型把业务规则说清楚事务保证数据不烂尾部署让源码变成服务验证脚本让改动不失控。按这个顺序一层层吃透源码就能把它真正变成自己的系统。本文还有配套的精品资源点击获取
返回列表