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

如何在Django中关联多模型表以实现数据导入导出?

实现方案

一、先把模型关联起来

选Musician_1做主表,另外两个表用OneToOneField关联到主表,这样三个表的行就能对应同一个完整对象。修改后的模型代码:

from django.db import models

class Musician_1(models.Model):
    first_name_1 = models.CharField(max_length=50)
    last_name_1 = models.CharField(max_length=50)
    instrument_1 = models.CharField(max_length=100)

class Musician_2(models.Model):
    # 关联主表,删除主表时子表也跟着删除
    musician = models.OneToOneField(Musician_1, on_delete=models.CASCADE, related_name='musician_2')
    first_name_2 = models.CharField(max_length=50)
    last_name_2 = models.CharField(max_length=50)
    instrument_2 = models.CharField(max_length=100)

class Musician_3(models.Model):
    musician = models.OneToOneField(Musician_1, on_delete=models.CASCADE, related_name='musician_3')
    first_name_3 = models.CharField(max_length=50)
    last_name_3 = models.CharField(max_length=50)
    instrument_3 = models.CharField(max_length=100)

二、做一个合并表单

把三个模型的字段整合到一个表单里,提交时一次性保存三个表的数据:

from django import forms
from .models import Musician_1, Musician_2, Musician_3

class MusicianCombinedForm(forms.Form):
    # 整合三个表的所有字段
    first_name_1 = forms.CharField(max_length=50)
    last_name_1 = forms.CharField(max_length=50)
    instrument_1 = forms.CharField(max_length=100)
    
    first_name_2 = forms.CharField(max_length=50)
    last_name_2 = forms.CharField(max_length=50)
    instrument_2 = forms.CharField(max_length=100)
    
    first_name_3 = forms.CharField(max_length=50)
    last_name_3 = forms.CharField(max_length=50)
    instrument_3 = forms.CharField(max_length=100)

    def save(self):
        # 先保存主表数据
        musician1 = Musician_1.objects.create(
            first_name_1=self.cleaned_data['first_name_1'],
            last_name_1=self.cleaned_data['last_name_1'],
            instrument_1=self.cleaned_data['instrument_1']
        )
        # 再保存两个子表,绑定到主表实例
        Musician_2.objects.create(
            musician=musician1,
            first_name_2=self.cleaned_data['first_name_2'],
            last_name_2=self.cleaned_data['last_name_2'],
            instrument_2=self.cleaned_data['instrument_2']
        )
        Musician_3.objects.create(
            musician=musician1,
            first_name_3=self.cleaned_data['first_name_3'],
            last_name_3=self.cleaned_data['last_name_3'],
            instrument_3=self.cleaned_data['instrument_3']
        )
        return musician1

三、视图处理表单提交

写个视图处理表单的GET和POST请求:

from django.shortcuts import render, redirect
from .forms import MusicianCombinedForm

def add_musician(request):
    if request.method == 'POST':
        form = MusicianCombinedForm(request.POST)
        if form.is_valid():
            form.save()
            return redirect('musician_list')  # 跳转到你的数据列表页
    else:
        form = MusicianCombinedForm()
    return render(request, 'add_musician.html', {'form': form})

对应的模板add_musician.html用常规表单写法即可:

<form method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit">保存</button>
</form>

四、数据导入导出

用django-import-export库实现,比自己写代码高效。

第一步:安装并注册库

pip install django-import-export

然后在settings.py的INSTALLED_APPS中添加'import_export'。

第二步:自定义资源类

把三个表的字段合并成一个导入导出资源:

from import_export import resources
from import_export.fields import Field
from .models import Musician_1, Musician_2, Musician_3

class MusicianCombinedResource(resources.ModelResource):
    # 声明子表的字段
    first_name_2 = Field()
    last_name_2 = Field()
    instrument_2 = Field()
    first_name_3 = Field()
    last_name_3 = Field()
    instrument_3 = Field()

    class Meta:
        model = Musician_1
        # 指定要导入导出的所有字段
        fields = ('first_name_1', 'last_name_1', 'instrument_1', 
                  'first_name_2', 'last_name_2', 'instrument_2',
                  'first_name_3', 'last_name_3', 'instrument_3')

    # 导出时从子表提取数据
    def dehydrate_first_name_2(self, obj):
        return obj.musician_2.first_name_2 if hasattr(obj, 'musician_2') else ''

    def dehydrate_last_name_2(self, obj):
        return obj.musician_2.last_name_2 if hasattr(obj, 'musician_2') else ''

    def dehydrate_instrument_2(self, obj):
        return obj.musician_2.instrument_2 if hasattr(obj, 'musician_2') else ''

    def dehydrate_first_name_3(self, obj):
        return obj.musician_3.first_name_3 if hasattr(obj, 'musician_3') else ''

    def dehydrate_last_name_3(self, obj):
        return obj.musician_3.last_name_3 if hasattr(obj, 'musician_3') else ''

    def dehydrate_instrument_3(self, obj):
        return obj.musician_3.instrument_3 if hasattr(obj, 'musician_3') else ''

    # 导入时同时保存子表数据
    def before_save_instance(self, instance, using_transactions, dry_run):
        data = self.data[self.get_row_index(instance)]
        instance.save()
        # 用update_or_create避免重复创建数据
        Musician_2.objects.update_or_create(
            musician=instance,
            defaults={
                'first_name_2': data.get('first_name_2'),
                'last_name_2': data.get('last_name_2'),
                'instrument_2': data.get('instrument_2')
            }
        )
        Musician_3.objects.update_or_create(
            musician=instance,
            defaults={
                'first_name_3': data.get('first_name_3'),
                'last_name_3': data.get('last_name_3'),
                'instrument_3': data.get('instrument_3')
            }
        )

第三步:在admin中注册(或自定义视图)

如果用admin管理数据,直接注册主表并绑定资源类:

from django.contrib import admin
from import_export.admin import ImportExportModelAdmin
from .models import Musician_1
from .resources import MusicianCombinedResource

@admin.register(Musician_1)
class MusicianAdmin(ImportExportModelAdmin):
    resource_class = MusicianCombinedResource

这样在admin后台就能看到导入导出按钮,操作和单表完全一致。

如果不想用admin,自定义导出视图也很简单:

from django.http import HttpResponse
from .resources import MusicianCombinedResource

def export_musicians(request):
    resource = MusicianCombinedResource()
    dataset = resource.export()
    response = HttpResponse(dataset.csv, content_type='text/csv')
    response['Content-Disposition'] = 'attachment; filename="musicians.csv"'
    return response

五、查询完整对象

要获取一个完整对象的所有参数,用select_related一次性关联查询,避免多次数据库请求:

musician1 = Musician_1.objects.select_related('musician_2', 'musician_3').get(id=1)
# 把所有字段整合到一个字典中
full_data = {
    'first_name_1': musician1.first_name_1,
    'last_name_1': musician1.last_name_1,
    'instrument_1': musician1.instrument_1,
    'first_name_2': musician1.musician_2.first_name_2,
    'last_name_2': musician1.musician_2.last_name_2,
    'instrument_2': musician1.musician_2.instrument_2,
    'first_name_3': musician1.musician_3.first_name_3,
    'last_name_3': musician1.musician_3.last_name_3,
    'instrument_3': musician1.musician_3.instrument_3,
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 02:13:12