简介:这是一份基于 Python 与 Django 的企业物流管理系统毕业设计文档,适合计算机、物流专业学生以及需要交付物流信息化方案的后端开发者参考。文档以学士学位论文形式,完整呈现系统从需求分析到设计实现的过程,功能覆盖订单管理、运输规划、路线优化、运输监控、异常处理等模块;技术实现采用 Django 框架结合 MySQL 数据库,并对大数据、人工智能、区块链等新技术在物流管理中的应用做了延伸讨论。资源为一个 docx 文件,压缩包仅 1.31MB,打开即可看到完整论文正文,包含摘要、关键词、英文摘要、系统设计与模块说明,并梳理了灵活性高、操作简单、响应快、安全性好等重要特点;数据库设计部分清晰展示了表关系,便于理解整体系统架构。目前已有 381 人学习,适合正在撰写物流管理系统论文、需要快速搭建设计思路的读者。
1. 为什么企业物流管理系统偏偏选了 Python+Django
物流管理系统这种业务,传统上第一反应是 Java 或者 .NET,但这套基于 Python + Django 的实现,反而把企业物流里最难处理的几个问题简化了:订单状态流转、运输任务分配、货物出入库联动,以及不同角色之间的权限控制。原因在于 Django 自带 ORM 和 Admin 后台,数据模型一旦定义好,增删改查的代码量能压缩到传统写法的三分之一左右。加上 Django 的 migrations 机制,表结构变更可以走版本化管理,这对物流这种字段调整频繁的业务场景非常实用。适合谁呢?一类是内部信息化团队想快速搭一套物流管理后台的中小企业,另一类是正在做课程设计或毕业设计的开发者,需要一套完整的、能跑通前后端和数据库的参考实现。这套系统的核心价值不在于功能有多花哨,而在于它把物流管理的标准模块——订单、货物、仓储、运输、人事——用一套清晰的分层结构组织起来了,可以直接在此基础上做二次开发。
2. 系统模块拆解与 Django 数据模型设计
2.1 物流管理的核心模块划分
这套系统的功能边界很明确:登录后进入仪表盘,能看到每日用户数、收入支出等汇总数据,同时展示当前运行环境的 Python 版本和 MySQL 版本。订单管理分为临时订单和正式订单,临时订单只有查看权限,正式订单才能执行修改和审核操作。货物管理负责产品的录入和状态维护,仓储管理处理出入库操作,运输管理分成运输员管理和运输订单两块,人事管理则覆盖用户信息维护和密码修改。
从模块划分来看,这是一个典型的前后台分离思路:前台负责用户注册登录和基础信息录入,后台负责业务数据的审核与管理。两者访问同一个 MySQL 数据库,但是操作的数据对象不同。这种设计的好处是权限边界清晰,普通用户只能操作自己相关的数据,管理员才能看到全局汇总和审核入口。
2.2 Django 模型定义与 ORM 映射
在 Django 中实现这套模块,第一步是把业务表结构定义成 Model 类。下面是订单和货物两个核心模型的示例:
# logistics/models.py from django.db import models from django.contrib.auth.models import User class Order(models.Model): STATUS_CHOICES = [ ('pending', '待审核'), ('confirmed', '已确认'), ('delivering', '运输中'), ('completed', '已完成'), ('cancelled', '已取消'), ] order_id = models.AutoField(primary_key=True) customer = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='客户') order_status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending') goods = models.ManyToManyField('Goods', through='OrderGoods') created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Goods(models.Model): goods_id = models.CharField(max_length=50, primary_key=True, verbose_name='货物ID') goods_name = models.CharField(max_length=100, verbose_name='货物名称') quantity = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='数量') weight = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='重量(kg)') volume = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='体积(m³)') sender_address = models.CharField(max_length=200, verbose_name='发货地址') receiver_address = models.CharField(max_length=200, verbose_name='收货地址') status = models.CharField(max_length=20, default='in_stock', verbose_name='货物状态') class OrderGoods(models.Model): order = models.ForeignKey(Order, on_delete=models.CASCADE) goods = models.ForeignKey(Goods, on_delete=models.CASCADE) quantity = models.IntegerField(verbose_name='订单内数量')这段模型定义有几个关键点。order_id设置为自增主键,符合物流系统中订单号需要唯一且递增的业务需求。order_status用 Django 的 choices 参数限定可枚举状态,这样在视图层直接使用get_order_status_display()就能拿到中文描述,避免到处写魔法字符串。OrderGoods作为中间表,解决订单和货物的多对多关系——一个订单可以包含多种货物,一种货物也可以出现在多个订单中。
提示:on_delete=models.CASCADE在物流场景下要谨慎使用。如果客户被删除,关联订单会一并删除,这在真实业务中可能造成数据丢失。更稳妥的做法是用PROTECT或自定义回调。
2.3 数据表规划与字段设计要点
对照需求分析中的数据字典,系统至少要建这几张表:物流表(logistics_id、order_id、goods_id、transport_mode、transport_status、transport_date)、订单表(order_id、customer_id、order_status 等)、货物表(goods_id、goods_name、quantity、weight、volume、sender_address、receiver_address)、用户表(Django 内置的 auth_user 即可满足大部分需求)。
这里有一个设计上的取舍:物流表通过 order_id 和 goods_id 与订单、货物建立外键关联,但运输状态和订单状态需要同步更新。常见的做法是在运输订单状态变更时,通过 Django signal 或事务钩子同步修改关联订单的状态。后面 5.3 节会给出具体实现。
3. 基于 Django View 与 URL 路由的模块实现流程
3.1 URL 路由与视图函数的分层设计
Django 的分层设计从 urls.py 开始。把不同模块的路由拆分到各自的 app 中,是保持项目可维护性的关键。下面是项目级的 URL 配置:
# config/urls.py from django.contrib import admin from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('api/orders/', include('apps.orders.urls')), path('api/goods/', include('apps.goods.urls')), path('api/warehouse/', include('apps.warehouse.urls')), path('api/transport/', include('apps.transport.urls')), path('api/hr/', include('apps.hr.urls')), path('', include('apps.dashboard.urls')), ]每个 app 内部再定义自己的 urls.py。以订单模块为例:
# apps/orders/urls.py from django.urls import path from . import views urlpatterns = [ path('temporary/', views.temporary_order_list, name='temporary_order_list'), path('temporary/<int:order_id>/review/', views.temporary_order_review), path('formal/', views.formal_order_list, name='formal_order_list'), path('formal/<int:order_id>/edit/', views.formal_order_edit), path('formal/<int:order_id>/status/', views.formal_order_status_update), ]这种路由划分的用意在于:临时订单和正式订单虽然是同一张表的数据,但业务权限完全不同。临时订单只能查看和审核,正式订单才能编辑。URL 层面把它们区分开,视图函数就可以针对性地做权限校验,而不需要在同一个函数里写大量 if-else 判断。
3.2 列表查询视图的翻页与条件搜索实现
物流管理系统的列表页通常数据量大,必须支持分页和按条件检索。下面是正式订单列表视图的一种实现方式:
# apps/orders/views.py from django.core.paginator import Paginator from django.shortcuts import render from .models import Order def formal_order_list(request): queryset = Order.objects.filter(order_status__in=['confirmed', 'delivering', 'completed']) # 按货物名称搜索 goods_name = request.GET.get('goods_name', '') if goods_name: queryset = queryset.filter(goods__goods_name__icontains=goods_name) # 按订单状态筛选 status = request.GET.get('status', '') if status: queryset = queryset.filter(order_status=status) # 分页 paginator = Paginator(queryset, 20) # 每页 20 条 page_number = request.GET.get('page') page_obj = paginator.get_page(page_number) return render(request, 'orders/formal_list.html', { 'page_obj': page_obj, 'goods_name': goods_name, 'status': status, })这段逻辑里,Order.objects.filter(...)返回的是 QuerySet,它是惰性求值的,只有在模板中真正迭代时才执行 SQL。__icontains是 Django ORM 的模糊查询语法,对应 SQL 里的 LIKE '%keyword%',并且不区分大小写。Paginator把 QuerySet 切分成页,避免一次性加载全部数据导致内存暴涨。
参数说明:goods_name和status从 GET 请求的 query string 中读取,这样在模板中构造分页链接时必须把这两个参数一起带上,否则翻页后搜索条件就丢了。模板里可以用page_obj.has_previous和page_obj.next_page_number渲染上下页链接。
注意:这里的filter(goods__goods_name__icontains=goods_name)会走 JOIN 查询,如果订单表数据量超过百万级,性能会明显下降。届时应考虑引入全文索引或 Elasticsearch,而不是继续用 LIKE 模糊匹配。
3.3 数据录入与更新的表单处理
货物管理模块需要支持新增和编辑,Django 的 ModelForm 可以自动根据模型生成表单:
# apps/goods/forms.py from django import forms from .models import Goods class GoodsForm(forms.ModelForm): class Meta: model = Goods fields = ['goods_id', 'goods_name', 'quantity', 'weight', 'volume', 'sender_address', 'receiver_address'] widgets = { 'sender_address': forms.Textarea(attrs={'rows': 2}), 'receiver_address': forms.Textarea(attrs={'rows': 2}), }# apps/goods/views.py from django.shortcuts import render, redirect from .forms import GoodsForm def goods_create(request): if request.method == 'POST': form = GoodsForm(request.POST) if form.is_valid(): goods = form.save() return redirect('goods_detail', pk=goods.goods_id) else: form = GoodsForm() return render(request, 'goods/goods_form.html', {'form': form})ModelForm 的好处是自动做字段校验:DecimalField会拒绝非数字输入,max_length会限制字符串长度,必填字段的required属性也会自动标记。不需要手写校验逻辑。form.save()直接写入数据库,返回的 goods 实例可以用于跳转。
在模板中渲染表单时,推荐用{{ form.as_p }}快速出效果,但正式项目中一般会手动渲染每个字段,以便利用 Bootstrap 或 AdminLTE 的样式控制布局。
3.4 运输管理模块的联动查询
运输员管理和运输订单是运输模块的两个入口。运输员管理按货物名称查询当日出车情况,运输订单按运输员姓名检索收发车时间。这两块逻辑可以共用同一个数据模型,只是查询维度不同:
# apps/transport/models.py class Transporter(models.Model): name = models.CharField(max_length=50, verbose_name='姓名') license_plate = models.CharField(max_length=20, verbose_name='车牌号') is_on_duty = models.BooleanField(default=True, verbose_name='是否在岗') transport_quantity = models.IntegerField(default=0, verbose_name='今日运输数量') class TransportOrder(models.Model): transporter = models.ForeignKey(Transporter, on_delete=models.PROTECT) order = models.ForeignKey(Order, on_delete=models.PROTECT) depart_time = models.DateTimeField(null=True, blank=True, verbose_name='发车时间') return_time = models.DateTimeField(null=True, blank=True, verbose_name='收车时间') status = models.CharField(max_length=20, default='pending')按运输员姓名查找运输订单的查询是:
# apps/transport/views.py def transport_order_list(request): transporter_name = request.GET.get('transporter_name', '') queryset = TransportOrder.objects.select_related('transporter', 'order') if transporter_name: queryset = queryset.filter(transporter__name__icontains=transporter_name) return render(request, 'transport/transport_order_list.html', { 'transport_orders': queryset, 'transporter_name': transporter_name, })这里使用select_related是为了解决 N+1 查询问题。如果不用它,循环模板渲染transport_order.transporter.name时,每一条记录都会再查一次 transporter 表,总共会产生 N+1 次 SQL;加上select_related之后,Django 会一次性通过 JOIN 把关联的表数据取回来。
4. 数据库设计:从数据字典到 MySQL 落地
4.1 表结构设计与范式取舍
根据数据字典,订单表包含 order_id、customer_id、order_status、goods_id、goods_name 等字段;货物表包含 goods_id、goods_name、goods_quantity、goods_weight、goods_volume、sender_address、receiver_address;物流表包含 logistics_id、order_id、goods_id、transport_mode、transport_status、transport_date。
在设计时需要注意范式与性能的平衡。比如订单表里冗余了 goods_name,第三范式是不允许的,但实际查询列表页时,如果每次都通过 JOIN 去货物表取名称,会多一次关联查询。对于物流这种读多写少的场景,适度冗余反而能提升响应速度。方案是:在订单表冗余 goods_name,通过应用层在创建订单时写入,并接受可能的数据不一致风险。
表与表之间的关联关系如下:
| 表名 | 关联字段 | 指向表 | 关系 |
|---|---|---|---|
| order | customer_id | auth_user | 多对一 |
| order_goods | order_id / goods_id | order / goods | 多对多中间表 |
| logistics | order_id / goods_id | order / goods | 多对一 |
| transport_order | transporter_id / order_id | transporter / order | 多对一 |
4.2 MySQL 建表语句与索引规划
在设计 Django 模型时,虽然可以用 migrations 自动建表,但在部署环境里手动确认表结构仍然是必要的。下面是订单表的 MySQL DDL 示例:
CREATE TABLE `order` ( `order_id` INT NOT NULL AUTO_INCREMENT, `customer_id` INT NOT NULL, `order_status` VARCHAR(20) NOT NULL DEFAULT 'pending', `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`order_id`), KEY `idx_customer_id` (`customer_id`), KEY `idx_order_status` (`order_status`), CONSTRAINT `fk_order_customer` FOREIGN KEY (`customer_id`) REFERENCES `auth_user` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;索引规划有两个要点。customer_id是高频查询条件,按客户查订单非常常见,必须建普通索引。order_status虽然区分度不高,但在后台列表页几乎总是按状态筛选,所以也建索引。created_at字段用于排序和按日期统计,如果系统有报表需求,也应该加索引。
特别提一下 utf8mb4 字符集。MySQL 的 utf8 实际只支持最多 3 字节的字符,而 Django 默认会生成 utf8mb4。如果你手动建库时用了 utf8,那么录入 emoji 或生僻字会报错。建库语句必须是:
CREATE DATABASE logistics CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;4.3 Django Migrations 与 MySQL 的协同使用
开发环境使用 SQLite,生产环境切换 MySQL,这是 Django 项目的常见做法。需要在 settings.py 里配置数据库连接:
# config/settings.py DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'logistics', 'USER': 'logistics_user', 'PASSWORD': 'your_password', 'HOST': '127.0.0.1', 'PORT': '3306', 'OPTIONS': { 'charset': 'utf8mb4', 'init_command': "SET sql_mode='STRICT_TRANS_TABLES'", }, } }执行迁移命令时,Django 会根据 models.py 生成 SQL 并应用到 MySQL:
python manage.py makemigrations python manage.py migrate需要额外安装 PyMySQL 并配置pymysql.install_as_MySQLdb(),放在项目根目录的__init__.py中。这个兼容层让 Django 的 MySQL 后端能正常连接数据库。
提示:STRICT_TRANS_TABLES开启后,如果写入的数据超出字段长度或类型范围,MySQL 直接报错而不是截断静默处理。这个模式在调试阶段能快速发现问题。
5. 权限控制、数据统计与运维部署
5.1 基于 Django 内置 User 的权限模型
这套系统的用户权限设计分为三个层级:管理员拥有全部模块的操作权限,普通用户只能查看和录入自己相关的数据,运输员则只能看到运输订单和自己的出车记录。Django 自带的auth_user表加上Group和Permission即可覆盖大部分需求。
在视图层做权限校验,最简单的是用装饰器:
# views.py from django.contrib.auth.decorators import login_required, permission_required @login_required @permission_required('orders.can_review_temporary_order', raise_exception=True) def temporary_order_review(request, order_id): # 只有拥有该权限的用户才能审核临时订单 pass@login_required拦截未登录用户,跳转到登录页;@permission_required在用户已登录但权限不足时返回 403 页面。权限名是 Django 根据 Model 和自定义 Permission 自动生成的,可以在 Admin 后台给 Group 勾选对应权限。
一个容易被忽略的细节:如果临时订单和正式订单共用同一张 Order 表,那么权限控制不能在 Model 层做,只能在 View 层拦截。这是因为 ORM 没有行级权限的概念,Django 本身也不支持。简单场景下可以直接在查询后面追加.filter(customer=request.user)来限制数据范围,但管理员需要能看全部数据,所以实际项目中更推荐用django-guardian这类第三方库来做对象级别的权限控制。
5.2 仪表盘数据汇总的聚合查询
仪表盘要展示用户数、收入支出和系统版本信息。这些数据分散在多张表里,需要用聚合查询实现:
# apps/dashboard/views.py from django.db.models import Sum, Count from django.shortcuts import render from django.contrib.auth.models import User from apps.orders.models import Order from apps.transport.models import TransportOrder def dashboard(request): context = { 'total_users': User.objects.count(), 'total_orders': Order.objects.count(), 'orders_by_status': Order.objects.values('order_status').annotate(cnt=Count('order_id')), 'total_transport_today': TransportOrder.objects.filter( depart_time__date=timezone.localdate() ).count(), 'completed_orders_today': Order.objects.filter( order_status='completed', updated_at__date=timezone.localdate() ).count(), } return render(request, 'dashboard/index.html', context).values('order_status').annotate(cnt=Count('order_id'))生成的是GROUP BY order_status的 SQL,返回结果是字典列表,例如[{'order_status': 'pending', 'cnt': 23}, {'order_status': 'confirmed', 'cnt': 45}]。这样可以一次性拿到所有状态的订单数量分布,在模板里用循环渲染柱状图或饼图。
模板里展示系统版本信息时,可以直接读取运行环境:
import sys, platform import MySQLdb mysql_version = None try: db = MySQLdb.connect(host='127.0.0.1', user='logistics_user', passwd='your_password', db='logistics') cursor = db.cursor() cursor.execute("SELECT VERSION()") mysql_version = cursor.fetchone()[0] db.close() except Exception: pass context['python_version'] = sys.version context['platform_info'] = platform.platform() context['mysql_version'] = mysql_version这个逻辑在启动时会读一次,如果数据库连接失败会静默跳过,不会导致整个仪表盘崩溃。实际部署中建议把这部分放到settings.py加载时执行一次并缓存,避免每次请求都查询数据库版本。
5.3 运输状态联动更新的信号机制
运输订单状态变更时,订单表的 order_status 也需要同步更新。用 Django signal 实现最简洁:
# apps/transport/signals.py from django.db.models.signals import post_save from django.dispatch import receiver from .models import TransportOrder from apps.orders.models import Order @receiver(post_save, sender=TransportOrder) def sync_order_status(sender, instance, **kwargs): if instance.status == 'delivering': Order.objects.filter(order_id=instance.order_id).update(order_status='delivering') elif instance.status == 'completed': Order.objects.filter(order_id=instance.order_id).update(order_status='completed') elif instance.status == 'cancelled': Order.objects.filter(order_id=instance.order_id).update(order_status='cancelled')在apps/transport/__init__.py中导入信号模块:
# apps/transport/__init__.py default_app_config = 'apps.transport.apps.TransportConfig'再到apps.py里重载ready方法:
# apps/transport/apps.py from django.apps import AppConfig class TransportConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' name = 'apps.transport' def ready(self): import apps.transport.signals信号机制的好处是状态同步逻辑集中在一个地方,视图里不需要在每一处修改位置重复写更新代码。但要注意:使用.update()批量更新时不触发post_save信号,所以上面必须在信号处理器里用.update()而不是instance.save(),否则会陷入递归调用。
5.4 基于 Nginx + Waitress 的 Django 部署
生产环境部署时,python manage.py runserver不能用于线上。Django 官方推荐的部署方式是 WSGI 服务器 + 反向代理。在 Windows Server 环境里,Waitress 比 Gunicorn 更合适,它是纯 Python 实现,不依赖 Unix 特有的 fork 机制:
pip install waitress waitress-serve --listen=127.0.0.1:8000 logistics.wsgi:applicationNginx 配置反向代理和静态文件服务:
server { listen 80; server_name logistics.example.com; location /static/ { alias /var/www/logistics/static/; } location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }关键配置是proxy_set_header那几行。Django 的 CSRF 校验和重定向逻辑依赖Host头,如果 Nginx 不传Host,Django 会把请求当作外部请求处理,导致 CSRF 验证失败或重定向到错误的域名。X-Forwarded-Proto用于 HTTPS 场景下 Django 正确识别协议。同时需要在 settings.py 里配置:
ALLOWED_HOSTS = ['logistics.example.com', 'localhost', '127.0.0.1'] CSRF_TRUSTED_ORIGINS = ['http://logistics.example.com']如果这里不配置ALLOWED_HOSTS,Django 会拒绝带有未知 Host 头的请求,返回 400 错误。
5.5 物流系统的测试策略
测试是物流系统上线前必须做的环节。Django 提供了基于unittest的测试框架,可以覆盖视图、模型和表单。下面是针对订单状态更新的测试用例:
# apps/transport/tests.py from django.test import TestCase from django.contrib.auth.models import User from apps.orders.models import Order from apps.transport.models import Transporter, TransportOrder class TransportOrderSignalTest(TestCase): def setUp(self): self.user = User.objects.create_user(username='testdriver', password='testpass') self.order = Order.objects.create(customer=self.user, order_status='confirmed') self.transporter = Transporter.objects.create(name='张师傅', license_plate='A12345') def test_order_status_sync(self): transport_order = TransportOrder.objects.create( transporter=self.transporter, order=self.order, status='delivering' ) self.order.refresh_from_db() self.assertEqual(self.order.order_status, 'delivering')测试的关键在于refresh_from_db()。因为信号处理器里用的是批量更新,Django ORM 不会自动刷新当前实例的状态,必须手动从数据库重新读取才能验证最新的值。这个细节如果不注意,测试结果会出现虚假的失败。
写测试时还要注意:Django 测试框架默认每跑完一个测试类会重建一次测试数据库(SQLite 内存模式),所以不需要手动清理数据。但如果用了 MySQL 作为测试库,需要先创建测试数据库并授予权限,否则会报Got an error creating the test database。
6. 实战技巧:从 Excel 导入货物数据到 MySQL
物流管理系统上线初期,最大的痛点不是代码,而是历史数据的迁移。大部分企业手里的货物信息都在 Excel 表里,逐条手工录入既不现实也容易出错。这里分享一个用 Django 管理命令批量导入的思路。
在apps/goods/management/commands/import_goods.py里写一个自定义命令:
import csv from django.core.management.base import BaseCommand from apps.goods.models import Goods class Command(BaseCommand): help = '从 CSV 文件批量导入货物数据' def add_arguments(self, parser): parser.add_argument('csv_path', type=str, help='CSV 文件路径') def handle(self, *args, **options): csv_path = options['csv_path'] success = 0 failed = 0 with open(csv_path, 'r', encoding='utf-8-sig') as f: reader = csv.DictReader(f) for row in reader: try: Goods.objects.update_or_create( goods_id=row['goods_id'], defaults={ 'goods_name': row['goods_name'], 'quantity': row['quantity'], 'weight': row['weight'], 'volume': row['volume'], 'sender_address': row['sender_address'], 'receiver_address': row['receiver_address'], 'status': row.get('status', 'in_stock'), } ) success += 1 except Exception as e: failed += 1 self.stderr.write(f"导入失败 {row['goods_id']}: {e}") self.stdout.write(self.style.SUCCESS(f"导入完成,成功 {success} 条,失败 {failed} 条"))几个实际踩坑后的处理原则。文件编码用utf-8-sig而不是utf-8,因为 Windows 导出的 Excel CSV 文件通常带 BOM 头,直接按utf-8读取会在第一列列名里混入\ufeff字符,导致DictReader的 key 对不上。update_or_create以goods_id为唯一键,已存在的货物只更新字段不重复创建,重复执行导入命令是幂等的。出错记录通过self.stderr.write输出,不会中断整个导入流程。
运行命令的方式:
python manage.py import_goods data/goods_2025_import.csv如果要插入的数据量超过几万条,逐条update_or_create性能会比较差。这时可以改用 Django 的bulk_create加update_conflicts参数,一次批量写入:
Goods.objects.bulk_create( goods_list, batch_size=1000, update_conflicts=True, update_fields=['goods_name', 'quantity', 'weight', 'volume'], unique_fields=['goods_id'] )bulk_create只在 MySQL 8.0.13 以上版本支持update_conflicts,如果部署环境版本较旧,需要先用bulk_update做全量更新,再对新记录执行bulk_create。判断哪些是新记录的办法也很简单:先Goods.objects.filter(goods_id__in=existing_ids).values_list('goods_id', flat=True)查出已存在的 ID,不在列表里的就是待插入的。这套导入逻辑在系统上线初期基本能解决八成以上的历史数据迁移问题。
本文还有配套的精品资源,点击获取