资讯动态

Python Django框架库存管理系统源码详解:从数据模型到事务并发控制

发布时间:2026/8/30 8:53:15 来源:尧图企业网站定制
简介这是一套基于Python Django框架开发的轻量级库存管理系统源码面向中小型企业管理者及Python Web开发初学者解决日常库存录入、查询、统计与可视化管理等核心需求。资源包共2000个文件总大小29.27MB涵盖1653个SVG图标资源支撑前端界面可视化、162个JavaScript交互脚本实现动态表单、搜索与增删逻辑、75个CSS样式文件含Bootstrap Icons、Font Awesome等主流UI组件库以及25个核心Python源码文件含Django模型、视图与URL路由结构清晰、模块解耦便于二次开发与功能扩展。已有729人学习下载代码注释完整配套静态资源丰富开箱即用尤其适合用于课程设计、毕业项目或企业内部简易ERP系统原型搭建可快速部署并掌握Django前后端协同开发全流程。 仓库管理这件事听起来不复杂真正做起来才知道坑有多深。我前前后后帮朋友公司做过两套库存系统第一套用Excel硬扛数据一多直接卡死对账全靠人工后来咬牙上了Django算是把这块彻底理顺了。最近看到不少人在找基于Python Django框架的库存管理系统源码问的人多了我干脆把整个开发思路、核心代码、还有那些文档里不会写的坑一次性整理出来。这篇东西适合谁看正在做毕业设计的学生、刚入职打算用Django做内部工具的后端开发、还有那些想给自己小仓库搞个管理系统的店主。我这里不写那种一键生成的商业源码而是把一个能真正跑起来的系统从设计到落地每个关键环节掰开揉碎讲清楚。你照着敲一遍收获的不是一段代码而是以后遇到类似业务都能用得上的建模思路和排错能力。1. 为什么选Django做库存管理而不是Flask或Spring Boot先聊点实在的。库存管理系统这种业务本质就是数据的增删改查外加一点统计逻辑听起来用啥框架都行但实际选型差别很大。我见过有人用Flask写了个原型越写到后面越痛苦光一个admin后台就得手撸一大堆路由和表单也见过团队硬上Spring Boot结果一个简单得不能再简单的查询接口配了一堆XML和注解小项目直接被复杂度拖垮。Django在这个场景下有个天然优势它自带Admin后台、ORM、表单校验、模板引擎、认证体系这些东西恰好是库存管理系统的地基。你可以不用Java那套笨重的企业级约束也不用像Flask那样从零开始拼积木Django把80%的公共部分给你准备好了你只需要专注业务本身。另外一个现实问题是国内Django生态非常成熟。你搜索Python Django国内使用广泛么答案基本是肯定的——豆瓣早期就是Django写的国内不少政务系统、企业内部平台、自动化运维平台都在用它。这意味着你遇到任何问题中文资料几乎都能搜到解法。相比那些冷门框架Django的求助半径大得多对新手尤其友好。Python社区里有句玩笑话人生苦短我用Python。放到库存系统这个场景我觉得改成库存苦短我用Django也成立。它内置的迁移机制migration让你改表结构跟喝水一样简单开发环境下改了模型直接跑一句makemigrations就同步到数据库不用像传统Java项目那样维护一堆SQL脚本。还有一点必须提Django的ORM对复杂查询的支持足够好。库存系统必然会涉及多表关联、聚合统计、条件筛选Django ORM用Python代码表达这些逻辑非常直观调试起来比拼SQL字符串舒服太多。后面我写库存预警那块你会看到ORM怎么帮我省掉大量原生SQL。提示如果你的项目确实只有三五个表、访问量极小那用什么框架都差别不大。但库存系统的特点是——表结构会随着业务推进不断扩张今天加个批次明天加个供应商后天加个调拨单。Django的迁移体系在这种系统演化过程中是最舒服的这点你用到中期就能体会到。2. 系统整体设计思路与数据模型拆解2.1 先理清楚库存业务到底在管什么我设计系统之前花了一整天蹲在朋友仓库里看他们实际怎么干活。这步特别重要请不要对着空气写代码。实地观察后发现一个实打实的库存系统业务链条是这么走的采购员进货 - 库管员验收入库 - 商品上架 - 销售出库 - 库存预警 - 月度盘点 - 退货/报损处理这个链条里最核心的实体不是库存本身而是商品的进出流水transaction。库存数字只是一个结果真正的数据源头是每一笔入库单和出库单。所以我的模型设计把流水记录放在比当前库存更优先的位置。那到底建几张表我第一版设计了三张核心表外加两张辅助表商品表Product存商品编码、名称、规格、单位、默认供应商库存表Stock存商品、仓库、可用数量、锁定数量、预警阈值出入库记录表StockMovement存商品、类型入库/出库/退货/报损、数量、关联单号、操作人、时间仓库表Warehouse多地多仓场景会用到单仓可省略供应商表Supplier存供应商联系方式方便采购追溯为什么把库存表单独拆出来而不是直接在商品表上加个数量字段因为同一个商品可能存放在多个仓库。如果你只给商品表加一个总数量那分仓查询的需求一来整个表结构就得重构。拆出来之后商品和仓库是多对多关系中间表就是库存表这是一步到位的设计。2.2 数据模型详细代码与设计思维下面是我的models.py核心代码这个版本是经过实际业务检验、又简化过的拿去可以直接用from django.db import models from django.contrib.auth.models import User from django.utils import timezone class Warehouse(models.Model): name models.CharField(仓库名称, max_length100, uniqueTrue) location models.CharField(仓库位置, max_length200, blankTrue) created_at models.DateTimeField(创建时间, auto_now_addTrue) def __str__(self): return self.name class Meta: db_table inventory_warehouse verbose_name 仓库 verbose_name_plural 仓库 class Supplier(models.Model): name models.CharField(供应商名称, max_length200) contact models.CharField(联系人, max_length50, blankTrue) phone models.CharField(联系电话, max_length20, blankTrue) address models.CharField(地址, max_length300, blankTrue) def __str__(self): return self.name class Meta: db_table inventory_supplier verbose_name 供应商 verbose_name_plural 供应商 class Product(models.Model): sku models.CharField(商品编码, max_length50, uniqueTrue) name models.CharField(商品名称, max_length200, db_indexTrue) spec models.CharField(规格型号, max_length100, blankTrue) unit models.CharField(单位, max_length20, default件) category models.CharField(分类, max_length50, blankTrue) supplier models.ForeignKey(Supplier, on_deletemodels.PROTECT, verbose_name默认供应商, nullTrue, blankTrue) created_at models.DateTimeField(创建时间, auto_now_addTrue) updated_at models.DateTimeField(更新时间, auto_nowTrue) def __str__(self): return f{self.sku} - {self.name} class Meta: db_table inventory_product verbose_name 商品 verbose_name_plural 商品 ordering [-created_at] class Stock(models.Model): product models.ForeignKey(Product, on_deletemodels.CASCADE, verbose_name商品, related_namestocks) warehouse models.ForeignKey(Warehouse, on_deletemodels.CASCADE, verbose_name仓库, related_namestocks) quantity models.IntegerField(可用库存, default0) locked_quantity models.IntegerField(锁定库存, default0) low_stock_threshold models.IntegerField(预警阈值, default10) updated_at models.DateTimeField(更新时间, auto_nowTrue) class Meta: db_table inventory_stock verbose_name 库存 verbose_name_plural 库存 unique_together (product, warehouse) def available_quantity(self): return self.quantity - self.locked_quantity def __str__(self): return f{self.product.name} {self.warehouse.name}: {self.available_quantity()}/{self.quantity} class StockMovement(models.Model): MOVEMENT_TYPES ( (IN, 入库), (OUT, 出库), (RETURN, 退货), (LOSS, 报损), (ADJUST, 盘点调整), ) product models.ForeignKey(Product, on_deletemodels.PROTECT, verbose_name商品, related_namemovements) warehouse models.ForeignKey(Warehouse, on_deletemodels.PROTECT, verbose_name仓库) movement_type models.CharField(类型, max_length10, choicesMOVEMENT_TYPES) quantity models.IntegerField(数量) balance_after models.IntegerField(操作后库存, default0) reference_no models.CharField(关联单号, max_length50, blankTrue) note models.CharField(备注, max_length300, blankTrue) operator models.ForeignKey(User, on_deletemodels.SET_NULL, nullTrue, verbose_name操作人) created_at models.DateTimeField(操作时间, auto_now_addTrue, db_indexTrue) def __str__(self): return f{self.get_movement_type_display()} {self.product.name} x{self.quantity} class Meta: db_table inventory_stock_movement verbose_name 出入库流水 verbose_name_plural 出入库流水 ordering [-created_at]这里有几个设计细节我要重点说明都是实际踩过坑之后才悟出来的第一on_delete参数不能随便选。商品表关联供应商用的是PROTECT。意思是有商品还挂着这个供应商你就不能把供应商删掉只能先改掉商品的供应商关联再删。我一开始用CASCADE图省事结果有一次误删供应商连带把一批商品全删了差点出事故。库存相关的数据宁可麻烦一点不能让它静默消失。第二Stock表加了唯一约束unique_together。没有这个约束你在代码里用get_or_create去维护库存记录并发场景下很容易插入两条相同商品、相同仓库的记录。加了唯一约束数据库层面就帮你兜底了这是比代码逻辑更可靠的防线。第三流水表里有个balance_after字段。这个字段记录的是这条流水发生之后库存变成了多少。很多人觉得没必要觉得库存可以实时算出来。但你做月度对账的时候会发现有历史快照的流水记录回溯起来不要太爽。你直接能看出某天某个操作是不是有问题而不需要把当天所有流水重放一遍。2.3 库存系统的事务处理逻辑库存系统最怕什么最怕数据不一致。想象一个场景用户下单买走了最后一件商品同时在后台管理界面管理员也把这个商品的数量从5调成了0。两个操作同时发生系统到底听谁的这里必须要使用数据库事务。Django的transaction.atomic()就是干这个用的from django.db import transaction from django.db.models import F from django.core.exceptions import ValidationError transaction.atomic def create_inbound_order(product_id, warehouse_id, quantity, operator, reference_no): 入库操作更新库存 写入流水必须在一个事务里完成 # 这里不能用 user User.objects.get(...) 而应该从视图层传入 product Product.objects.select_for_update().get(pkproduct_id) warehouse Warehouse.objects.get(pkwarehouse_id) stock, created Stock.objects.select_for_update().get_or_create( productproduct, warehousewarehouse, defaults{quantity: 0} ) stock.quantity F(quantity) quantity stock.save() # 刷新获取最新值用于写流水 stock.refresh_from_db() StockMovement.objects.create( productproduct, warehousewarehouse, movement_typeIN, quantityquantity, balance_afterstock.quantity, reference_noreference_no, operatoroperator ) return stock看到select_for_update()没有这是行级锁。事务里先把这个商品的库存行锁住别的请求只能等当前事务提交后才能操作这行数据。实际并发测试的时候你会发现不加锁两个请求同时在库存只剩1件的情况下各自减1最后结果变成-1这就出大问题了。顺便说个Python操作的小技巧F() quantity这个写法非常关键。如果你直接写stock.quantity quantity再save()在高并发下会丢更新——你先读到旧值另一个请求也读到旧值两个都加1最后只加了1。用F()表达式数据库层面原子操作这个问题直接消失。注意on_deletemodels.PROTECT和数据库层面的PROTECT约束是两回事。Django的PROTECT会阻止删除被引用对象但如果你用的MySQL且外键约束没生效比如MyISAM引擎那Django的保护也只是代码层面。建议生产环境优先用InnoDB引擎并且在数据库中确认外键约束已建立。3. 核心功能代码实现与实操过程3.1 出入库操作的序列化与视图函数库存系统的核心操作就是入和出。Django里面我习惯直接用FormView或者DRFDjango REST Framework来做不搞花里胡哨的前后端分离。如果你只需要一个内部管理系统用Django模板加少量AJAX就够了如果你以后打算做小程序或者App那直接用DRF一步到位。我这里用DRF做一个入库接口的示例这套结构是完全可以复用的from rest_framework import serializers, viewsets, status from rest_framework.decorators import action from rest_framework.response import Response from django.db import transaction from django.db.models import F from .models import Product, Stock, StockMovement, Warehouse class StockMovementSerializer(serializers.ModelSerializer): product_name serializers.CharField(sourceproduct.name, read_onlyTrue) warehouse_name serializers.CharField(sourcewarehouse.name, read_onlyTrue) class Meta: model StockMovement fields [id, product, product_name, warehouse, warehouse_name, movement_type, quantity, balance_after, reference_no, note, operator, created_at] read_only_fields [balance_after, operator, created_at] class StockMovementViewSet(viewsets.ModelViewSet): queryset StockMovement.objects.select_related(product, warehouse, operator).all() serializer_class StockMovementSerializer def perform_create(self, serializer): # 创建流水时操作人自动取当前登录用户 serializer.save(operatorself.request.user) action(detailFalse, methods[post]) def inbound(self, request): 入库接口 try: # 校验数据合法性 product Product.objects.get(pkrequest.data.get(product_id)) warehouse Warehouse.objects.get(pkrequest.data.get(warehouse_id)) quantity int(request.data.get(quantity, 0)) if quantity 0: raise ValueError(数量必须大于0) with transaction.atomic(): stock, _ Stock.objects.select_for_update().get_or_create( productproduct, warehousewarehouse, defaults{quantity: 0} ) stock.quantity F(quantity) quantity stock.save() stock.refresh_from_db() movement StockMovement.objects.create( productproduct, warehousewarehouse, movement_typeIN, quantityquantity, balance_afterstock.quantity, reference_norequest.data.get(reference_no, ), noterequest.data.get(note, ), operatorrequest.user ) return Response(StockMovementSerializer(movement).data, statusstatus.HTTP_201_CREATED) except Product.DoesNotExist: return Response({error: 商品不存在}, statusstatus.HTTP_404_NOT_FOUND) except Warehouse.DoesNotExist: return Response({error: 仓库不存在}, statusstatus.HTTP_404_NOT_FOUND) except (ValueError, TypeError) as e: return Response({error: str(e)}, statusstatus.HTTP_400_BAD_REQUEST)这个inbound接口我实际测试下来连续发100个并发请求往同一件商品入库最终库存数字完全正确。核心就是select_for_update()和F()的组合拳这是整个系统里最值得你记住的套路。3.2 库存预警功能的实现思路预警功能是关键中的关键。库存低于某个阈值系统要提醒采购员补货。我这里设计了三种提醒方式系统内预警列表在仪表盘显示哪些商品低于预警阈值邮件通知每天上午9点定时扫描给采购员发低库存邮件API接口让外部系统比如OA能拉取预警数据前端展示低库存商品的查询逻辑def get_low_stock_products(): from django.db.models import F # 用F表达式比较两个字段 low_stock Stock.objects.filter(quantity__lteF(low_stock_threshold)) # 只返回有实际库存记录的商品 return low_stock.select_related(product, warehouse).exclude(quantity__lt0)有些人的系统没有预警阈值这个字段直接代码里写死quantity 10。这太死板了。不同商品的价值和销量完全不同一盒别针和一箱芯片的补货周期能一样吗预警阈值必须做成商品或者库存维度的字段让管理员能按实际情况设置。邮件通知用Django的EmailMultiAlternativesfrom django.core.mail import EmailMultiAlternatives from django.template.loader import render_to_string def send_low_stock_email(low_stock_list): subject f【库存预警】{len(low_stock_list)}种商品库存不足 html_content render_to_string(emails/low_stock.html, {items: low_stock_list}) msg EmailMultiAlternatives(subject, , fromexample.com, [buyerexample.com]) msg.attach_alternative(html_content, text/html) msg.send()定时任务这块我推荐用django-crontab或者Celery beat。如果你只是发个邮件没必要上Celery那种庞然大物django-crontab配合服务器Cron就够了。这种够用就好的取舍在真实项目里很重要——不是技术越重越好而是越匹配需求越好。3.3 出库时的库存校验和异常处理出库比入库复杂因为你要先判断库存够不够。这个逻辑写在视图层但模型层最好也定义一个方法方便多处复用class Stock(models.Model): # ... 前面字段省略 def can_outbound(self, quantity): 判断是否能出库 return self.available_quantity() quantity def outbound(self, quantity, operator, reference_no, note): 出库操作返回流水记录 if not self.can_outbound(quantity): raise ValidationError(f库存不足可用库存 {self.available_quantity()}出库数量 {quantity}) self.quantity F(quantity) - quantity self.save() self.refresh_from_db() return StockMovement.objects.create( productself.product, warehouseself.warehouse, movement_typeOUT, quantityquantity, balance_afterself.quantity, reference_noreference_no, notenote, operatoroperator )注意这里出库数量我在业务上规定为正数存储。入库正数、出库正数靠movement_type区分。有些人习惯出库存负数数据库里就得写一堆abs()和条件判断纯属给自己找麻烦。用类型字段区分方向算合计时用SUM(CASE WHEN ... THEN ... END)逻辑清晰也方便报表。3.4 库存盘点流程设计盘点是个特殊场景不是简单的入库出库而是把账上库存调整为实际库存。核心逻辑是记录差异然后生成一条ADJUST类型的流水。transaction.atomic def adjust_stock(stock_id, actual_quantity, operator, note): stock Stock.objects.select_for_update().get(pkstock_id) diff actual_quantity - stock.quantity if diff 0: return {changed: False, message: 账实相符无需调整} # 记录调整前库存 old_quantity stock.quantity stock.quantity actual_quantity stock.save() StockMovement.objects.create( productstock.product, warehousestock.warehouse, movement_typeADJUST, quantityabs(diff), # 调整数量记录绝对值 balance_afteractual_quantity, notef{note} [调整前:{old_quantity}], operatoroperator ) return {changed: True, diff: diff}盘点这里有个坑盘点期间不能允许正常出入库操作否则库存刚调整完又被新订单覆盖。我的做法是盘点时给仓库加个盘点锁定状态所有出入库接口先检查这个状态。这属于业务流程层面的事但必须写进接口逻辑里堵住。3.5 Django Admin后台配置技巧Django原生Admin是库存系统最好的脚手架。你甚至可以在模型写好之后先用Admin做数据录入验证业务逻辑再慢慢开发定制页面。我的Admin配置长这样from django.contrib import admin from .models import Product, Stock, StockMovement, Warehouse, Supplier admin.register(Product) class ProductAdmin(admin.ModelAdmin): list_display [sku, name, category, unit, supplier, created_at] list_filter [category, supplier] search_fields [sku, name] list_per_page 20 admin.register(Stock) class StockAdmin(admin.ModelAdmin): list_display [product, warehouse, quantity, locked_quantity, low_stock_threshold] list_filter [warehouse] search_fields [product__sku, product__name] actions [mark_low_stock] admin.register(StockMovement) class StockMovementAdmin(admin.ModelAdmin): list_display [product, warehouse, movement_type, quantity, balance_after, operator, created_at] list_filter [movement_type, warehouse, created_at] search_fields [product__sku, product__name, reference_no] date_hierarchy created_at readonly_fields [balance_after, operator] def save_model(self, request, obj, form, change): obj.operator request.user super().save_model(request, obj, form, change)有个细节非常实用search_fields里可以用双下划线跨表搜索product__sku就是搜索商品的编码字段。Admin列表页默认不显示多对多或外键对象的字段你得显式指定product__name这种方式。这些细节没有文档会专门教你但实际用起来能省不少事。提示如果你的表单里涉及operator这类自动填充字段务必用readonly_fields或者重写save_model不然Admin后台会直接给你报这个字段是必填项的错新手那里经常卡壳。4. 报表统计功能的实现方案4.1 实时库存查询与过滤的组合技巧库存系统光有增删改查远远不够报表统计才是老板真正关心的。我实现了一个实时库存报表页面核心是下面这段查询from django.db.models import Sum, F, Q, DecimalField, Case, When, Value def get_stock_report(warehouse_idNone, categoryNone, low_stock_onlyFalse): queryset Stock.objects.select_related(product, warehouse).all() if warehouse_id: queryset queryset.filter(warehouse_idwarehouse_id) if category: queryset queryset.filter(product__categorycategory) if low_stock_only: queryset queryset.filter(quantity__lteF(low_stock_threshold)) # 计算库存金额假设商品有cost_price字段 queryset queryset.annotate( stock_valueCase( When(product__cost_price__isnullFalse, thenF(quantity) * F(product__cost_price)), defaultValue(0), output_fieldDecimalField(max_digits12, decimal_places2) ) ) return queryset这段代码用到了Django ORM三个很重要的能力select_related**查询时连表避免N1查询问题。库存列表页如果显示100种商品不用这个就是100次额外的商品表查询数据库直接被打爆。annotate Case/When**在数据库层面做条件计算不用把数据都拉到Python里再算效率高一个数量级。F表达式比较字段和字段直接比较这个前面已经说过了。4.2 月度出入库统计按月统计每个商品的入库量、出库量这是采购和财务最常用的功能。我的实现思路是用Django ORM的TruncMonthfrom django.db.models.functions import TruncMonth from django.db.models import Sum def get_monthly_movement_stats(yearNone): queryset StockMovement.objects.filter( created_at__yearyear or timezone.now().year ).annotate( monthTruncMonth(created_at) ).values(month, movement_type).annotate( total_quantitySum(quantity) ).order_by(month) return querysetTruncMonth是个非常好用的数据库函数它能把时间字段截断到月份。配合valuesannotate一条查询就拿到了按月的聚合结果不用写原生SQL。实际展示的时候我会在模板里用前端图表库比如ECharts画柱状图一眼就能看出哪个月出库暴增、哪个月采购堆积。数据接口一个JSON返回前端轮询或者定时刷新都行。4.3 库存周转率这个指标这个指标老板最爱看但大多数库存系统都没实现。逻辑是库存周转率 出库成本 / 平均库存。平均库存可以用(期初 期末) / 2近似。def get_turnover_rate(product_id, start_date, end_date): movements StockMovement.objects.filter( product_idproduct_id, created_at__date__range[start_date, end_date] ) total_out movements.filter(movement_typeOUT).aggregate(totalSum(quantity))[total] or 0 # 平均库存 期初库存 总入库 - 总出库 / 计算期间天数简化版 opening_stocks StockMovement.objects.filter( product_idproduct_id, created_at__date__ltstart_date ).order_by(-created_at).first() opening_qty opening_stocks.balance_after if opening_stocks else 0 ending_stock Stock.objects.get(product_idproduct_id).quantity avg_stock (opening_qty ending_stock) / 2 turnover_rate total_out / avg_stock if avg_stock 0 else 0 return { total_out: total_out, avg_stock: avg_stock, turnover_rate: round(turnover_rate, 2) }这个指标的价值在于帮决策者判断库存管理健康度。周转率太低说明压货严重资金被套在库存里太高又可能断货风险大。有了这个指标系统就不再只是个记录工具而是真正能给业务决策提供支持。5. 性能优化与部署场景的常见问题5.1 Django ORM性能优化警惕N1查询陷阱库存系统在数据量小的时候怎么查都行。但商品超过几千种、流水好几万条性能问题马上就来了。最常见的问题就是N1查询。什么叫N1比如你查了100条库存记录然后模板里循环访问stock.product.name。每访问一次ORM就发一条查询去数据库取商品表数据。100条就是100次额外查询加上本身那条一共101次。这就是N1。解法就两个select_related(product, warehouse)- 用于外键关联SQL里直接JOIN出来prefetch_related(product__supplier)- 用于多对多关系或反向关联分两次查询然后在Python内存里组装我实测过一个案例加了select_related之后库存列表接口响应时间从2.3秒降到120毫秒。就这么简单一行20倍差距。5.2 高并发下的库存扣减正确性库存系统最怕超卖——明明只有5件商品10个人同时抢购最后卖出去了8件。解决办法我在前面已经讲过核心思路事务 行级锁。再补充一个进阶技巧用乐观锁兜底。在Stock表加一个version字段每次更新的时候检查版本号updated Stock.objects.filter( pkstock.pk, versioncurrent_version ).update( quantityF(quantity) - quantity, versioncurrent_version 1 ) if updated 0: raise ValidationError(库存已被其他操作修改请重试)行级锁适合商品数量少、高频操作的场景乐观锁适合读多写少的场景。实际项目里可以两者结合短事务用悲观锁长流程比如整单出库用乐观锁。这是一种组合拳的思维方式单靠某一种锁并不能覆盖全部场景。5.3 Django项目的部署坑位源码能跑起来和能部署上线是两回事。我把部署时最容易踩的坑列一下DEBUGFalse静态文件404Django生产环境默认不处理静态文件必须用collectstatic把静态文件收集到指定目录再由Nginx托管。我见过太多人开发环境好端端一关DEBUG就白屏。# settings.py STATIC_ROOT os.path.join(BASE_DIR, staticfiles) # 部署时执行 python manage.py collectstatic --noinput数据库连接池默认配置下每个请求都新建数据库连接并发一高就报Too many connections。Django 4.0以后有了内置的CONN_MAX_AGE配置。我一般设成60几行配置解决大问题DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: inventory_db, USER: inventory_user, PASSWORD: your_password, HOST: 127.0.0.1, PORT: 3306, CONN_MAX_AGE: 60, OPTIONS: {charset: utf8mb4} } }迁移文件冲突多人协作开发时每个人生成了自己的迁移文件合并代码后经常冲突。我的经验是只在必要时合并迁移文件能用makemigrations --merge就尽量合并不要手动删迁移文件否则migrate直接凉凉。5.4 数据备份与恢复的最朴素方案库存系统核心资产是数据库所以备份策略必须提前定好。我用的是最朴素的方案# 每天凌晨2点备份保留最近7天 0 2 * * * /usr/bin/mysqldump -u inventory_user -ppassword inventory_db | gzip /backup/inventory_$(date \%Y\%m\%d).sql.gz find /backup -name inventory_*.sql.gz -mtime 7 -deleteCron表达式里的百分号记得要转义这个坑很多人不知道导致定时任务永远不执行。恢复的时候一句话gunzip /backup/inventory_20250601.sql.gz | mysql -u inventory_user -p inventory_db别小看这几行真遇到误删数据或者服务器宕机这可能是你最感谢自己写下的东西。5.5 常见报错速查表报错信息产生原因解决办法Field id doesnt have a default value数据库表主键不是自增检查模型主键字段加AutoField或确认已执行迁移OperationalError: (1054, Unknown column xxx in field list)模型改了但没迁移执行python manage.py makemigrations和migrateTemplateDoesNotExist模板目录路径配置错误检查settings.py里的TEMPLATES配置的DIRS路径STATICFILES_DIRS报错路径不存在静态文件路径配置问题在STATICFILES_DIRS中写实际存在的绝对路径用os.path.join(BASE_DIR, static)ForeignKey关联出错no such table数据库表还没创建python manage.py migrate先创建表ModuleNotFoundError: No module named django虚拟环境没激活或没安装Djangopip install django或重新激活虚拟环境并发扣库存出现负数没加事务和行锁参考3.2节代码用transaction.atomicselect_for_updateDataError: Out of range value整数超出字段范围检查IntegerFieldvsBigIntegerField库存量大的用BigIntegerField6. 这套源码的适用场景与后续扩展思路6.1 哪些场景直接套用哪些需要改造这套源码是基于多商品、多仓库、出入库流水完整记录这个模型设计的。适合直接套用的场景中小型贸易公司的进销存管理电商卖家的库存后台跟电商平台做数据同步生产企业的原材料和成品库管理餐饮门店的食材库存管理需要改造的场景带批次管理比如食品、医药有保质期要求需要在库存表上增加批次号、生产日期、失效日期字段出库时按照先进先出FIFO策略选择批次。这块核心逻辑变化较大要在Stock模型上再加一层StockBatch。带序列号管理比如电子产品每台设备有唯一序列号需要把数量概念改成明细概念。这种场景就不能用IntegerField计数量了要专门建一张序列号表来跟踪每个单品的状态。多单位换算箱和瓶的关系入库按箱出库按瓶。需要在商品表上增加base_unit和conversion_rate字段所有底层计算统一用最小单位。条码扫码支持扫码枪本质就是个键盘输入设备在输入框聚焦状态下扫一下条形码内容会像键盘输入一样进到输入框里。这块改造比较轻量前端加个监听就行。6.2 从单体到微服务的演进思路我见过不少项目一开始就用微服务结果光服务发现、配置中心、链路追踪就折腾掉一半时间。库存管理系统这种业务单体内聚就好。等真的用户量大了、团队多了再拆也不迟。真要拆的场景大概是库存服务、订单服务、商品服务各自独立部署、独立数据库服务间通过消息队列或HTTP接口通信。这时候事务一致性就成了大难题分布式事务方案TCC、Saga等复杂度直接上一个台阶。所以我的建议很明确初期单体后期按需拆别为了架构而架构。6.3 库存数据的安全审计思路库存数据涉及钱和货审计追踪不能少。我在系统里做了一个操作日志功能所有关键操作都通过Django的signals记录到一张审计表。from django.db.models.signals import post_save from django.dispatch import receiver receiver(post_save, senderStockMovement) def log_stock_movement(sender, instance, created, **kwargs): if created: AuditLog.objects.create( action_typeSTOCK_MOVEMENT, action_detailf{instance.get_movement_type_display()}: {instance.product.name} x{instance.quantity}, operatorinstance.operator, ip_addressget_client_ip(get_current_request() if hasattr(instance, _request) else None) )这里的get_current_request需要在请求中间件里自己实现并挂到当前线程上不然拿不到操作人的IP地址。这个小细节对事后追溯很有价值——出了问题能定位到具体哪个人、哪个IP、什么时间干了什么事。7. 我对这套系统的经验总结最后分享几点做库存系统以来最深刻的体会。第一业务梳理比代码重要一百倍。我第一版库存系统代码写得飞快但上线后改来改去原因是前期根本没搞懂过账和登记是两回事。库存系统真正的业务核心是资金和货权的流转记录不是简单的数量加减。你设计数据模型的时候如果能把每一笔操作会带来什么后果想清楚后面的开发会顺畅非常多。第二数据一致性怎么强调都不过分。库存出了问题往往不是技术问题而是业务人员根据错误数据分析做决策然后带来一系列连锁反应。所以我在系统里每条流水都留了balance_after快照每个操作都写审计日志宁可在记录上多花一点存储也要保证任何时间点都能还原当时库存的真实状态。这个习惯帮我好几次在客户面前赢回信任。第三系统永远在演进。你现在的需求只是出入库、库存查询但三个月后可能就冒出批次追溯先进先出自动盘点这些需求。所以设计模型时一定要预留扩展空间——用外键而不是字符串存关联关系用独立的流水表而不是在商品表里直接改数量。我当时多花了一天时间做的设计后来至少节省了三个月的返工时间这笔账非常划算。这套基于Django的库存管理系统源码整体下来大概2500行左右包含前面的模型、视图、接口、Admin配置、模板足够撑起一个小型公司的日常库存管理需求。你如果照着抄一遍运行起来再根据自己业务去改模型字段整个过程下来对Django的理解会比看十篇教程都深刻。个人建议你把核心的出入库事务逻辑单独拿出来多研究几遍。那段代码里浓缩了整个库存系统最精华的部分——在数据正确性、系统性能、代码可维护性三者之间找到平衡点。把这个想明白了库存系统对你来说就不再是个难题。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价