这个基于Python Flask/Django框架的水果蔬菜农场信息管理系统,是我去年为一个本地有机农场开发的数字化管理解决方案。农场主王姐找到我的时候,正被Excel表格和纸质记录搞得焦头烂额 - 种植计划、采收记录、库存管理全混在一起,每次客户下单都要翻半天本子查库存。
系统上线后,他们实现了从种植到销售的全流程数字化管理。最让我自豪的是采收模块:工人用平板扫码记录采收数据后,库存自动更新,销售端实时可见可售数量,再没出现过超卖的情况。现在连60岁的农场老师傅都能熟练使用,每天少说节省2小时对账时间。
采用Gantt图可视化呈现作物生长周期,我在Django admin基础上二次开发了拖拽调整功能。关键实现点:
python复制# models.py
class PlantingPlan(models.Model):
crop = models.ForeignKey(Crop, on_delete=models.CASCADE)
plot = models.ForeignKey(Plot, on_delete=models.CASCADE)
start_date = models.DateField()
end_date = models.DateField()
status_choices = [
('planned', '计划中'),
('planted', '已种植'),
('harvested', '已采收')
]
status = models.CharField(max_length=20, choices=status_choices)
# 前端使用FullCalendar库实现拖拽
$('#calendar').fullCalendar({
editable: true,
eventDrop: function(event) {
// AJAX更新后台日期
}
})
踩坑提醒:初期直接用Django的DateTimeField导致时区问题,后来统一改用DateField并存储UTC时间,前端根据用户时区显示。
这个模块我们迭代了3个版本:
关键代码片段:
python复制# 称重设备数据接口
@app.route('/api/weight', methods=['POST'])
def receive_weight():
data = request.get_json()
harvest = Harvest.objects.get(barcode=data['barcode'])
harvest.actual_weight = data['weight']
harvest.save()
# 触发库存更新
update_inventory(harvest.crop, harvest.actual_weight)
return jsonify({'status': 'success'})
采用"期初库存+采收入库-销售出库"的实时计算模型,而非定期盘点。核心在于建立库存流水表:
python复制class InventoryFlow(models.Model):
OPERATION_TYPES = [
('initial', '期初'),
('harvest', '采收'),
('sale', '销售'),
('adjust', '调整')
]
crop = models.ForeignKey(Crop, on_delete=models.CASCADE)
quantity = models.DecimalField(max_digits=10, decimal_places=2)
operation_type = models.CharField(max_length=20, choices=OPERATION_TYPES)
reference_id = models.CharField(max_length=100) # 关联单据ID
created_at = models.DateTimeField(auto_now_add=True)
# 实时库存视图
def current_inventory(crop_id):
inflows = InventoryFlow.objects.filter(
crop_id=crop_id,
operation_type__in=['initial', 'harvest']
).aggregate(total=Sum('quantity'))
outflows = InventoryFlow.objects.filter(
crop_id=crop_id,
operation_type='sale'
).aggregate(total=Sum('quantity'))
return (inflows['total'] or 0) - (outflows['total'] or 0)
最终选择Django而非Flask的考量:
实测数据(处理10万条采收记录):
| 操作类型 | Django(ms) | Flask+SQLAlchemy(ms) |
|---|---|---|
| 单条插入 | 120 | 150 |
| 批量插入 | 800 | 1200 |
| 复杂查询 | 200 | 350 |
采用星型模型设计:
特别为采收记录添加了JSONField存储额外属性:
python复制class HarvestRecord(models.Model):
base_info = models.JSONField() # 存储不同作物的特殊属性
# 比如水果的甜度、蔬菜的农药间隔期等
经验:不要过度使用JSONField,查询性能会下降。我们只对非过滤条件的数据用JSON存储。
采用三级缓存方案:
配置示例:
python复制# settings.py
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379/1",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
}
}
}
# 视图层使用
@cache_page(60 * 5)
def inventory_view(request):
...
农场网络不稳定,我们开发了离线功能:
同步核心逻辑:
javascript复制function syncData() {
if(navigator.onLine) {
localDB.replicate.to(remoteDB, {
filter: function(doc) {
return doc._id.startsWith('harvest_');
}
}).on('complete', showSyncSuccess);
}
}
针对采收工人的老旧安卓设备:
CSS关键设置:
css复制/* 确保可点击区域足够大 */
button, .btn {
min-width: 44px;
min-height: 44px;
font-size: 1.2rem;
}
采收标签打印的坑:
最终方案:统一生成PDF再调用系统打印
python复制from reportlab.pdfgen import canvas
def generate_label_pdf(data):
buffer = BytesIO()
p = canvas.Canvas(buffer)
p.setFont("Helvetica", 12)
p.drawString(100, 100, f"产品:{data['crop_name']}")
# ...更多绘制逻辑
p.showPage()
p.save()
return buffer.getvalue()
我们部署了这些监控点:
使用Django Signals实现:
python复制@receiver(post_save, sender=HarvestRecord)
def check_harvest_outlier(sender, instance, **kwargs):
avg = HarvestRecord.objects.filter(
crop=instance.crop
).aggregate(avg=Avg('weight'))['avg']
if instance.weight > avg * 1.5:
alert.delay(f"采收异常:{instance.crop.name}单次采收{instance.weight}kg")
采用3-2-1原则:
备份脚本示例:
bash复制#!/bin/bash
# 每日凌晨执行
DATE=$(date +%Y%m%d)
pg_dump -Fc farm_db > /backups/db_$DATE.dump
python manage.py dumpdata --format=json > /backups/json_$DATE.json
rclone copy /backups remote:backups
系统上线后的关键指标变化:
客户反馈最有用的三个功能:
下一步计划迭代:
这个项目给我的最大启示是:农业数字化不是要把系统做得多么高大上,而是要解决"老农民用着顺手"这个最朴实的需求。有时候一个扫码功能比十个数据分析图表更有价值。