Welcome to the Treehouse Community

The Treehouse Community is a meeting place for developers, designers, and programmers of all backgrounds and skill levels to get support. Collaborate here on code errors or bugs that you need feedback on, or asking for an extra set of eyes on your latest project. Join thousands of Treehouse students and alumni in the community today. (Note: Only Treehouse students can comment or ask questions, but non-students are welcome to browse our conversations.)

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and a supportive community. Start your free trial today.

Python

Vittorio Somaschini
Vittorio Somaschini
33,371 Points

Django model formset POST problem

Hello people.

I have been having the following problem for a few days now and I can't really solve it and since my django app is almost ready to be develop on a webserver (thinking AWS) I am really annoyed. So, as you may have guessed, I getting this problem locally on my machine. I have copied all the involved code to a workspace and here is the snapshot:

https://w.trhou.se/kajyo3lmct Those files are only part of my code and so far everything is working fine.

Let's talk about the problem: I can't POST the data that I am inserting through the formset that I am using ScoreFormSet. (I think) it basically fails to validate after I submit the formset (when inserting a score) I end up on the form_invalid path (and url consequently, this is why I am guessing the problem is the validation).

Also, even if I'm trying to print out all the possible errors in the template, no errors get printed, which make me believe I am not passing in the data to the form somewhere/somehow. The url I am doing this on is http://localhost:8000/gamestream/insert_score/9/ which (I think is correct). The number 9 stands for the id of the fixture the score that I'm trying to insert relates to. I have tried a lot of things already, being stuck for quite a number of days now.

I must add that if in the template I substitute this;

    {% for form in score_form %}
    <p>{{ form }}</p>
    {% endfor %}

with just this:

{{ form }}

In this case only a single form is rendered and I am able to POST the score to the relative fixture (foreignkey field).

Last piece of information: given the fact that I am using class based views and I the formset lessons that we have on Treehouse are based on function based views I have basically followed the following post: http://kevindias.com/writing/django-class-based-views-multiple-inline-formsets/

Any ideas? I really can't debug this!

Maybe the great Kenneth Love can help? ;-)

Thanks, Vittorio

2 Answers

Chris Howell
seal-mask
.a{fill-rule:evenodd;}techdegree seal-36
Chris Howell
Python Web Development Techdegree Graduate 49,610 Points

Hey Vittorio Somaschini

So I forked your Share but nothing is really popping out at me. It is even harder to troubleshoot this issue when you are not able to run the actual project. I cannot mimic the form submission, I am assuming this is a private project for yourself? Like you do not have it up on Git so I can pull the repo and test?

If not that is fine. I would say since you mentioned that a problem is POST'ing data and failure to validate. I would say start reverse engineering that code and starting with the post method on your view.

Like I would probably add some print statements or use PDB to debug this function. Or even break up your conditional check for validation of both the forms into separate conditional checks. This is where I would start then begin working backwards through the different function calls.

def post(self, request, *args, **kwargs):
    """
    Handles POST requests, instantiating a form instance and its inline
    formsets with the passed POST variables and then checking them for
    validity.
    """
    self.object = None
    fixture = Fixture.objects.get(id=self.kwargs['fixture_id'])
    form_class = self.get_form_class()
    form = self.get_form(form_class)
    score_form = forms.ScoreFormSet(self.request.POST, instance=fixture)
    # Break and nest, use print() or pdb to debug
    # which form or if both are failing validation.
    if form.is_valid() and score_form.is_valid(): 
        return self.form_valid(form, score_form)
    else:
        return self.form_invalid(form, score_form)

If you want to add some additional comments with what debug results you got, I can try to help you work through it. But I am limited because I cant run or debug the live project without the files.

Vittorio Somaschini
Vittorio Somaschini
33,371 Points

Hey Chris Howell ! First thanks for your reply. I thought I had provided all the relative code but yes, you can't really test that out will those lines only. Nothing to hide it's just that I have a few files in the project already and I thought it would be heavy to paste all of the code.

Anyway, I have basically tried to debug my code also following the suggestions you provide and... I am able to create instances using an inline formset. So that's good! I have changed my views.py to something very light ( I have basically only overridden the form_valid function in order to focus on that part):

class InsertScoreView(CreateView):
    form_class = forms.ScoreForm
    model = Score
    template_name = 'gamestream/insert_score.html'
    success_url = "/gamestream/score_list/"

    def get_context_data(self, **kwargs):
        fixture = Fixture.objects.get(id=self.kwargs['fixture_id'])
        data = super(InsertScoreView, self).get_context_data(**kwargs)
        if self.request.POST:
            data['scores'] = forms.ScoreFormSet(self.request.POST)
            data['fixture'] = fixture
        else:
            data['scores'] = forms.ScoreFormSet()
            data['fixture'] = fixture
        return data

    def form_valid(self, form, **kwargs):
        fixture = Fixture.objects.get(id=self.kwargs['fixture_id'])
        context = self.get_context_data()
        formset = forms.ScoreFormSet(self.request.POST)
        if formset.is_valid():
            scores = formset.save(commit=False)
            for score in scores:
                score.fixture = fixture
                score.save()

        return super(InsertScoreView, self).form_valid(form)

I will then add the rest of the form and formset handling.

So as I said this actually creates the Score instances that I want it to create, but there is still a problem. I get the following error (after the instances get created as I can see them in the database):

IntegrityError at /gamestream/insert_score/9/ null value in column "fixture_id" violates not-null constraint DETAIL: Failing row contains (212, null, null, null, null).

And yes 200+ tries or so... :D If you look at my models.py the Score fixture foreign key field is required, and can't be blank and that is causing the problem. So I have tried this; I have changed the first 2 lines of the Score model to this (for testing purposes):

class Score(models.Model):
    fixture = models.ForeignKey(Fixture, blank=True, null=True, related_name="score")
    ...

So, it can now accept blank and null for the "fixture" field. With this it all works and the instances get created according to the forms of the formset that I fill in (please see template file below). So, that's what I wanted to achieve, BUT there is also a blank form that gets passed in and a 100% blank row gets saved to the database (new 100% blank instance gets created)!

I really can't explain this behavior, I can't understand where this blank form comes from. No matter how many forms from the formset I fill in with data, only 1 and always 1 blank instance of Score gets created.

template:

<div class="container">
  <h3>Please insert scores for the following match:</h3>
  <h4>{{ fixture.starting_time }} -- {{ fixture.league.league_name }} -- {{ fixture.home_team }} vs {{ fixture.away_team }}</h4>


  <form action="." method="POST">
            {% csrf_token %}
            {{ scores.management_form }}
            {% for form in scores %}
                <p>{{ form }}</p>
            {% endfor %}
            <br>
            <fieldset>
                <legend>Please doublecheck and the press "Insert score"</legend>
                {{ score_form.management_form }}
                {{ score_form.non_form_errors }}
                {% for form in score_form %}
                    {{ form.id }}
                    <div class="inline {{ scores.prefix }}">
                        {{ form.description.errors }}
                        {{ form.description.label_tag }}
                        {{ form.description }}
                        {{ form.erros }}
                        {{ form.non_field_errors }}
                    </div>
                {% endfor %}
            </fieldset>
            <input type="submit" value="Insert score" class="submit" />
        </form>
</div>

I have googled this behavior but can't even find something that points me in the right direction, any ideas?

Again, thanks ;)

Vitto

Vittorio Somaschini
Vittorio Somaschini
33,371 Points

In the end, all I did was to change the class from CreateView to FormView and I got rid of the unexpected behavior.