Multiple Models 1 form and foreignKey


I have a form based on 1 model but the number of columns is growing so I think I should be creating multiple tables.

I will link the tables using a foreign key and was thinking of creating forms across multiple views, kind of like page 1,2,3 etc but I’m not sure how I would pass the key Between pages.

The foreign key would be assigned to a project name, but the project won’t be created until the forms have been completed so I’m not sure how I maintain the project name throughout each form??

Would it be the case that I would need to create the project and submit to database and then go back and complete the remainder of forms?

I hope that makes sense.

I wouldn’t necessary assume that automatically. In many situations it’s a judgement call.

I’ve had a few situations with something like this. I tend to rely upon Form Wizard from the Form Tools package as my first choice.

You have a couple different options, but the essence with all of them is that you need to save the data somewhere between views. You can save it in your tables, you could save it in a temporary work table (avoids having to supply data to non-nullable columns), or in session (which really is basically the same as saving it in a work table).

1 Like

Thanks Ken, i’ve hit about 200 columns. I have setup limits to charFields etc, but was worried that 200 might become unmanagable once data starts building up.

I was thinking that i would have the inital form which would just populate a lot less columns. And from the intial form you can go on from there.

So this would ideally get round the problem of passing on the FK. But what i would need to do is when the first form is submitted it would then load another view but with the details of the first form, the problem i have is that how do i redirect stright back to the submitted details as i wont know what the FK is?

Yes, I would generally say that 200 columns is a bit much. Somewhere about half that is when I start looking to restructure tables - even if it’s only to establish one-to-one relationships.
There are very few cases I’ve ever encountered where a truly normalized database yields a table with more than 50 columns, but there are valid cases where you’re better off with a not-fully-normalized schema.

The form tools referenced above handles this itself.

Doing it manually, you’ve got a couple different choices:

  • You can store a “form status” in session for that user. This has the benefit that the status remains active as long as the session is valid. This means that the user could jump away from that page and come back to it, picking up where they left off.

  • Each form identifies where it forwards to upon a successful submission. For example, the generic class-based views call a method named get_success_url. Your implementation of that method can construct the url for the next step of the process, passing the FK as a URL parameter.

(There are other options, but I would consider them to generally be variations of one of these two.)

The FormWizard looks to be a good solution. I will give that a go. Would you mind if i reply back when i hit a issue :wink:

Thanks, Ken


Hi Ken,

Sorry to ask for help, but i have followed the steps from the link.
But when i load the page it nothing in rendered. Do i need to manually assign the form fields to html a based form using something like

{{ form.instance.project }}

This is my view

class FormWizardView(SessionWizardView):
    template_name = 'pages/project_form.html'
    form_list = [projectForm, projectDetailsForm]

    def done(self, form_list, **kwargs):
        return render(self.request, 'done.html', {
        'form_data': [form.cleaned_data for form in form_list],

    def get(self, request, *args, **kwargs):
            return self.render(self.get_form())
        except KeyError:
            return super().get(request, *args, **kwargs)

and basic forms

class projectForm(forms.Form):
    project_name = forms.CharField(max_length=100)

class projectDetailsForm(forms.Form):
    project_website = URLField(max_length=100)
    project_description = forms.CharField(widget=forms.Textarea)

I also think that i am missing posting the data to the database which i think i need to add to this section of my view?

 def done(self, form_list, **kwargs):
        return render(self.request, 'done.html', {
        'form_data': [form.cleaned_data for form in form_list],

A full sample template is at the bottom of that linked page.

(You didn’t post your “project_form.html” template, so I can’t comment on it.)

Yes, it’s needed. (You already have that in your view?!?!)

Also, I’m not sure why you’re overriding a get method. I don’t see anything in the docs mentioning it, and I’ve never done that.

I was trying to get it working so i added the GET to the view and forgot to remove it.

This is what i have in my html page, sorry forgot to add before.

{% extends 'partials/base.html' %}
{% load static %}
{% block head_title%}
        <title>Form View</title>
{% endblock %}
{% load i18n %}

{% block head %}
{{ }}
{% endblock %}

{% block content %}
<p>Step {{ wizard.steps.step1 }} of {{ wizard.steps.count }}</p>
<form action="" method="post">{% csrf_token %}
{{ wizard.management_form }}
{% if wizard.form.forms %}
    {{ wizard.form.management_form }}
    {% for form in wizard.form.forms %}
        {{ form }}
    {% endfor %}
{% else %}
    {{ wizard.form }}
{% endif %}
{% if wizard.steps.prev %}
<button name="wizard_goto_step" type="submit" value="{{ wizard.steps.first }}">{% trans "first step" %}</button>
<button name="wizard_goto_step" type="submit" value="{{ wizard.steps.prev }}">{% trans "prev step" %}</button>
{% endif %}
<input type="submit" value="{% trans "submit" %}"/>
{% endblock %}

which i took from the site - just to see if i could get it rendered, but what gets displayed is this:

I’d check to ensure my base template (‘partials/base.html’) is correct. You’ve got static text that should be showing up on the page even if nothing else is getting rendered.

It may also be worth looking at what is actually rendered on that page (view source or through the developer tools) to verify what you’re getting.

I’d also look at the console where you’re running this to see if any errors are being generated.

Hi Ken, you were spot on with the base.html. When i remove this the form renders. I have a feeling that base.html is rendering the wizardform off the page? Maybe the CSS is messed up with this.

If i already have a form template, how do i map the formWizard fields to my existing form?

That’s why you want to check what’s actually rendered in the browser. It’ll help you determine what’s being sent out.

The other possibility is that your base template isn’t set up correctly and the blocks aren’t in the right place.

That’s not quite the direction to look at it. Reverse your perspective - tell FormWizard to use a template that you’ve defined as the form for a step. See Using a different template for each form. That template would be the template for just the form, not the entire page.

When i include the base.html it loads all the other elements of my site, like the nav bar etc. Just not the form?

And the form is showing in the view source.

<p>Step 1 of 2</p>
<form action="" method="post"><input type="hidden" name="csrfmiddlewaretoken" value="8mqaMrn4E0gb8S5uNNHCohj0R9aZ53ecqx7KcCPCCY5RiwusJsKASRj8ncI4uaa3">
<input type="hidden" name="contact_wizard-current_step" value="0" id="id_contact_wizard-current_step">

    <tr><th><label for="id_0-subject">Subject:</label></th><td><input type="text" name="0-subject" maxlength="100" required id="id_0-subject"></td></tr>
<tr><th><label for="id_0-sender">Sender:</label></th><td><input type="email" name="0-sender" required id="id_0-sender"></td></tr>


<input type="submit" value="submit"/>

Sorted it. Its was missing the

<div class="main-content">
    <div class="page-content">
        <div class="container-fluid">

within the html form.

Thanks, Ken. I will take a look at using a different template for each form now.

Side note: I use Crispy Forms - my form layout is effectively defined in the form class itself rather than in a template. This also works well.