Django: Strange Formset behaviour after first submission

Long post ahead!

This is a follow-up to my previous question, which was about creating a feedback page that associates one form to each user. I was able to get that done, but it was done in what I guess is a hacked-together way because I'm seeing some really strange behaviour after I submit feedback one time.

Let's say I've not submitted any feedback previously, and now I want to submit feedback for two of three people (img). The hidden management_form details appear as follows:

<input id="id_form-TOTAL_FORMS" name="form-TOTAL_FORMS" type="hidden" value="3" />
<input id="id_form-INITIAL_FORMS" name="form-INITIAL_FORMS" type="hidden" value="0" />  
<input id="id_form-MAX_NUM_FORMS" name="form-MAX_NUM_FORMS" type="hidden" value="1000" />

On submit this successfully creates two new entries in the Feedback table, as wanted. The problem is that now if I go to any feedback page (as any user) I see the feedback I have already created and I'll get errors on submit.

If I now go to a page with one or two users I'll see one or two forms (this is good) but the management_form data is incorrect. For example, on a one user page I'll see this management_form data:

<input id="id_form-TOTAL_FORMS" name="form-TOTAL_FORMS" type="hidden" value="3" />
<input id="id_form-INITIAL_FORMS" name="form-INITIAL_FORMS" type="hidden" value="2" />   
<input id="id_form-MAX_NUM_FORMS" name="form-MAX_NUM_FORMS" type="hidden" value="1000" />

And get this error:

MultiValueDictKeyError at /feedback/2/
"u'form-1-id'"

Since I'm only supposed to have one form appear that's all I see, and it's set to the first of the two feedbacks already created, but there's obviously a problem with the management data. Initial should be 0 (not 2) and total forms should be 1 (not 3).

If I go to a page with an equal number of users I'll see the original three feedback forms, with this management_form data:

<input id="id_form-TOTAL_FORMS" name="form-TOTAL_FORMS" type="hidden" value="5" />
<input id="id_form-INITIAL_FORMS" name="form-INITIAL_FORMS" type="hidden" value="2" />
<input id="id_form-MAX_NUM_FORMS" name="form-MAX_NUM_FORMS" type="hidden" value="1000" />

Again, total should be 3 (not 5) and initial should be 0 (not 2). This time I don't get an error though, because I'll get a message saying that I need to fill in values for two forms which don't even appear on the page.

Hopefully that explains the problem well enough, so here's the code:

models.py

class Feedback(models.Model):
    action = models.ForeignKey(Action)
    feedback = models.CharField(max_length=1)
    feedback_by = models.ForeignKey(UserProfile, related_name='feedback_by')
    feedback_for = models.ForeignKey(UserProfile, related_name='feedback_for')
    comment = models.CharField(max_length=200)
    created = models.DateTimeField()
    modified = models.DateTimeField()

    def save(self, *args, **kwargs):        
        if not self.id:
            self.created = datetime.datetime.today()
        self.modified = datetime.datetime.today()

        return super(Feedback, self).save(*args, **kwargs)

forms.py

class FeedbackForm(forms.ModelForm):
    choices = (('g', '(+1) Positive'),
               ('b', '(±0) Negative'),
               ('n', '(-1) No Show'),
               ('d', 'Don\'t Leave Feedback'))
    feedback = forms.ChoiceField(widget=forms.RadioSelect(), choices=choices, initial='d')
    comment = forms.CharField(widget=forms.Textarea())

    class Meta:
        model = Feedback
        fields = ['feedback_for','feedback','comment']

views.py

@login_required
def new_feedback(request, action_id):

    action = get_object_or_404(Action, id=action_id)
    profile = UserProfile.objects.get(user_id=request.user.id)  
    participants = all_info_many_profiles(action.participants.filter(~Q(id=profile.id)))

    fbformset = modelformset_factory(Feedback, form=FeedbackForm, extra=len(participants))

    if request.method == 'POST':    
        formset = fbformset(request.POST, request.FILES)
        if formset.is_valid():
            #formset.save(commit=False)
            for form in formset:
                tmp = form.save(commit=False)
                tmp.action = action
                tmp.feedback_by = profile
                if tmp.feedback != 'd':
                    tmp.save()
            return index(request)
        else:
            print formset.errors
            #return index(request)        
    else:
        formset = fbformset()

    return render(request, 'app/new_feedback.html', 
                  {'action': action, 'participants': participants, 'formset': formset}
                  )

feedback.html

{% load multifor %}
{% block body_block %}
    <h1>Leave Feedback</h1>  

    <form method="post" action="{% url 'app:new_feedback' action.id%}">
        {% csrf_token %}
        {% comment %}
        <input id="id_form-TOTAL_FORMS" name="form-TOTAL_FORMS" type="hidden" value="{{participants.count}}" />
        <input id="id_form-INITIAL_FORMS" name="form-INITIAL_FORMS" type="hidden" value="0" />
        <input id="id_form-MAX_NUM_FORMS" name="form-MAX_NUM_FORMS" type="hidden" value="1000" />
        {% endcomment %}
        {{ formset.management_form }}
        {{ formset.errors }}
        {% for form in formset; participant in participants %}
            {{ form.id }}
            {{ form.errors }}
            <input id="id_form-{{forloop.counter0}}-feedback_for" name="form-{{forloop.counter0}}-feedback_for" type="hidden" value="{{participant.id}}" /> <br />
            {{ form.feedback_for.label }} {{ participant.username }}: <br />
            {% for radio in form.feedback %}
                {{ radio }} <br />
            {% endfor %}<br />
            {{ form.comment.label }} {{ form.comment }} <br /><br />
        {% endfor %}
        <input type="submit" name="submit" value="Submit Feedback" />
    </form>

{% endblock %}

Answers


The problem is that you are using a modelformset_factory, this factory is tied to the model and it will "help" you by populating stuff, this is why the count is at 5.

You might get the result you want by using a plain formset_factory. Django's docs has some examples


Need Your Help

How to stretch and extend a grid up to a parent margin

c# wpf grid parent stretch

I have a very simple structure (semplification of a more complicated one) made like this:

How to Get WCF RIA service URL

asp.net silverlight wcf-ria-services

I have created a WFC RIA Service based on ASP.Net Website and adding the nuget packages for RIA service. I have also created a Service named "FactoryService" by extending DomainService class.