You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现Django用户上传Excel/CSV自动插入PostgreSQL的fournisseurs模型?

当然可行!这在Django项目里是非常常见的批量数据导入需求,我给你梳理下具体的实现步骤,跟着来就能搞定:

第一步:先完善你的模型定义

首先要注意,你当前的fournisseur模型没有继承Django的models.Model,这是必须的前提,不然没法用Django的ORM和数据库交互。另外可以根据需求优化字段,比如给id_facture加唯一性约束,避免重复导入相同数据:

from django.db import models

class Fournisseur(models.Model):  # 首字母大写符合Python命名规范,更易读
    id_facture = models.IntegerField(unique=True)  # 唯一性约束防止重复数据
    numpiece = models.IntegerField(null=True)

    class Meta:
        db_table = 'fournisseurs'  # 指定数据库表名和你现有表一致
第二步:创建文件上传表单

写一个表单类来处理文件上传,同时限制允许的文件类型为CSV和Excel:

# forms.py
from django import forms

class FournisseurImportForm(forms.Form):
    import_file = forms.FileField(
        label="选择Excel/CSV文件",
        widget=forms.FileInput(attrs={'accept': '.csv,.xlsx'})
    )
第三步:编写视图处理上传与数据导入

这里需要分别处理CSV和Excel文件,记得先安装处理Excel的依赖:pip install openpyxl。视图里要完成文件读取、数据验证、批量插入,还要做好异常处理,给用户清晰的提示:

# views.py
import csv
from openpyxl import load_workbook
from django.shortcuts import render, redirect
from django.contrib import messages
from .models import Fournisseur
from .forms import FournisseurImportForm

def import_fournisseur(request):
    if request.method == 'POST':
        form = FournisseurImportForm(request.POST, request.FILES)
        if form.is_valid():
            import_file = request.FILES['import_file']
            file_ext = import_file.name.split('.')[-1].lower()
            data_list = []

            try:
                if file_ext == 'csv':
                    # 处理CSV文件
                    reader = csv.DictReader(import_file.read().decode('utf-8').splitlines())
                    for row in reader:
                        # 验证必填字段
                        if not row.get('id_facture'):
                            messages.error(request, "CSV文件中存在缺失id_facture的行")
                            return redirect('import_fournisseur')
                        # 转换数据类型,避免格式错误
                        try:
                            id_facture = int(row['id_facture'])
                            numpiece = int(row['numpiece']) if row['numpiece'] else None
                        except ValueError:
                            messages.error(request, f"行数据格式错误:{row}")
                            return redirect('import_fournisseur')
                        # 跳过已存在的数据
                        if not Fournisseur.objects.filter(id_facture=id_facture).exists():
                            data_list.append(Fournisseur(id_facture=id_facture, numpiece=numpiece))
                
                elif file_ext == 'xlsx':
                    # 处理Excel文件
                    wb = load_workbook(import_file)
                    sheet = wb.active
                    # 读取表头(假设第一行是列名:id_facture、numpiece)
                    headers = [cell.value.lower() for cell in sheet[1]]
                    if 'id_facture' not in headers:
                        messages.error(request, "Excel文件缺少id_facture列")
                        return redirect('import_fournisseur')
                    id_col_idx = headers.index('id_facture')
                    num_col_idx = headers.index('numpiece') if 'numpiece' in headers else None

                    for row in sheet.iter_rows(min_row=2, values_only=True):
                        id_facture = row[id_col_idx]
                        if not id_facture:
                            messages.error(request, "Excel文件中存在缺失id_facture的行")
                            return redirect('import_fournisseur')
                        try:
                            id_facture = int(id_facture)
                            numpiece = int(row[num_col_idx]) if num_col_idx is not None and row[num_col_idx] else None
                        except ValueError:
                            messages.error(request, f"行数据格式错误:{row}")
                            return redirect('import_fournisseur')
                        if not Fournisseur.objects.filter(id_facture=id_facture).exists():
                            data_list.append(Fournisseur(id_facture=id_facture, numpiece=numpiece))
                
                else:
                    messages.error(request, "仅支持CSV和XLSX格式的文件")
                    return redirect('import_fournisseur')

                # 批量插入数据,比循环save()效率高很多
                if data_list:
                    Fournisseur.objects.bulk_create(data_list)
                    messages.success(request, f"成功导入{len(data_list)}条供应商数据")
                else:
                    messages.info(request, "没有新的可导入数据")
                
                return redirect('import_fournisseur')
            
            except Exception as e:
                messages.error(request, f"导入失败:{str(e)}")
                return redirect('import_fournisseur')
    else:
        form = FournisseurImportForm()
    return render(request, 'import_fournisseur.html', {'form': form})
第四步:创建上传页面模板

在templates目录下创建import_fournisseur.html,做一个简单的上传界面:

<!-- templates/import_fournisseur.html -->
<!DOCTYPE html>
<html>
<head>
    <title>导入供应商数据</title>
</head>
<body>
    <h1>导入供应商数据</h1>
    {% if messages %}
        {% for message in messages %}
            <div style="color: {% if message.tags == 'success' %}green{% elif message.tags == 'error' %}red{% else %}blue{% endif %}">
                {{ message }}
            </div>
        {% endfor %}
    {% endif %}
    <form method="post" enctype="multipart/form-data">
        {% csrf_token %}
        {{ form.as_p }}
        <button type="submit">上传并导入</button>
    </form>
</body>
</html>
第五步:配置URL路由

在项目的urls.py里添加路由,让用户能访问到上传页面:

# urls.py
from django.urls import path
from .views import import_fournisseur

urlpatterns = [
    # 其他路由...
    path('import-fournisseur/', import_fournisseur, name='import_fournisseur'),
]

几个关键注意点

  • 数据验证:一定要做字段格式检查和必填项验证,避免脏数据进入数据库
  • 批量插入:用bulk_create比循环调用save()效率高很多,尤其是数据量大的时候
  • 异常处理:捕获文件读取、数据转换、数据库操作的异常,给用户友好的提示
  • 权限控制:如果需要限制只有特定用户能导入数据,可以给视图添加@login_required装饰器,或者结合Django的权限系统

内容的提问来源于stack exchange,提问作者Elroum

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 04:26:03