Populating formset with a query

Long time programmer, newer to Django with only a couple lightweight crud and api projects complete. I’m building a survey builder application, and chat-gpt has me knee deep in formsets. Questions are added with a modal that is initiated with a JS call (button pressed). When adding a question, forms are populated by JS, and retrieving the formeset in the view works beautifully. On edit, I need to retrieve the question model and its options (multiple choice) and render the form (and formset). Everything works except I get an empty formset. I don’t know how much code helpers want to see, so I’ll paste in models, forms, form template and the view that’s causing errors. Any pointers in the right direction appreciated.

    survey_section_component = models.ForeignKey(SurveySectionComponent, on_delete=models.CASCADE,
                                                 related_name='surveyquestion_set')
    question_text = models.TextField()
    question_type = models.CharField(max_length=20, choices=[
        ('multiple_choice', 'Multiple Choice'),
        ('short_answer', 'Short Answer'),
        ('paragraph', 'Paragraph'),
        ('dropdown', 'Drop Down'),
        ('checkbox', 'Checkbox'),
        ('rating_scale', 'Rating Scale'),
    ])
    order = models.IntegerField()

    def __str__(self):
        return self.question_text

    # This method can be used to retrieve the options related to this question
    def get_options(self):
        return self.options.all()


class SurveyQuestionOption(models.Model):
    survey_question = models.ForeignKey(SurveyQuestion, on_delete=models.CASCADE, related_name='options')
    option_text = models.CharField(max_length=255)
    order = models.IntegerField()  # Useful if you want to allow ordering of options

    def __str__(self):
        return self.option_text

class SurveyQuestionForm(forms.ModelForm):
    class Meta:
        model = SurveyQuestion
        fields = ['question_text', 'question_type']

    def __init__(self, *args, **kwargs):
        super(SurveyQuestionForm, self).__init__(*args, **kwargs)
        self.fields['question_text'].widget.attrs.update({'class': 'form-control'})
        self.fields['question_type'].widget.attrs.update({'class': 'form-control'})


class SurveyQuestionOptionForm(forms.ModelForm):
    class Meta:
        model = SurveyQuestionOption
        fields = ['option_text', 'order']

    def __init__(self, *args, **kwargs):
        super(SurveyQuestionOptionForm, self).__init__(*args, **kwargs)
        self.fields['option_text'].widget.attrs.update({'class': 'form-control'})
        self.fields['order'].widget.attrs.update({'class': 'form-control', 'type': 'number'})


SurveyQuestionOptionFormSet = inlineformset_factory(
    SurveyQuestion,
    SurveyQuestionOption,
    form=SurveyQuestionOptionForm,
    extra=0  # No extra forms initially
)

def edit_question_component(request, component_id):
    component = get_object_or_404(SurveySectionComponent, id=component_id)
    question = get_object_or_404(SurveyQuestion, survey_section_component=component)

    if request.method == 'POST':
        question_form = SurveyQuestionForm(request.POST, instance=question)
        option_formset = SurveyQuestionOptionFormSet(request.POST, instance=question)
        if question_form.is_valid() and option_formset.is_valid():
            question_form.save()
            option_formset.save()

            return JsonResponse({'success': True, 'component_id': component.id, 'content': question.question_text})
        else:
            return JsonResponse({'success': False, 'errors': question_form.errors})


# Get request, grab the options and build the edit 
    
    question_form = SurveyQuestionForm(instance=question)

    options = question.options.all()
    print(f"Options for question {question.id}:")

    for option in options:
        print(f"Option Text: {option.option_text}, Order: {option.order}")

    option_formset = SurveyQuestionOptionFormSet(queryset=question.options.all())
    print(f"Option FormSet forms: {[form.cleaned_data for form in option_formset]}")
    for form in option_formset:
        print(form.initial)  # This should show the initial data being loaded into the formset
    print(f"Option FormSet {option_formset}")

    form_html = render_to_string('surveys/partials/question_component_form.html', {
        'question_form': question_form,
        'option_formset': option_formset,
        'component_id': component_id,
        'button_text': 'Save Changes',
    }, request=request)
    return JsonResponse({'success': True, 'form_html': form_html})

Output from debugging prints
Options for question 15:
Option Text: Red, Order: 1
Option Text: Blue, Order: 2
Option FormSet forms:
Option FormSet

Hello @iXanadu! It would help if you enclosed your code blocks in three backticks so it formats correctly. You can add the language too for syntax highlighting. e.g.:

```python
class SurveyQuestion(models.Model):
    # etc
```

Great - new here. Edited - thanks for the tip.

Try to pass the queryset like this to your SurveyQuestionOptionFormSet:

option_formset = SurveyQuestionOptionFormSet(request.POST, instance=question, queryset=question.options.all())

Thanks for the help - it didn’t work.

What is the output? Any form errors? Any javascript console errors - messages? Also, post your templates code.

Same output from print statements as before

Output from print statements (below)

Options for question 15:
Option Text: Red, Order: 1
Option Text: Blue, Order: 2
Option FormSet forms: []
Option FormSet <ul class="errorlist nonfield"><li>(Hidden field TOTAL_FORMS) This field is required.</li><li>(Hidden field INITIAL_FORMS) This field is required.</li></ul>

  <div><input type="hidden" name="options-TOTAL_FORMS" id="id_options-TOTAL_FORMS"><input type="hidden" name="options-INITIAL_FORMS" id="id_options-INITIAL_FORMS"><input type="hidden" name="options-MIN_NUM_FORMS" id="id_options-MIN_NUM_FORMS"><input type="hidden" name="options-MAX_NUM_FORMS" id="id_options-MAX_NUM_FORMS"></div>

Relevant part of view:

question_form = SurveyQuestionForm(instance=question)

    options = question.options.all()
    print(f"Options for question {question.id}:")

    for option in options:
        print(f"Option Text: {option.option_text}, Order: {option.order}")

        option_formset = SurveyQuestionOptionFormSet(request.POST, instance=question, queryset=question.options.all())

    print(f"Option FormSet forms: {[form.cleaned_data for form in option_formset]}")
    for form in option_formset:
        print(form.initial)  # This should show the initial data being loaded into the formset
    print(f"Option FormSet {option_formset}")

template:

<form id="component-form" method="post">
    {% csrf_token %}
    <input type="hidden" id="section-id-input" name="section_id" value="{{ section_id }}">
    <input type="hidden" id="component-type-input" name="component_type" value="question">

    <!-- Render the main question form -->
    {{ question_form.as_p }}

    <!-- Container for options, initially hidden -->
    <div class="options-section" style="display: {% if option_formset.total_form_count > 0 %}block{% else %}none{% endif %};">
        {{ option_formset.management_form }}
        {% for form in option_formset %}
            <div class="form-row">
                {{ form.option_text.label_tag }} {{ form.option_text }}
                {{ form.order.label_tag }} {{ form.order }}
                {{ form.DELETE.label_tag }} {{ form.DELETE }}
            </div>
        {% endfor %}
    </div>

    <!-- Button to add new options dynamically -->
    <button type="button" id="add-option" class="btn btn-secondary">Add Option</button>

    <input type="hidden" id="component-id-input" name="component_id" value="{{ component_id|default:'' }}">
    <button type="submit" class="btn btn-primary">{{ button_text }}</button>
</form>

Try this: SurveyQuestionOptionFormSet = inlineformset_factory( SurveyQuestion, SurveyQuestionOption, form=SurveyQuestionOptionForm, extra=1, can_delete=True, fields=['option_text', 'order'] )

and add paste the output here of the edit_question_component:

def edit_question_component(request, component_id):
    component = get_object_or_404(SurveySectionComponent, id=component_id)
    question = get_object_or_404(SurveyQuestion, survey_section_component=component)

    if request.method == 'POST':
        question_form = SurveyQuestionForm(request.POST, instance=question)
        option_formset = SurveyQuestionOptionFormSet(request.POST, instance=question)
        if question_form.is_valid() and option_formset.is_valid():
            question_form.save()
            option_formset.save()
            return JsonResponse({'success': True, 'component_id': component.id, 'content': question.question_text})
        else:
            return JsonResponse({'success': False, 'errors': question_form.errors})

    question_form = SurveyQuestionForm(instance=question)
    option_formset = SurveyQuestionOptionFormSet(instance=question)

    print(f"Options for question {question.id}:")
    for option in question.options.all():
        print(f"Option Text: {option.option_text}, Order: {option.order}")

    print(f"Option FormSet forms: {[form.initial for form in option_formset]}")
    
    for form in option_formset:
        print(f"Form initial: {form.initial}")

    print(f"Option FormSet: {option_formset}")
    print(f"Management form: {option_formset.management_form}")

    form_html = render_to_string('surveys/partials/question_component_form.html', {
        'question_form': question_form,
        'option_formset': option_formset,
        'component_id': component_id,
        'button_text': 'Save Changes',
    }, request=request)
    return JsonResponse({'success': True, 'form_html': form_html})