I have code snippets below, but the summary of my issue is this:
When displaying a formset with a known number of extra forms, each needing initialization with data from another object, the form's __init__() function is called for each form in the formset, and then one extra time. This causes an error, because the in the last call to __init__(), kwargs does not contain the expected item used for initialization.
My friends and I play a spreadsheet-based sports picking game which is very tedious to make changes to. I've wanted to learn Django for a while so I've been working on creating it as a webapp. Here's the relevant model for my issue:
class Pick(models.Model):
sheet = models.ForeignKey(Sheet)
game = models.ForeignKey(Game)
HOME = 'H'
AWAY = 'A'
PICK_TEAM_CHOICES = (
(HOME, 'Home'),
(AWAY, 'Away'),
)
pick_team = models.CharField(max_length=4,
choices=PICK_TEAM_CHOICES,
default=HOME)
... other stuff
And I've defined a form related to this model. The custom __init__() is so the form is initialized with information from a related Game object, passed with the 'initial' parameter in form creation:
class PickForm(ModelForm):
class Meta:
model = Pick
widgets = {'game': forms.HiddenInput()}
fields = ['sheet','game','amount','pick_type','pick_team']
def __init__(self, *args, **kwargs):
game = kwargs['initial']['game']
super(PickForm, self).__init__(*args, **kwargs)
self.fields['pick_team'].choices = ( ('H', game.home_team), ('A', game.away_team), )
I recently created the 'atomic' case where a user can pick a game via a PickForm in the related template, and that form is processed in the post() method of an adapted class based view. I'm trying to extend this case to handle multiple forms by creating a formset of PickForms:
class GameList(ListView):
template_name = 'app/games.html'
context_object_name = 'game_list'
def get_queryset(self):
games = get_list_or_404(Game, week = self.kwargs['week'])
return games
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
#create a formset of PickForms
PickFormSet = formset_factory(PickForm, extra = len(context['game_list'])-1)
pick_list = []
sheet = Sheet.objects.get(user=self.request.user,league_week=self.kwargs['week'])
picks = Pick.objects.filter(sheet = sheet)
for index, game in enumerate(context['game_list'],start=0):
#logic to create a list of objects for initial data
#create the formset with the pick dictionary
context['pickforms'] = PickFormSet(initial=[{'game':pick.game,
'sheet':pick.sheet,
'amount':pick.amount,
'pick_type':pick.pick_type,
'pick_team':pick.pick_team,} for pick in pick_list])
return context
The get_context_data() in the view contructs the pick_list properly and initializes the PickFormSet- my issue occurs in the template. I'm letting Django handle the rendering so it's very simple right now:
<form action="{% url 'game_list' week %}" method="post">
{{ pickforms }}
<input type="submit" name="pickformset" value="Submit" />
</form>
It seems Django actually initializes the PickForms while rendering the template, because the problem occurs in the __init__() of my PickForm. When debugging, i can step through as it initializes a PickForm for each form in the formset- there are a total of 6 right now. So for 'form-0' (autogenerated prefix, I think) through 'form-5', the initialization works properly, because the kwargs dictionary contains an 'initial', as expected.
However, after initializing those 6 forms, it loops through the __init__() again, for a form with prefix 'form-6' (so a 7th form). This form has no initial data associated with it, and therefore errors out in the __init__() due to a KeyError.
Why is Django attempting to create another form? I have extra = 5 specified in the formset_factory call, so there should only be 6 forms total, each having a related initial data dictionary.
I thought it may be related to the included management_form of the formset, however explicitly rendering that, then using a for loop to iterate over the PickForms didn't work either- I ran into the same issue where the template engine is trying to initialize an extra form without any initial data.
Also: I tried using modelformset_factory and specifying PickForm, however in that case the PickForms seem to be initialized differently. There's no 'initial' data in kwargs, but an 'instance' instead, which behaves differently. I'm still new to Django so I'm confused as to why the two methods would pass different kwargs to the PickForm __init__()