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

如何用Django 2.2、django-select2与Formset创建关联列表?

实现Grupo与Unidade关联的数据表展示组件

首先咱们先明确模型间的核心关系:Grupo和Unidade是多对多关联,GrupoUnidade作为中间表承担关联作用。先给Grupo模型补全多对多字段定义,让Django能更便捷地处理关联逻辑:

class Grupo(models.Model):
    nome = models.CharField(max_length=60)
    id_condominio = models.ForeignKey(Condominio, on_delete=models.CASCADE)
    # 添加多对多字段,绑定中间表
    unidades = models.ManyToManyField(Unidade, through='GrupoUnidade', related_name='grupos')
    
    class Meta:
        db_table = 'grupo'
    
    def __str__(self):
        return self.nome

接下来分两种核心场景实现需求:基础的关联数据展示,以及结合django-select2的关联管理功能。


一、展示指定Grupo的关联Unidade列表

如果只是需要展示某个Grupo对应的所有Unidade,直接通过模型关联查询即可:

1. 视图代码

from django.shortcuts import get_object_or_404, render
from .models import Grupo

def grupo_unidades_list(request, grupo_id):
    # 获取目标Grupo,不存在则返回404
    grupo = get_object_or_404(Grupo, id=grupo_id)
    # 通过多对多字段直接获取关联的Unidade
    unidades = grupo.unidades.all()
    
    context = {
        'grupo': grupo,
        'unidades': unidades,
    }
    return render(request, 'grupo_unidades_list.html', context)

2. 模板代码(grupo_unidades_list.html)

<h2>Grupo: {{ grupo.nome }}</h2>
<table border="1" cellpadding="8">
    <thead>
        <tr>
            <th>Tipo de Unidade</th>
            <th>Número da Unidade</th>
            <th>Condomínio</th>
        </tr>
    </thead>
    <tbody>
        {% for unidade in unidades %}
        <tr>
            <td>{{ unidade.tipo.nome }}</td> <!-- 假设TipoUnidade有nome字段,按需调整 -->
            <td>{{ unidade.unidade }}</td>
            <td>{{ unidade.id_condominio.nome }}</td> <!-- 假设Condominio有nome字段,按需调整 -->
        </tr>
        {% empty %}
        <tr>
            <td colspan="3">Nenhuma unidade associada a este grupo.</td>
        </tr>
        {% endfor %}
    </tbody>
</table>

二、用django-select2+Formset管理Grupo与Unidade的关联

如果需要实现添加/修改/删除Grupo的关联Unidade,并通过django-select2实现带搜索的下拉选择,结合Formset批量处理,可以按以下步骤操作:

1. 配置django-select2

确保已安装django-select2==7.1.0,然后在settings.py中添加配置:

INSTALLED_APPS = [
    # ... 其他已安装的app
    'django_select2',
]

# 配置缓存(开发环境可用本地缓存,生产建议用Redis)
CACHES = {
    "default": {
        "BACKEND": "django.core.cache.backends.locmem.LocMemCache",
    },
    "select2": {
        "BACKEND": "django.core.cache.backends.locmem.LocMemCache",
        "LOCATION": "select2_cache",
    }
}

SELECT2_CACHE_BACKEND = "select2"

在项目根urls.py中添加select2的路由:

from django.urls import path, include

urlpatterns = [
    # ... 其他路由
    path('select2/', include('django_select2.urls')),
]

2. 创建关联选择表单

用ModelSelect2Widget实现带搜索的Unidade选择:

from django import forms
from django_select2.forms import ModelSelect2Widget
from .models import GrupoUnidade, Unidade

class GrupoUnidadeForm(forms.ModelForm):
    class Meta:
        model = GrupoUnidade
        fields = ['id_unidade']
        widgets = {
            'id_unidade': ModelSelect2Widget(
                model=Unidade,
                # 设置搜索字段:支持按单元号、单元类型名称搜索
                search_fields=['unidade__icontains', 'tipo__nome__icontains'],
                attrs={'data-placeholder': 'Buscar unidade...', 'style': 'width: 300px;'}
            )
        }

    def __init__(self, *args, **kwargs):
        self.grupo = kwargs.pop('grupo', None)
        super().__init__(*args, **kwargs)
        # 过滤出当前Grupo所属Condominio的Unidade,避免跨Condominio选择
        if self.grupo:
            self.fields['id_unidade'].widget.queryset = Unidade.objects.filter(id_condominio=self.grupo.id_condominio)

3. 创建Formset

生成用于批量管理关联的Formset:

from django.forms import formset_factory

GrupoUnidadeFormSet = formset_factory(
    GrupoUnidadeForm,
    extra=1,  # 默认显示1个空表单用于新增关联
    can_delete=True,  # 开启删除功能
)

4. 视图代码

处理Formset的提交、验证和数据操作:

from django.shortcuts import get_object_or_404, redirect, render
from .models import Grupo
from .forms import GrupoUnidadeFormSet

def manage_grupo_unidades(request, grupo_id):
    grupo = get_object_or_404(Grupo, id=grupo_id)
    # 生成已关联Unidade的初始表单数据
    initial_data = [{'id_unidade': gu.id_unidade.id} for gu in grupo.grupounidade_set.all()]
    
    if request.method == 'POST':
        formset = GrupoUnidadeFormSet(request.POST, grupo=grupo)
        if formset.is_valid():
            # 删除标记为删除的关联
            for form in formset.deleted_forms:
                if form.cleaned_data.get('id_unidade'):
                    GrupoUnidade.objects.filter(id_grupo=grupo, id_unidade=form.cleaned_data['id_unidade']).delete()
            # 添加新的关联(避免重复)
            for form in formset:
                if form.cleaned_data and not form.cleaned_data.get('DELETE'):
                    unidade = form.cleaned_data['id_unidade']
                    if not GrupoUnidade.objects.filter(id_grupo=grupo, id_unidade=unidade).exists():
                        GrupoUnidade.objects.create(id_grupo=grupo, id_unidade=unidade)
            # 保存后跳转到列表页
            return redirect('grupo_unidades_list', grupo_id=grupo.id)
    else:
        formset = GrupoUnidadeFormSet(initial=initial_data, grupo=grupo)
    
    context = {
        'grupo': grupo,
        'formset': formset,
    }
    return render(request, 'manage_grupo_unidades.html', context)

5. 模板代码(manage_grupo_unidades.html)

<h2>Gerenciar Unidades do Grupo: {{ grupo.nome }}</h2>
<form method="post">
    {% csrf_token %}
    {{ formset.management_form }} <!-- 必须包含的Formset管理字段 -->
    <table border="1" cellpadding="8">
        <thead>
            <tr>
                <th>Selecionar Unidade</th>
                <th>Remover</th>
            </tr>
        </thead>
        <tbody>
            {% for form in formset %}
            <tr>
                <td>{{ form.id_unidade }}</td>
                <td>{{ form.DELETE }}</td>
                <!-- 渲染所有隐藏字段,Formset依赖这些字段 -->
                {% for hidden in form.hidden_fields %}
                    {{ hidden }}
                {% endfor %}
            </tr>
            {% endfor %}
        </tbody>
    </table>
    <button type="submit" style="margin-top: 16px;">Salvar Alterações</button>
</form>

<!-- 自动引入select2的CSS和JS -->
{{ formset.media }}

三、配置路由

在app的urls.py中添加对应路由:

from django.urls import path
from .views import grupo_unidades_list, manage_grupo_unidades

urlpatterns = [
    path('grupo/<int:grupo_id>/unidades/', grupo_unidades_list, name='grupo_unidades_list'),
    path('grupo/<int:grupo_id>/unidades/manage/', manage_grupo_unidades, name='manage_grupo_unidades'),
]

注意事项

  • 模板中引用的TipoUnidade.nome、Condominio.nome需根据你的实际模型字段调整,确保字段存在。
  • django-select2的search_fields要匹配Unidade模型的实际字段,保证搜索功能正常。
  • Formset的can_delete功能依赖模板中的{{ form.DELETE }}复选框,不要遗漏。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 06:40:12