0

I'm trying to use a form with one field (shown as a dropdown in rendered template.html) to change an attribute of multiple objects of the same model. However, for some reason I can't apply the retrieved form data to the objects.

models.py:

class Player(models.Model):
    class Race(models.TextChoices):
        HUMAN = "Human"
        UNDEAD = "Undead"
    race = models.CharField(choices=Race.choices)

forms.py:

class PlayerRaceForm(ModelForm):
    class Meta:
        model = Player
        fields = [
            'race'
        ]

views.py:

def index(request):
    players = Player.objects.all()
    player_race_form = PlayerRaceForm()
    if request.method == 'POST':
        print(player_race_form.errors) # nothing here
        print(player_race_form.non_field_errors) # see next code block
        if player_race_form.is_valid(): # it aint for some reason :/
            for player in players:
                player.race = player_race_form.cleaned_data['race']
                player.save()
    
    context = {'player_race_form': player_race_form,}
    return render(request, 'template.html', context)

On print(player_race_form.non_field_errors):

<bound method BaseForm.non_field_errors of <PlayerRaceForm bound=False, valid=False, fields=(race)>>

I tried to bypass the is_valid and cleaned_data and made it work somehow, but it saved a noticeably wrong value not in the Race class.

def index(request):
    players = Player.objects.all()
    player_race_form = PlayerRaceForm()
    if request.method == 'POST':
        print(player_race_form.errors) # nothing here
        print(player_race_form.non_field_errors) # see next code block
        for player in players:
            player.race = player_race_form['race'].value()
            # player.race = player_race_form.data['race'] #MultiValueDictKeyError
            player.save()

    context = {'player_race_form': player_race_form,}
    return render(request, 'template.html', context)

Also tried to modify one object only:

def index(request):
    players = Player.objects.all()
    player_race_form = PlayerRaceForm()
    if request.method == 'POST':
        print(player_race_form.errors) # nothing here
        print(player_race_form.non_field_errors) # see next code block
        if player_race_form.is_valid(): # it aint for some reason :/
           player[0].race = player_race_form.cleaned_data['race']
           player[0].save()
    
    context = {'player_race_form': player_race_form,}
    return render(request, 'template.html', context)

Still doesn't work.

While it's theoretically easy to just hack it by making a few if-else statements matching the value from the form to the actual value inside Race, it's kinda wasteful to do so, so I'd like a Django way around this issue.

mashedpotatoes
  • 395
  • 2
  • 20

1 Answers1

0

I have reproduced your error. Then using suggestions from GeeksForGeeks about forms I have changed your code in few places:

models.py:

class Player(models.Model):
    class Race(models.TextChoices):
        HUMAN = "Human"
        UNDEAD = "Undead"
    race = models.CharField(max_length=6, choices=Race.choices)  # added max_lenght because of makemigrations error

forms.py:

from django import forms  # imported it and used it below 
from projects.models import Player


class PlayerRaceForm(forms.ModelForm):  # instead of ModelForm
    class Meta:
        model = Player
        fields = "__all__"  # geeks suggestion, but yours work just fine

views.py:

def index(request):
    context = {}
    player_race_form = PlayerRaceForm(request.POST or None, request.FILES or None)
    if player_race_form.is_valid():
        player_race_form.save()
        # print(player_race_form)  # uncomment ii to see valid, saved form

    context['form'] = player_race_form
    return render(request, 'projects/template.html', context)

also my templates.html looks like this:

<form action = "" method = "post">
    {% csrf_token %}
    {{form}}
    <input type="submit" value="Submit">
</form>

and my output in console:

<tr><th><label for="id_race">Race:</label></th><td><select name="race" required id="id_race">
  <option value="">---------</option>

  <option value="Human" selected>Human</option>

  <option value="Undead">Undead</option>

</select></td></tr>

I think, it is saving the selected value correctly :)

  • Hello! Does it save? My issue is that it doesn't save for some reason, I can show the dropdown list already. I apologize for the misunderstanding – mashedpotatoes Oct 28 '20 at 08:52