项目初始化:Django项目创建、应用模块划分与数据库模型设计原则
好,咱们正式开始动手了。这一章,我带你从零搭起ERP进销存系统的骨架。说白了,就是先把Django项目建起来,把四个核心模块(采购、销售、库存、财务)划分清楚,再聊聊数据库模型该怎么设计。
我记得刚带团队做第一个ERP项目时,上来就急着写业务代码,结果模块之间耦合得跟麻花似的,后来重构了两次才理顺。所以,这一步走稳了,后面能省不少心。
3.1 创建Django项目
先确认你的环境。我习惯用Python 3.10+,Django 4.2 LTS版本。为什么选LTS?你想想看,企业项目一跑就是好几年,选长期支持版心里踏实。
# 创建虚拟环境
python -m venv erp_env
source erp_env/bin/activate # Windows下用 erp_env\Scripts\activate
# 安装Django
pip install django==4.2
# 创建项目
django-admin startproject erp_backend
cd erp_backend
# 创建四个核心应用
python manage.py startapp purchase
python manage.py startapp sales
python manage.py startapp inventory
python manage.py startapp finance
嗯,这里要注意:应用名我全用了英文单数形式。有些团队喜欢用复数,我个人觉得单数更简洁,跟模型类名保持一致就好。
3.2 应用模块划分
四个模块怎么分工?我直接给你画个图:
| 模块 | 核心职责 | 典型模型 |
|---|---|---|
| purchase | 采购订单、供应商管理、入库单 | PurchaseOrder, Supplier, PurchaseItem |
| sales | 销售订单、客户管理、出库单 | SalesOrder, Customer, SalesItem |
| inventory | 商品档案、库存台账、库存变动 | Product, Warehouse, StockMovement |
| finance | 应收应付、收款付款、会计凭证 | Invoice, Payment, AccountingEntry |
你可能会问:为什么把商品放在inventory而不是单独建个product模块?我在项目中踩过这个坑。商品信息跟库存变动是强绑定的,分开后每次查库存都要跨模块join,性能差不说,逻辑还容易乱。
核心原则:每个模块只管自己的事。采购模块只管「跟供应商下订单」,别去管库存怎么扣减。库存变动通过信号(signal)或事件驱动来触发,模块之间通过接口通信,不直接调用对方的模型。
3.3 数据库模型设计原则
做ERP系统,数据库设计是地基。地基歪了,楼盖得再高也得塌。我总结了四条铁律,你记一下:
原则一:每个模型都要有审计字段
企业系统里,谁在什么时候创建了这条记录、最后谁修改的,这是刚需。我习惯写一个抽象基类:
# common/models.py
from django.db import models
from django.utils import timezone
class BaseModel(models.Model):
created_at = models.DateTimeField(default=timezone.now, verbose_name="创建时间")
updated_at = models.DateTimeField(auto_now=True, verbose_name="更新时间")
created_by = models.CharField(max_length=64, blank=True, verbose_name="创建人")
updated_by = models.CharField(max_length=64, blank=True, verbose_name="更新人")
is_deleted = models.BooleanField(default=False, verbose_name="逻辑删除")
class Meta:
abstract = True
所有业务模型都继承这个BaseModel。注意那个is_deleted字段——企业数据不能物理删除,只能打标记。我曾经见过一个项目直接删数据,月底对账对不上,查了三天才发现是误删了。
原则二:用编码字段,别用自增ID做业务标识
采购单号、销售单号、商品编码,这些业务标识要单独设计。自增ID只做内部主键,对外展示用编码:
class PurchaseOrder(BaseModel):
# 内部主键 Django 自动生成
order_code = models.CharField(max_length=32, unique=True, verbose_name="采购单号")
# ... 其他字段
def save(self, *args, **kwargs):
if not self.order_code:
# 生成规则:PO + 日期 + 流水号
today = timezone.now().strftime("%Y%m%d")
last_order = PurchaseOrder.objects.filter(
order_code__startswith=f"PO{today}"
).order_by('-order_code').first()
if last_order:
seq = int(last_order.order_code[-4:]) + 1
else:
seq = 1
self.order_code = f"PO{today}{seq:04d}"
super().save(*args, **kwargs)
为什么这么做?你想想看,客户打电话说「帮我查一下PO202403150001这个单子」,你总不能说「好的,请告诉我数据库自增ID是几」吧?
原则三:金额字段用Decimal,别用Float
这个坑我踩得特别深。有一次财务说系统算的税额跟发票差了一分钱,查了半天,是float精度问题。从那以后,所有金额字段我都用Decimal:
from django.db import models
class SalesItem(models.Model):
quantity = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="数量")
unit_price = models.DecimalField(max_digits=12, decimal_places=4, verbose_name="单价")
tax_rate = models.DecimalField(max_digits=5, decimal_places=2, verbose_name="税率(%)")
@property
def subtotal(self):
# 金额计算用Decimal,保证精度
return self.quantity * self.unit_price
@property
def tax_amount(self):
return self.subtotal * self.tax_rate / Decimal('100')
警告:Decimal的decimal_places要留够。单价我习惯用4位小数,因为有些商品按克计价,单价可能是0.1234元。总金额用2位就够了,但中间计算过程必须保留足够精度。
原则四:用外键关联,但别滥用
采购单关联供应商、采购明细关联商品,这些用外键没问题。但有些场景,比如历史快照,就不能用外键。举个例子:
class PurchaseItem(BaseModel):
order = models.ForeignKey(
PurchaseOrder, on_delete=models.CASCADE,
related_name='items', verbose_name="所属订单"
)
product = models.ForeignKey(
Product, on_delete=models.PROTECT, # 注意:用PROTECT,别用CASCADE
verbose_name="商品"
)
# 快照字段:记录下单时的商品信息
product_name = models.CharField(max_length=128, verbose_name="商品名称(快照)")
product_code = models.CharField(max_length=32, verbose_name="商品编码(快照)")
product_spec = models.CharField(max_length=64, blank=True, verbose_name="规格(快照)")
为什么要有快照字段?因为商品信息会变。今天下单时商品叫「A型号」,明天改名叫「A+型号」了,但历史订单里还得显示原来的名字。用外键关联实时数据,历史记录就全乱了。
3.4 项目结构一览
最后,给你看看我习惯的项目目录结构:
erp_backend/
├── erp_backend/ # 项目配置
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── common/ # 公共模块
│ ├── models.py # BaseModel
│ ├── utils.py # 工具函数
│ └── signals.py # 信号定义
├── purchase/ # 采购模块
│ ├── models.py
│ ├── views.py
│ └── serializers.py
├── sales/ # 销售模块
│ ├── models.py
│ ├── views.py
│ └── serializers.py
├── inventory/ # 库存模块
│ ├── models.py
│ ├── views.py
│ └── serializers.py
├── finance/ # 财务模块
│ ├── models.py
│ ├── views.py
│ └── serializers.py
└── manage.py
小提示:common模块不是Django应用,不需要注册到INSTALLED_APPS里。它就是个公共代码仓库,所有模块都能引用。另外,我习惯把信号(signals)单独放一个文件,别写在models.py里,不然导入顺序容易出问题。
好了,项目骨架搭起来了。下一章咱们开始往里面填肉——先做商品管理和库存台账,这是进销存系统的核心。到时候我会带你一步步写模型、做序列化、搭视图,把理论落地成代码。
记住:好的开始是成功的一半。项目初始化这一步,值得你花时间认真做。