Trying to embed a form within a form of foreign keys

I’m trying to create a form where a logged in user (PrincipalGuest) can update themselves but also multiple guests (AccompanyingGuest). I want an entire form with the attending and dietary_restrictions field embedded within a form, not just the ModelMultipleChoiceField.

models.py

class Guest(models.Model):
    """ """

    attending = models.ManyToManyField("itinerary.Event", blank=True)
    dietary_restrictions = models.ManyToManyField(DietaryRestrictions, blank=True)
    last_modified = models.DateTimeField(auto_now=True)

    class Meta:
        abstract = True


class PrincipalGuest(Guest, AbstractUser):
    """ """

    USERNAME_FIELD = "email"
    REQUIRED_FIELDS = []

    username = None
    email = models.EmailField(_("email address"), unique=True)
    phone_number = PhoneNumberField(null=True)
    address = AddressField(on_delete=models.CASCADE, null=True)

    objects = PrincipalGuestManager()

    @property
    def name(self):
        return self.first_name + " " + self.last_name

    def __str__(self):
        return self.name


class AccompanyingGuest(Guest):
    """ """

    principal = models.ForeignKey(
        PrincipalGuest, related_name="accompanying_guests", on_delete=models.CASCADE
    )
    first_name = models.CharField(max_length=20)
    last_name = models.CharField(max_length=20)
    is_under_ten = models.BooleanField(default=False)

    @property
    def name(self):
        return self.first_name + " " + self.last_name

    def __str__(self):
        return self.name

views.py

class RendezvousFormView(SuccessMessageMixin, UpdateView):
    template_name = "rsvp.html"
    form_class = RendezvousForm
    success_url = "/rsvp"
    success_message = "Thank you for your RSVP"

    def get_object(self):
        return self.request.user

forms.py

class RendezvousForm(ModelForm):
    """ """

    first_name = CharField(
        label="", widget=TextInput(attrs={"placeholder": "First Name"})
    )
    last_name = CharField(
        label="", widget=TextInput(attrs={"placeholder": "Last Name"})
    )
    email = CharField(label="", widget=TextInput(attrs={"placeholder": "Email"}))
    phone_number = CharField(
        label="", widget=TextInput(attrs={"placeholder": "Phone Number"})
    )
    address = AddressField(
        label="", widget=AddressWidget(attrs={"placeholder": "Home Address"})
    )
    attending = ModelMultipleChoiceField(
        queryset=Event.objects.all(),
        required=False,
        label="Will be attending",
        widget=CheckboxSelectMultiple(attrs={"class": "h4"}),
    )
    dietary_restrictions = ModelMultipleChoiceField(
        DietaryRestrictions.objects.all(),
        required=False,
        widget=CheckboxSelectMultiple(attrs={"class": "h4"}),
    )

    class Meta:
        model = PrincipalGuest
        fields = [
            "first_name",
            "last_name",
            "email",
            "phone_number",
            "address",
            "attending",
            "dietary_restrictions",
        ]

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.label_suffix = ""
        self.fields["accompanying_guests"] = ModelMultipleChoiceField(
            queryset=AccompanyingGuest.objects.filter(principal=kwargs["instance"])
        )

I’m sorry, I don’t believe I understand what you’re asking for here.

It sounds to me like what you’re asking for is that within your form for PrincipleGuest, you want one or more forms for AccompanyingGuest, and within each instance of the AccompanyingGuest forms, you want one or more forms for each of DietaryRestrictions and one or more forms for each of Event.

Is this a correct restatement of your question?

Yes, that’s correct, whilst maintaining the fields for the PrincipalGuest object itself.

So whenever you’re talking about multiple instances of a form for the same object, you’re talking about formsets.

You can nest formsets such that each entry of a form contains a field for a formset for the next level down.

While Django doesn’t directly provide support for nested formsets, it can be made to work. However, you are responsible for managing the prefix attributes for all the formsets to ensure that every formset ends up with unique id attributes. (I talk a little bit about some of the specifics here: Multiples of one formset - #12 by KenWhitesell)

Thanks, I’ll look in to getting this working. It’s suprising that it doesn’t exist “properly” in Django as the admin console does this out-of-the-box.

Nested inlines in the admin? I don’t think so.

I wouldn’t think that

Reading up on this further from the links you kindly sent, I’m guessing I need to make a form for PincipalGuest and then formsets for each AccompnyingGuest?


Here’s proof the admin site does this automatically, out-of-the-box.

It would be one formset for the collection of AccompanyingGuest, with a formset in each one of those form for the DietaryRestrictions and Event.

(And no, that screenshot is not showing nested formsets. You do not have a form for each DietaryRestriction as you are asking for here.)

Then perhaps I haven’t explained it correctly; what I’m trying to replicate is what you see in the admin site, just using ModelMultipleChoiceField for attending and dietary restrictions instead of the default select/option box you see here.

So you’re saying that you only want to replace the widget that is being used to render that field?
I see where you have the widget specified for the attending and dietary_restrictions - if that’s not doing what you want, then no, I’m not understanding what you’re looking for.

They are doing the right thing for PrincipalGuest. Each AccompanyingGuest has a fk to PrincipalGuest and I want to inline the same form for each AccompanyingGuest that has an fk to this PrincipalGuest. I want the AccompanyingGuest formset to be almost the same as the one for PrincipalGuest, but with a couple of fields removed.

If you have a the form that you want to use for one AccompanyingGuest, then you use that form as the form in a formset.

Assuming this can’t be done within RendezvousFormView, is there anyway to make a FormSet on the fly, like this?

def rendezvousFormView(request):
    """ """
    context = {
        "principal_form": PrincipalGuestForm(
            auto_id="principal_%s", instance=request.user
        ),
        "accompanying_formset": [],
    }

    for guest in request.user.accompanying_guests.all():
        context["accompanying_formset"].append(
            AccompanyingGuestForm(
                auto_id=f"accompanying_{guest.id}_%s",
                instance=guest,
                principal=request.user,
            )
        )

    return render(request, "rsvp.html", context=context)

I’m guessing I’m also going to have to write the POST section, assuming I can’t use UpdateView?

A modelformset creates a formset for the set of models related to another model. Use the modelformset_factory to create the formset class, and then create an instance of the formset to be rendered. And just like any normal formset, you can specify what form class to use for the individual forms.

The issues I’m having with modelformsets are:

  1. I cannot seem to populate them with the instance (that’s what I’m doing in this view example).
  2. If you look at the original post, I have custom ModelForms for both models and cannot seem to use these with the modelformset factory.

Please post the code where you’re calling modelformset_factory and the creation of the formset in your view.

See the example at Creating forms from models | Django documentation | Django for using a custom form in a formset.

Okay, thanks, I’ve got it rendering with:
forms.py

...
AccompanyingGuestFormSet = modelformset_factory(
    AccompanyingGuest, form=AccompanyingGuestForm, extra=0
)

views.py

def rendezvousFormView(request):
    """ """
    context = {
        "principal_form": PrincipalGuestForm(
            auto_id="principal_%s", instance=request.user
        ),
        "accompanying_formset": AccompanyingGuestFormSet(
            queryset=request.user.accompanying_guests.all()
        ),
    }

    if request.method == "POST":
        form = PrincipalGuestForm(request.POST, instance=request.user)
        if form.is_valid():
            if form.cleaned_data:
                form.save()

        formset = AccompanyingGuestFormSet(
            request.POST, queryset=request.user.accompanying_guests.all()
        )
        if formset.is_valid():
            for form in formset:
                if form.cleaned_data:
                    form.save()

    return render(request, "rsvp.html", context=context)

But, of course, POST doesn’t work because we’re still mixing forms.

Anytime you have more than one form or formset on a page, you should be using the prefix attribute on all (or all-but-one) to ensure that there are no duplicated field names between the forms or formsets.

Also, you don’t need to process the forms individually in a formset. You can treat them as a unit. See the example at Saving objects in the formset. You can usually trust Django to do the right thing. (For example, it knows how to handle the delete flag for forms within the formset.)

I think prefix is what I’ve been looking for all along! It’s now working (along with saving the entire formset rather than for form in formset).

Thanks so much for your paitence and help, if you have a ko-fi (or similar) link or a charity you support, I’d be more than happy to kick a few bucks for your time.