Multiples of one formset

Hello there,
I am currently building a django website, one of the apps is a advanced poll tool. My aim is to make it similiar to Google/Microsoft forms.
My current problem is that my create view uses formsets but I use several formsets for my choices (per question one) and also they are associated with one poll which is created in the same request and therefore the same page.
Relevant template code:

{{ question_formset.management_form }}

                {% for question_form in question_formset %}

                    <div class="card shadow my-4">

                        <div class="card-header">

                            <div class="row my-0">

                                <div class="col">

                                    {% include "base_inputs/input_field.html" with field=question_form.text field_with_filters=question_form.text|add_class:"my-1 mx-sm-2" %}

                                </div>

                                <div class="row col-auto">

                                    <label for="{{question_form.type.id_for_label}}" class = "col-auto col-form-label">{{question_form.type.label}}</label>

                                    <div class="col-auto">

                                        {% include "base_inputs/input_field.html" with field=question_form.type field_with_filters=question_form.type|add_class:"custom-select my-1 mr-sm-2" %}

                                    </div>

                                </div>

                            </div>

                        </div>

                        {{choice_formset.management_form}}

                        {% for choice_form in choice_formset %}

                            <div class="row card-body">

                                <div class="col-md-6">

                                    {% include "base_inputs/input_field.html" with field=choice_form.text field_with_filters=choice_form.text %}

                                </div>

                            </div>

                        {% endfor %}

                        <div class="card-footer">

                            {% if question_formset.can_delete %}

                                <div class="custom-control custom-switch form-group">

                                    {% include "base_inputs/input_field.html" with field=question_form.DELETE field_with_filters=question_form.DELETE|attr:"class:custom-control-input" %}

                                    <label for="{{question_form.DELETE.id_for_label}}" class="custom-control-label">{{question_form.DELETE.label}}</label>

                                </div>

                            {% endif %}

                        </div>

                    </div>

                {% endfor %}

Sidenote: Yes I am using widget_tweaks because I think it is a bit more intuitive and gives me better control over what I want the front-end to look like.

In short my choice_formset is looped over and placed after each question so it is inserted multiple times and therfore there is multiple times the same code.

My question is how do I properly save the data on the server when I get all question with their associated choices in one post request?

I have some ideas but I don’t think they are the proper way of handling this. My first idea is to just leave the template as is and looping over the resulting list in the view and manually saving the data.
Another idea would be to use ajax and saving the question and choices everytime something is changed but I am not very familiar to ajax and would like to have another solution.

Here is my current template fully rendered (yes I am german):


I intent to add some JQuery code and buttons to let the user add questions and choices depending on the question type.

The rest of the source code that might be useful

models.py

class Poll (models.Model):

    creator = models.ForeignKey(SiteUser, on_delete = models.CASCADE, related_name = 'polls', verbose_name = 'Ersteller')

    creation_date = models.DateTimeField(auto_now_add = True, verbose_name = 'Erstellungsdatum')

    start_date = models.DateTimeField(verbose_name = 'Startdatum')

    end_date = models.DateTimeField(verbose_name = 'Enddatum')

    title = models.CharField(max_length = 150, verbose_name = 'Titel')

    token = models.CharField('Token für url', max_length = 32)

    multiple_votes = models.BooleanField('Mehrmals Abstimmen')

    class Meta:

        verbose_name = "Poll"

        verbose_name_plural = "Polls"

    def __str__(self):

        return f'{self.title}'

class Question (models.Model):

    poll = models.ForeignKey(Poll, on_delete = models.CASCADE, related_name = 'question', verbose_name = 'zugehörige Umfrage')

    text = models.CharField(max_length = 128, verbose_name = 'Frage')

    required = models.BooleanField(verbose_name = 'benötigt', default = False)

    LITTLETEXT = 'text'

    TEXT = 'textarea'

    DATEFIELD = 'date'

    CHECKBOX = 'checkbox'

    RADIO = 'radio'

    SELECT = 'select'

    TYPE_CHOICES = [

        (TEXT, 'Langer Text'),

        (LITTLETEXT, 'Kurzer Text'),

        (DATEFIELD, 'Datum'),

        (CHECKBOX, 'Mehrfachauswahl'),

        (RADIO, 'Einzelauswahl'),

        (SELECT, 'Dropdown'),

    ]

    type = models.CharField('Fragetyp', max_length = 150, choices = TYPE_CHOICES)

    class Meta:

        verbose_name = "Question"

        verbose_name_plural = "Questions"

    def __str__(self):

        return f'{self.text}'

class Choice (models.Model):

    question = models.ForeignKey(Question, on_delete = models.CASCADE, related_name = 'choices', verbose_name = 'zugehörige Frage')

    text = models.CharField(max_length = 128, verbose_name = 'Option')

    class Meta:

        verbose_name = "Choice"

        verbose_name_plural = "Choices"

    def __str__(self):

        return f'{self.text}'

views.py so far

def create(request):

    QuestionInlineFormset = inlineformset_factory(Poll, Question, fields = ('text', 'type', 'required',), max_num = 100, can_delete = False,)

    ChoiceInlineFormset = inlineformset_factory(Question, Choice, fields = ('text',), max_num = 100, extra = 1, can_delete = False,)

    if request.method == 'POST':

        form = PollCreationForm(request.POST)

        print(request.POST)

        if form.is_valid():

            form.save()

            messages.success(request, 'Umfrage erfolgreich erstellt.')

    else:

        form = PollCreationForm()

    context = {

        'poll_form' : form,

        'question_formset': QuestionInlineFormset,

        'choice_formset': ChoiceInlineFormset,

    }

    return render(request, 'polls/polls_create.html', context)

Start with the docs for Using more than one formset in a view. The Reader’s Digest version is that you’ll want to create separate formsets for each occurrence on a page, using a different prefix for each one.

I am currently using inlineformsets which automatically attach prefixes but I think I have an idea how I could use different prefixes but that would require that I don’t use inlineformsets.
My current idea would be that I attach the prefix of the associated question to the choice and a additional prefix specifying that it is a choice. When processing the post request in the view I than would than construct the different prefixes again and pass them to the formset. Then I only need to write JQuery code to correctly copy the fields.
My current source code is attached at the end of my original post if you want to look into it.

If you have multiple formsets on a page, you must assign a unique prefix for each one. I do not see where you are doing that within your code.

If you are not dynamically altering the formset, you don’t need to write any JavaScript to handle them.

I am totally aware of that fact but I think my approach with inlineformsets was wrong because you are not able to change the prefix of those.
And I my intent is indeed to add the possibility to add more forms dynamically.

I’m definitely missing something here - what is making you think that you can’t change the prefix of an inline formset?

I just noticed that I didn’t look at the docs correctly and tried to provide a prefix with the inlineformset_factory() function which resulted in an error. I think I have an idea to get it to work correctly.

Overall this does not really solve my problem because I need to change the prefix of every occourence in the template and I am not able to this with my current knowledge.


I imagined to change the name of the different choices to something depicted here but I am not able to properly change the name of the different input fields.

I’d like to take a step back for a moment to make sure I’m understanding the situation correctly.

The basic case is that you are building a Poll.
For a Poll, you want all the Question for that poll to be entered on one page.
For each Question, you want all the Choices for that question to be entered on that same page.

Is this correct? Or am I not understanding what you’re trying to do here?

1 Like

That absolutly is correct. If you want I can translate my current page to english so it is easier to understand.

Thanks, we’re good for the moment. I think I’ve got something similar to this, I just need to find it - I’ll post back later when I can remember which project did this…

Just following up on this - I found the project I was thinking of, but it’s not quite a great parallel and quite frankly a bit of a mess. (It’s a module that has been worked on by 3 different people, and it shows.)

About the best I can say about it is that it does use inline formsets with custom prefixes for each.
There are sets of nested loops, where the constructor for the child formset has the parent formset’s prefix passed to it, and concatenated with it to make them all unique.
The format is pretty generic - the topmost level prefix is “t-”, which means each form has IDs like “t-0”, “t-1”, etc.
Each one of those forms is responsible for creating instances of a 2nd level form, which uses a prefix of “-r-” appended to the parent’s prefix so you end up with IDs like t-1-r-0, t-1-r-1, etc.
Finally, each one of those is responsible for creating a 3rd level formset, which uses a prefix of “-d-”, which results in IDs that end up looking like “t-1-r-0-d-0”, “t-1-r-0-d-1”, etc.

Now, since any one of these levels can have forms added to it, the JavaScript has to do a fair amount of work to figure out what the right prefixes need to be for the additions. We’ve created a couple of views that take the current prefix, and using the formset’s empty_form attribute, modifies the prefix to the right values before passing it back out to the client, which the JavaScript then injects into the proper location within the page.

The view which take the submission processes all the forms manually - there’s work that needs to be done with all the entries, it’s not just stored as data in the database. So we iterate through each form, which iterates through each 2nd level form, which iterates through each 3rd level form to process the individual entries. If we didn’t have to do that work, I believe we could just save the top level form and let Django do the rest - but that’s just conjecture on my part.

Bottom line is that it did turn out to be quite a bit of work, but it does work for us.

1 Like

Good news I now have my front-end working with dynamically adding inputs! Also the prefixes are now all correct and in working order. When I got my back-end finished I will post my js code, simplified html and back-end python code here so future visitors can use it as inspiration.

1 Like

We would love to see that :+1:

Sorry for my very late reply.
It has been a while since I worked on the project and I sadly haven’t got clean code so I myself do not quite know 100% how I did it but feel free to look at my code.

The relevant js can be found here
Templates are here pay attention to the includes as the different parts of the form got quite complex
the code for the view (I honestly didn’t really know what I was doing when I wrote that code and still do not really know what is happening but it works more or less)
the forms (I did end up dynamically generating the AnswerForm for the different question types)
the models (I do some more unconventional stuff when getting the question_type field where I create a new field class with changed widget, there is probably a better solution I did not think of)

In general you can poke around the polls directory to find out more about the code in question here, I should probably come back to this at some point and clean the code up and add some more features but that is a problem for the future. The code did work in production for some time now although I only use it occasionally for my personal polls.