如何用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
相关产品推荐
相关产品推荐

