Paul Hermans
Paul Hermans

Reputation: 65

Django filter the queryset of ModelChoiceField - what did i do wrong?

I know that many questions exist about this same topic, but i am confused on one point. My intent is to show two ModelChoiceFields on the form, but not directly tie them to the Game model.

I have the following:

forms.py

class AddGame(forms.ModelForm):
    won_lag = forms.ChoiceField(choices=[('1','Home') , ('2', 'Away') ])
    home_team = forms.ModelChoiceField(queryset=Player.objects.all())
    away_team = forms.ModelChoiceField(queryset=Player.objects.all())

    class Meta:
        model = Game
        fields = ('match', 'match_sequence')

Views.py

def game_add(request, match_id):
    game = Game()
    try:
        match = Match.objects.get(id=match_id)
    except Match.DoesNotExist:
        # we have no object!  do something
        pass

    game.match = match

    # get form
    form = AddGame(request.POST or None, instance=game)
    form.fields['home_team'].queryset = Player.objects.filter(team=match.home_team )

    # handle post-back (new or existing; on success nav to game list)
    if request.method == 'POST':
        if form.is_valid():
            form.save()
            # redirect to list of games for the specified match
            return HttpResponseRedirect(reverse('nine.views.list_games'))

    ...

Where i am confused is when setting the queryset filter. First i tried:

form.home_team.queryset = Player.objects.filter(team=match.home_team )

but i got this error

AttributeError at /nine/games/new/1 
'AddGame' object has no attribute 'home_team'
...

so i changed it to the following: (after reading other posts)

form.fields['home_team'].queryset = Player.objects.filter(team=match.home_team )

and now it works fine.

So my question is, what is the difference between the two lines? Why did the second one work and not the first? I am sure it is a newbie (i am one) question, but i am baffled.

Any help would be appreciated.

Upvotes: 1

Views: 1110

Answers (2)

Paulo Bu
Paulo Bu

Reputation: 29794

Django Forms are metaclasses:

>>> type(AddGame)
<class 'django.forms.forms.DeclarativeFieldsMetaclass'>

They basically create a form instance according to the information given in its definition. This means, you won't get exactly what you see when you define the AddGame form. When you instantiate it, the metaclass will return the proper instance with the fields provided:

>>> type(AddGame())
<class 'your_app.forms.AddGame'>

So, with the instance, you can access the fields by simply doing form.field. In fact, it is a bit more complicated than that. There are two types of fields you can access. With form['field'] you'll be accessing a BoundField. Which is used for output and raw_input.

By doing form.fields['fields'] you'll be then accessing to a field that python can understand. This is because if the from already got any input, there's where validation and data conversion take places (in fact, those are the fields used for this, the general process of validation is a bit more complicated).

I hope this might clear a little the issue for you but as you may see, the whole form's API is really big and complicated. Is very simple for end-users but it has a lot of programming behind the curtains :)

Reading the links provides will help clear your doubts and will improve your knowledge about this very useful topic and Django in general.

Good luck!

UPDATE: By the way, if you want to learn more about Python's Metaclasses, this is a hell of an answer about the topic.

Upvotes: 3

Hieu Nguyen
Hieu Nguyen

Reputation: 8623

In you views.py, you have this line:

form = AddGame(request.POST or None, instance=game)

So form is a Form object of class AddGame (Side note: you should change the name to AddGameForm to avoid confusion).

Since home_team is a field in AddGame class, it's not an attribute in form object. That's why you can't access it via form.home_team.

However, Django Form API provides fields attribute to any form object, which is a dict contains all form fields. That's why you can access form.fields['home_team'].

And finally since home_team is a ModelChoiceField, it can contain a queryset attribute, that's why you can access form.fields['home_team'].queryset

Upvotes: 0

Related Questions