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

如何在Django Admin实现导入Agent并关联新建AgentList的功能?

基于django-import-export的Agent批量导入并关联AgentList最优实现

问题背景

现有模型

# Agent模型
class Agent(models.Model):
    registration_number = models.CharField(max_length=50, unique=True)     
    first_name = models.CharField(max_length=50)     
    last_name = models.CharField(max_length=50)

# AgentList模型
class AgentList(models.Model):
    name = models.CharField(max_length=50)
    agents = models.ManyToManyField(Agent)

需求

点击Admin中的「Import」按钮后,跳转至包含两个字段的页面:

  • 选择包含registration_number、first_name、last_name字段的CSV文件
  • 填写列表名称
    导入的所有Agent需保存至Agent模型,同时新建一个AgentList并关联这些导入的Agent。

最优实现方案

利用django-import-export的自定义导入表单能力,复用官方成熟的导入逻辑,避免重复编写底层代码。

步骤1:定义Agent的Resource类

处理CSV文件到Agent模型的导入映射与逻辑:

from import_export import resources
from .models import Agent

class AgentResource(resources.ModelResource):
    class Meta:
        model = Agent
        # 指定与CSV列对应的导入字段
        fields = ('registration_number', 'first_name', 'last_name')
        # 用唯一标识字段判断数据是否已存在,避免重复导入
        import_id_fields = ('registration_number',)
        # 可选:重复数据仅跳过不更新,开启后需同时设置report_skipped=True
        # skip_unchanged = True
        # report_skipped = True

步骤2:自定义导入表单

添加「列表名称」字段,替换默认的导入表单:

from django import forms

class AgentListImportForm(forms.Form):
    file = forms.FileField(label="CSV文件")
    list_name = forms.CharField(label="列表名称", max_length=50, required=True)

步骤3:重写AgentListAdmin类

关联自定义表单和Resource,完成导入后自动创建AgentList并关联Agent:

from django.contrib import admin
from django.contrib import messages
from django.http import HttpResponseRedirect
from import_export.admin import ImportMixin
from .models import AgentList
from .resources import AgentResource
from .forms import AgentListImportForm

@admin.register(AgentList)
class AgentListAdmin(ImportMixin, admin.ModelAdmin):
    list_display = ("name",)
    filter_horizontal = ("agents",)
    # 指定自定义导入表单
    import_form_class = AgentListImportForm

    def get_import_resource_class(self):
        # 返回Agent的Resource类,用于处理Agent数据导入
        return AgentResource

    def import_action(self, request, *args, **kwargs):
        if request.method == "POST":
            # 实例化自定义导入表单
            form = self.import_form_class(request.POST, request.FILES)
            if form.is_valid():
                try:
                    # 初始化Resource并执行导入
                    resource = self.get_import_resource_class()()
                    dataset = self.get_dataset(request.FILES['file'])
                    # dry_run=False直接写入数据库,raise_errors=True抛出异常便于捕获处理
                    result = resource.import_data(
                        dataset, 
                        dry_run=False, 
                        raise_errors=True, 
                        request=request
                    )

                    if not result.has_errors():
                        # 获取所有导入成功的Agent
                        imported_reg_numbers = [row['registration_number'] for row in dataset.dict]
                        imported_agents = Agent.objects.filter(registration_number__in=imported_reg_numbers)
                        # 创建AgentList并关联Agent
                        agent_list = AgentList.objects.create(name=form.cleaned_data['list_name'])
                        agent_list.agents.add(*imported_agents)
                        # 提示成功信息
                        self.message_user(
                            request, 
                            f"成功导入{len(imported_agents)}个Agent,已创建列表「{form.cleaned_data['list_name']}」", 
                            messages.SUCCESS
                        )
                except Exception as e:
                    # 捕获导入异常并提示用户
                    self.message_user(request, f"导入失败:{str(e)}", messages.ERROR)
                # 重定向回AgentList列表页
                return HttpResponseRedirect(request.get_full_path())
        
        # GET请求时调用父类方法渲染导入表单
        return super().import_action(request, *args, **kwargs)

步骤4:自定义导入模板(可选)

如果默认模板无法正确渲染自定义表单字段,可复制官方import.html模板到项目admin目录下,确保表单正常显示:

{% extends "admin/base_site.html" %}
{% load i18n admin_urls static %}

{% block extrahead %}{{ block.super }}
<script type="text/javascript" src="{% url 'admin:jsi18n' %}"></script>
{{ media }}
{% endblock %}

{% block bodyclass %}{{ block.super }} {{ opts.app_label }}-{{ opts.model_name }} change-form{% endblock %}

{% block breadcrumbs %}
<div class="breadcrumbs">
<a href="{% url 'admin:index' %}">{% translate 'Home' %}</a>
&rsaquo; <a href="{% url 'admin:app_list' app_label=opts.app_label %}">{{ opts.app_config.verbose_name }}</a>
&rsaquo; <a href="{% url opts|admin_urlname:'changelist' %}">{{ opts.verbose_name_plural|capfirst }}</a>
&rsaquo; {% translate 'Import' %}
</div>
{% endblock %}

{% block content %}
<div id="content-main">
  <form enctype="multipart/form-data" method="post">{% csrf_token %}
    <div>
      {% if form.errors %}
        <p class="errornote">
        {% blocktranslate count counter=form.errors.items|length %}请修正下方错误。{% plural %}请修正下方错误。{% endblocktranslate %}
        </p>
        {{ form.non_field_errors }}
      {% endif %}

      <fieldset class="module aligned">
        {% for field in form %}
          <div class="form-row">
            {{ field.errors }}
            {{ field.label_tag }} {{ field }}
            {% if field.help_text %}
            <p class="help">{{ field.help_text|safe }}</p>
            {% endif %}
          </div>
        {% endfor %}
      </fieldset>

      <div class="submit-row">
        <input type="submit" value="{% translate '导入' %}" class="default">
      </div>
    </div>
  </form>
</div>
{% endblock %}

方案优势

  • 复用官方能力:依托django-import-export成熟的导入逻辑,无需手动解析CSV、处理数据验证和重复值判断
  • 逻辑清晰:导入Agent与创建AgentList的逻辑分离,易于维护和扩展
  • 用户体验一致:错误提示、表单样式与Django Admin原生风格保持统一
  • 扩展性强:后续可轻松添加导入预览、数据更新策略等功能

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 06:05:53