Spinnaay Spinnaay - 5 months ago 56
Python Question

Saving Django formset with Many to Many Field

I'm building a little app with a formset that lets me enter a number of game scores each linking to a player via a many to many field. I'm having problems getting this to save properly with various errors. Basic code.

Model.py

class Player(models.Model):
name = models.CharField(max_length=30)

def __unicode__(self):
return self.name

class Score(models.Model):
score = models.DecimalField(max_digits=4, decimal_places=0)
turn = models.ForeignKey(Turn)
players = models.ManyToManyField(Player)

def __unicode__(self):
return self.score


Each score is then joined to a turn but I haven't shown that bit.

Form.py

class NewScore(ModelForm):

class Meta:
model = Score
fields = ('score', 'players',)


View.py

def newscore(request):

NewScoreFormSet = formset_factory(NewScore, extra=2)

if request.method == 'POST':

formset = NewScoreFormSet(request.POST)

if formset.is_valid():

t = Turn.objects.latest('id')

for form in formset:

p = form.cleaned_data.get('players')

scoreSave = form.save(commit=False)
scoreSave.turn = t
scoreSave.save()
scoreSave.players.add(p)


else:
print 'Formset not OK'

return render(request, 'scorer/game.html',
{})

else:

formset = NewScoreFormSet()

return render(request, 'scorer/game.html',
{'formset': formset,})


The issue seems to be around getting the player saved through the many to many field and in this current setup I get the error message


TypeError: int() argument must be a string or a number, not 'QuerySet'


I have also tried removing the line

scoreSave.players.add(p)


but that just doesn't save the player at all. Just the score and turn.

Answer

The add() method used for associating instances in many-to-many relations takes one or more individual model instances. You cannot pass an entire list/queryset of players, but you can add them one by one:

for player in p:
    scoreSave.players.add(player)

Of course this is rather verbose, so you can use python's syntax sugar for calling add() with all instances at once:

scoreSave.players.add(*p) # same as scoreSave.players.add(p[0], p[1], ...)