POST data yielding hidden form missing error

Thanks. I’ve gotten used to the first being 0 in python that I default to that.

I removed objective from the formset:

ObjectiveTextFormSet = modelformset_factory(ObjectiveText, fields = ["objective_text"], extra = 0, widgets={'objective_text': Textarea(attrs={'cols': 15, 'rows': 5})} )

I’ve tried to get error messages but am not sure if I am or not. The terminal, in response to my code, yields:

Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

[21/Oct/2024 10:55:13] "GET /ISO22301/home/ HTTP/1.1" 200 15718
[21/Oct/2024 10:55:13] "GET /static/css/styles.css HTTP/1.1" 304 0
Formset errors: []
Not Vald
{'formset_list': <ObjectiveTextFormFormSet: bound=True valid=False total_forms=0>}
[21/Oct/2024 10:55:19] "POST /ISO22301/data/ HTTP/1.1" 200 638
[21/Oct/2024 10:55:19] "GET /static/css/styles.css HTTP/1.1" 304 0

So it looks like it is using GET to render the initial template, and after I enter data using POST to get the view. I split the home view into one that renders the template as a blank and then a separate view, data, to handle the POST data, just in case having them in the same view was causing the issue by creating a fresh, blank formset when called by POST.

Looking at the print data, the error message is a blank dictionary, but it is not executing the

    if request.method == "POST":

loop.
views (probably don’t need the duplicate formset_list = ObjectiveTextFormSet(request.POST):

def home(request):
    
    # Create unique prefixes for each element
                
        formset_list = [ 
            ObjectiveTextFormSet
                        (prefix=f'row-{i}',
                        queryset=ObjectiveText.objects.filter(
                            objective__objective_row=i
                            ).order_by('objective__objective_num')
                            )
            for i in range(1,7)       
        ]

        objheader = [1,2,3,4,5,6,7,8]

        # Add the formset to context dictionary
        context = {
            'formset_list': formset_list,
            'objheader': objheader,
        }
    
        return render(request, "ISO22301/home.html", context)

def data(request):
    context={}
    formset_list = ObjectiveTextFormSet(request.POST)
    context['formset_list']=formset_list
    print("Formset errors:",formset_list.errors)
    if request.method == "POST":
        formset_list = ObjectiveTextFormSet(request.POST)
    for formset in formset_list:
        if formset.is_valid():
            print("Valid")
            formset.save()
            return render(request, 'ISO22301/generic.html', context)
        else:
            print("Formset errors after checkink if valid:",formset_list.errors)
            return render(request, 'ISO22301/generic.html', context)
    print("Not Vald")
    print(context)
    return render(request, 'ISO22301/home.html', context)

This is exactly the opposite of the common / “default” Django pattern, which is one view to handle both the get and post, where the difference between the two is handled by an if request.method == 'POST' condition.

When you bind the submitted data, it needs to be bound to the same formset(s) structures that were created in for the get. That means you need to create the same list using the same parameters (queryset and prefix) in both cases. (This is one of the reasons why the common pattern is to handle both get and post in the same view - you can share the same code for building the forms.)

Yea, but this is a different situation. When you’re creating a formset, you’ve got some forms being created with existing data, identified by the queryset. You also have the ability to define additional forms for adding new data - and that’s the purpose of the extra attribute. In your case here, you’ve got exactly the right number of objects created, and so don’t need any additional forms to create new objects.

I fixed the view to handle GET and POST. My engineering background makes me want to split problems into smaller parts.

Thanks to your help, I got it working, although I am not sure exactly what is happening.

My error message was:

Errors:  [{'id': ['This field is required.']}, 

repeated for each form.

It appears the id of each objective_text as a hidden field Is required, so when I changed the template to include:

 {% for form in formset %}
    <td>  {{ form.objective_text }} {{ form.objective.as_hidden }} {{ form.id.as_hidden }}</td>
 {% endfor %}

the form were Valid and it saved the data to the db.

It does overwrite the previous data, so for historical purposes I probably need to append the data to the db table or first copy and append the old data to another table.

views.py

def home(request):
    
    # Create unique prefixes for each element
                
        formset_list = [ 
            ObjectiveTextFormSet
                        (prefix=f'row-{i}',
                        queryset=ObjectiveText.objects.filter(
                            objective__objective_row=i
                            ).order_by('objective__objective_num')
                            )
            for i in range(1,7)       
        ]

        objheader = [1,2,3,4,5,6,7,8]

        # Add the formset to context dictionary
        context = {
            'formset_list': formset_list,
            'objheader': objheader,
        }
        if request.method == "POST":
            formset_list=[ObjectiveTextFormSet(request.POST, prefix=f'row-{i}',
                        queryset=ObjectiveText.objects.filter(
                            objective__objective_row=i
                            ).order_by('objective__objective_num')
                            )
            for i in range(1,7)]
            print(formset_list)
            for formset in formset_list:
                if formset.is_valid():
                    print('Valid')
                    formset.save()
                else:
                    for formset in formset_list:
                        print('Errors: ', formset.errors)    
            return render(request, 'ISO22301/home.html', context)
        else:
            for formset in formset_list:
                print('Errors: ', formset.errors)
            return render(request, "ISO22301/home.html", context)

Side note:
I am next going to include the all the entries in the database \to show what has already been entered so someone can save data and then continue to update the data with currently entered values shown. I suspect a variable in the formset_list= that iterates over the data is the approach?

I’m not following what you’re looking to do here.

When you bring up the form, you should see what’s currently in the models. Beyond that, I don’t think I’m “getting” what you’re trying to describe.

Sorry I wasn’t clear.

The data entry template, “home” shows all the current values for objective_test, as desired.

Those entries will be used to populate a different template, that displays everything in nice color coded graphics.

At some point, users will want to be able to roll back an entry or see what the historical text was so I’d like to save it somewhere to be able to recall it if desired. Right now, because the way the db is setup, a new entry overwrites the old, which is fine since I use the table for displaying the current entries.