在中小型企业的日常运营中,仓库管理往往是最容易被忽视却又至关重要的环节。传统的手工记账或简单Excel管理方式,随着业务量的增长会暴露出数据易丢失、查询效率低下、库存更新不及时等问题。这正是我们选择Python+Django技术栈构建仓库管理系统的根本原因——用轻量级但足够强大的工具解决实际业务痛点。
我经手过三个不同行业的仓库管理系统改造项目,从五金配件到医疗耗材,最终沉淀下来的技术方案都指向同一个结论:Django框架的ORM层对库存变动的原子性操作、Admin后台对基础数据的快速维护、以及Python生态丰富的报表生成库,构成了仓库管理系统最坚实的技术三角。这个开源项目不仅提供了可直接部署的代码,更重要的是展示了如何用最小成本实现以下核心功能:
仓库管理系统的数据模型设计直接决定了系统的扩展性和稳定性。在models.py中,我们采用分层设计思想:
python复制class ProductCategory(models.Model):
name = models.CharField(max_length=100, unique=True)
parent = models.ForeignKey('self', null=True, blank=True) # 实现多级分类
class Product(models.Model):
sku = models.CharField(max_length=50, unique=True) # 国际标准库存单位
name = models.CharField(max_length=200)
category = models.ForeignKey(ProductCategory)
spec = models.JSONField() # 动态规格参数
min_stock = models.PositiveIntegerField() # 库存预警阈值
class StorageLocation(models.Model):
code = models.CharField(max_length=20) # 库位编码如A-01-02
capacity = models.DecimalField(max_digits=10, decimal_places=2)
class Inventory(models.Model):
product = models.ForeignKey(Product)
location = models.ForeignKey(StorageLocation)
quantity = models.PositiveIntegerField(default=0)
last_check = models.DateTimeField(auto_now=True)
特别要注意的是Inventory表的quantity字段更新必须使用F()表达式避免并发问题:
python复制Inventory.objects.filter(pk=item_id).update(quantity=F('quantity') + amount)
仓库管理最关键的在于保证库存数据的准确性。我们采用Django的transaction.atomic装饰器确保数据一致性:
python复制from django.db import transaction
@transaction.atomic
def process_stock_movement(movement_type, items):
for item in items:
inventory = Inventory.objects.select_for_update().get(
product_id=item['product_id'],
location_id=item['location_id']
)
if movement_type == 'IN':
inventory.quantity += item['amount']
else:
if inventory.quantity < item['amount']:
raise ValueError('库存不足')
inventory.quantity -= item['amount']
inventory.save()
StockTransaction.objects.create(
inventory=inventory,
amount=item['amount'],
movement_type=movement_type,
reference=item['order_no']
)
重要提示:必须使用select_for_update()锁定记录,防止并发修改导致库存数据异常。这是实际项目中最容易出现问题的环节。
在库存监控方面,我们实现了三级预警机制:
预警逻辑封装在独立服务中:
python复制class StockAlertService:
@classmethod
def check_single_product(cls, product_id):
product = Product.objects.get(pk=product_id)
total_stock = Inventory.objects.filter(
product=product
).aggregate(total=Sum('quantity'))['total'] or 0
if total_stock < product.min_stock:
alert = StockAlert.objects.create(
product=product,
current_stock=total_stock,
threshold=product.min_stock
)
cls._send_alert_notification(alert)
@staticmethod
def _send_alert_notification(alert):
# 集成邮件/短信通知
pass
针对大型仓库的海量数据查询,我们做了以下优化:
python复制class Meta:
indexes = [
models.Index(fields=['product', 'location']),
models.Index(fields=['last_check'])
]
python复制Inventory.objects.select_related('product', 'location').filter(...)
sql复制CREATE MATERIALIZED VIEW inventory_summary AS
SELECT product_id, SUM(quantity) as total
FROM warehouse_inventory
GROUP BY product_id;
推荐使用Docker Compose部署方案,docker-compose.yml关键配置:
yaml复制version: '3'
services:
db:
image: postgres:13
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- pg_data:/var/lib/postgresql/data
web:
build: .
command: gunicorn warehouse.wsgi:application --bind 0.0.0.0:8000
volumes:
- static_data:/app/staticfiles
depends_on:
- db
environment:
DATABASE_URL: postgres://postgres:${DB_PASSWORD}@db:5432/postgres
volumes:
pg_data:
static_data:
关键安全配置:
python复制SECURE_HSTS_SECONDS = 31536000 # 1年
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
python复制DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'OPTIONS': {
'sslmode': 'require'
}
}
}
仓库数据是企业核心资产,我们采用三级备份方案:
备份脚本示例:
bash复制#!/bin/bash
DATE=$(date +%Y%m%d)
pg_dump -Fc -h $DB_HOST -U $DB_USER $DB_NAME > /backups/db_$DATE.dump
find /backups -type f -mtime +30 -exec rm {} \;
现象:系统库存与实际盘点数量不符
排查步骤:
python复制transactions = StockTransaction.objects.filter(
inventory__product_id=problem_product_id
).order_by('created_at')
python复制def rebuild_inventory(product_id):
transactions = StockTransaction.objects.filter(
inventory__product_id=product_id
)
current = 0
for t in transactions:
current += t.amount if t.movement_type == 'IN' else -t.amount
Inventory.objects.filter(
product_id=product_id
).update(quantity=current)
慢查询分析案例:
python复制Product.objects.prefetch_related(
Prefetch('inventory_set',
queryset=Inventory.objects.select_related('location'))
).filter(category_id=category_id)
仓库管理往往需要现场操作,我们推荐两种移动端方案:
html复制<div class="d-grid gap-2 d-md-block">
<button class="btn btn-primary" type="button">入库</button>
<button class="btn btn-danger" type="button">出库</button>
</div>
python复制class InventoryViewSet(viewsets.ModelViewSet):
queryset = Inventory.objects.select_related('product')
serializer_class = InventorySerializer
filter_backends = [DjangoFilterBackend]
filterset_fields = ['location_id']
实际部署时通常需要对接企业ERP,建议采用以下方式:
sql复制CREATE VIEW erp_inventory_view AS
SELECT p.sku, i.location_id, i.quantity
FROM warehouse_inventory i
JOIN warehouse_product p ON i.product_id = p.id;
在三个月的实际运行中,这套系统成功支撑了日均2000+的出入库操作,最关键的经验是:所有库存变更必须通过StockTransaction记录,这是数据可追溯的基础。对于需要二次开发的同行,建议先从扩展Product的spec字段开始,这是适应不同行业特性的最佳切入点。