基于Django多对多through模型,如何创建含双外键的Inline Formset
问题描述
我有继承自AbstractUser的User模型和Relationship模型,User模型的friends字段是通过Relationship实现的自关联非对称多对多字段。
模型代码
class User(AbstractUser): username = models.CharField( verbose_name="username", max_length=256, unique=True) first_name = models.CharField(verbose_name="first name", max_length=30) last_name = models.CharField(verbose_name="last name", max_length=30) slug = models.SlugField(max_length=255, unique=True, blank=True) date_joined = models.DateTimeField( verbose_name="joined date", auto_now_add=True) friends = models.ManyToManyField('self', through='Relationship', symmetrical=False, related_name='following') is_active = models.BooleanField(default=True) is_superuser = models.BooleanField(default=False) is_staff = models.BooleanField(default=False) def __str__(self): return str(self.username) def save(self, *args, **kwargs): if not self.slug: self.slug = slugify(self.username) super().save(*args, **kwargs) class Relationship(models.Model): from_person = models.ForeignKey( User, related_name='from_people', on_delete=models.CASCADE) to_person = models.ForeignKey( User, related_name='to_people', on_delete=models.CASCADE) def __str__(self): return f"from {self.from_person} to {self.to_person}"
我想创建一个包含from_person和to_person字段的inline formset,用来为用户建立关联,但尝试实现时,因为fk_name的限制没法同时显示这两个字段,求解决办法。
尝试的视图代码
def staff_profile_view(request): user = User.objects.get_by_natural_key(request.user) AssignFriendsFormSet = inlineformset_factory( User, Relationship, fk_name='from_person', fields=('to_person', 'from_person',)) formset = AssignFriendsFormSet( queryset=Relationship.objects.none(), instance=user) if request.method == "POST": formset = AssignFriendsFormSet(request.POST, instance=user) if formset.is_valid(): formset.save() return render(request, 'staff_profile.html',{"formset": formset})
解决方案
问题核心是inlineformset_factory的默认逻辑:指定fk_name后,该外键会自动绑定到传入的instance对象,默认不会在表单中展示。要同时显示两个字段,有两种可行方案:
方案一:自定义Inline表单,手动添加from_person字段
- 先定义自定义的Relationship表单,手动加入
from_person字段:
from django import forms from .models import Relationship, User class RelationshipForm(forms.ModelForm): from_person = forms.ModelChoiceField(queryset=User.objects.all(), required=True) class Meta: model = Relationship fields = ('from_person', 'to_person')
- 在视图中使用这个自定义表单创建inline formset:
def staff_profile_view(request): user = User.objects.get_by_natural_key(request.user) # 关联自定义表单,同时保留fk_name指定 AssignFriendsFormSet = inlineformset_factory( User, Relationship, form=RelationshipForm, fk_name='from_person', fields=('from_person', 'to_person') ) if request.method == "POST": formset = AssignFriendsFormSet(request.POST, instance=user) if formset.is_valid(): formset.save() return redirect('staff_profile') # 保存后重定向刷新页面 else: # 可选:给新表单默认填充当前用户为from_person formset = AssignFriendsFormSet( queryset=Relationship.objects.none(), instance=user, initial=[{'from_person': user}] ) return render(request, 'staff_profile.html', {"formset": formset})
如果需要限制from_person只能是当前用户,可以在表单中添加校验逻辑:
class RelationshipForm(forms.ModelForm): from_person = forms.ModelChoiceField(queryset=User.objects.all(), required=True) class Meta: model = Relationship fields = ('from_person', 'to_person') def clean_from_person(self): from_person = self.cleaned_data.get('from_person') if from_person != self.instance: raise forms.ValidationError("只能选择当前用户作为关联发起者") return from_person
方案二:使用ModelFormSet替代InlineFormSet
如果不需要和User实例强绑定的inline逻辑,直接用ModelFormSet更灵活:
from django.forms import modelformset_factory def staff_profile_view(request): RelationshipFormSet = modelformset_factory( Relationship, fields=('from_person', 'to_person'), extra=1 ) if request.method == "POST": formset = RelationshipFormSet(request.POST) if formset.is_valid(): formset.save() return redirect('staff_profile') else: formset = RelationshipFormSet(queryset=Relationship.objects.none()) return render(request, 'staff_profile.html', {"formset": formset})
内容的提问来源于stack exchange,提问作者Menna Magdy
相关产品推荐
相关产品推荐

